diff --git a/CHANGELOG.md b/CHANGELOG.md index 39f114cc..72501dd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,8 @@ - `BuildpackTomlError::InvalidStarStack` has been replaced by `BuildpackTomlError::InvalidAnyStack`. - Update the Ruby example buildpack to no longer use anyhow and better demonstrate the intended way to work with errors. - `BuildpackTomlError` has been split into `BuildpackApiError` and `StackError`. +- `BuildpackApi` no longer implements `FromStr`, use `BuildpackApi::try_from()` instead. +- Fixed file extension for delimiters when writing `LayerEnv` to disk. - Add an external Cargo command for packaging libcnb buildpacks. See the README for usage. ## [0.3.0] 2021/09/17 diff --git a/README.md b/README.md index 6f465897..735353f9 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ impl Buildpack for HelloWorldBuildpack { // according to the returned value, handle both writing the build plan and exiting with // the correct status code for you. fn detect(&self, _context: DetectContext) -> libcnb::Result { - Ok(DetectResultBuilder::pass().build()) + DetectResultBuilder::pass().build() } // Similar to detect, this method will be called when the CNB lifecycle executes the @@ -147,7 +147,7 @@ impl Buildpack for HelloWorldBuildpack { println!("Hello World!"); println!("Build runs on stack {}!", context.stack_id); - Ok(BuildResultBuilder::new() + BuildResultBuilder::new() .launch(Launch::new().process(Process::new( process_type!("web"), "echo", @@ -155,7 +155,7 @@ impl Buildpack for HelloWorldBuildpack { false, true, ))) - .build()) + .build() } } diff --git a/examples/example-01-basics/buildpack.toml b/examples/example-01-basics/buildpack.toml new file mode 100644 index 00000000..e3bca65e --- /dev/null +++ b/examples/example-01-basics/buildpack.toml @@ -0,0 +1,9 @@ +api = "0.6" + +[buildpack] +id = "libcnb-examples/basics" +version = "0.1.0" +name = "Example libcnb buildpack: basics" + +[[stacks]] +id = "heroku-20" diff --git a/examples/example-01-basics/src/main.rs b/examples/example-01-basics/src/main.rs index ce26c627..56650955 100644 --- a/examples/example-01-basics/src/main.rs +++ b/examples/example-01-basics/src/main.rs @@ -10,12 +10,12 @@ impl Buildpack for BasicBuildpack { type Error = GenericError; fn detect(&self, _context: DetectContext) -> libcnb::Result { - Ok(DetectResultBuilder::pass().build()) + DetectResultBuilder::pass().build() } fn build(&self, context: BuildContext) -> libcnb::Result { println!("Build runs on stack {}!", context.stack_id); - Ok(BuildResultBuilder::new().build()) + BuildResultBuilder::new().build() } } diff --git a/examples/example-02-ruby-sample/buildpack.toml b/examples/example-02-ruby-sample/buildpack.toml index 469ffecc..bf7e97d8 100644 --- a/examples/example-02-ruby-sample/buildpack.toml +++ b/examples/example-02-ruby-sample/buildpack.toml @@ -1,13 +1,10 @@ -# Buildpack API version api = "0.6" -# Buildpack ID and metadata [buildpack] -id = "com.examples.buildpacks.ruby" -version = "0.0.1" -name = "Ruby Buildpack" +id = "libcnb-examples/ruby" +version = "0.1.0" +name = "Example libcnb buildpack: ruby" -# Stacks that the buildpack will work with [[stacks]] id = "heroku-20" diff --git a/examples/example-02-ruby-sample/src/main.rs b/examples/example-02-ruby-sample/src/main.rs index dd27b852..a20d7344 100644 --- a/examples/example-02-ruby-sample/src/main.rs +++ b/examples/example-02-ruby-sample/src/main.rs @@ -22,13 +22,11 @@ impl Buildpack for RubyBuildpack { type Error = RubyBuildpackError; fn detect(&self, context: DetectContext) -> libcnb::Result { - let result = if context.app_dir.join("Gemfile.lock").exists() { + if context.app_dir.join("Gemfile.lock").exists() { DetectResultBuilder::pass().build() } else { DetectResultBuilder::fail().build() - }; - - Ok(result) + } } fn build(&self, context: BuildContext) -> libcnb::Result { @@ -43,7 +41,7 @@ impl Buildpack for RubyBuildpack { }, )?; - Ok(BuildResultBuilder::new() + BuildResultBuilder::new() .launch( Launch::new() .process(Process::new( @@ -61,7 +59,7 @@ impl Buildpack for RubyBuildpack { false, )), ) - .build()) + .build() } } diff --git a/libcnb-data/Cargo.toml b/libcnb-data/Cargo.toml index 712f1b93..0d082a95 100644 --- a/libcnb-data/Cargo.toml +++ b/libcnb-data/Cargo.toml @@ -12,10 +12,12 @@ readme = "../README.md" include = ["src/**/*", "../LICENSE", "../README.md"] [dependencies] -lazy_static = "^1.4.0" fancy-regex = "^0.7.1" semver = { version = "^1.0.4", features = ["serde"] } serde = { version = "^1.0.126", features = ["derive"] } thiserror = "^1.0.26" toml = "^0.5.8" libcnb-proc-macros = { path = "../libcnb-proc-macros", version = "0.1.0" } + +[dev-dependencies] +serde_test = "1.0.130" diff --git a/libcnb-data/src/buildpack/api.rs b/libcnb-data/src/buildpack/api.rs index 84ea03dc..05a4051e 100644 --- a/libcnb-data/src/buildpack/api.rs +++ b/libcnb-data/src/buildpack/api.rs @@ -1,61 +1,34 @@ use std::convert::TryFrom; +use std::fmt; use std::fmt::{Display, Formatter}; -use std::{fmt, str::FromStr}; -use fancy_regex::Regex; -use lazy_static::lazy_static; use serde::Deserialize; -// Used as a "shadow" struct to store -// potentially invalid `BuildpackApi` data when deserializing -// -#[derive(Deserialize)] -struct BuildpackApiUnchecked(String); - -impl TryFrom for BuildpackApi { - type Error = BuildpackApiError; - - fn try_from(value: BuildpackApiUnchecked) -> Result { - Self::from_str(value.0.as_str()) - } -} - +/// The Buildpack API version. +/// +/// This MUST be in form `.` or ``, where `` is equivalent to `.0`. #[derive(Deserialize, Debug, Eq, PartialEq)] -#[serde(try_from = "BuildpackApiUnchecked")] +#[serde(try_from = "&str")] pub struct BuildpackApi { pub major: u32, pub minor: u32, } -impl FromStr for BuildpackApi { - type Err = BuildpackApiError; - - fn from_str(value: &str) -> Result { - lazy_static! { - static ref RE: Regex = Regex::new(r"^(?P\d+)(\.(?P\d+))?$").unwrap(); - } - - if let Some(captures) = RE.captures(value).unwrap_or_default() { - if let Some(major) = captures.name("major") { - // these should never panic since we check with the regex unless it's greater than - // `std::u32::MAX` - let major = major - .as_str() - .parse::() - .map_err(|_| Self::Err::InvalidBuildpackApi(String::from(value)))?; - - // If no minor version is specified default to 0. - let minor = captures - .name("minor") - .map_or("0", |s| s.as_str()) - .parse::() - .map_err(|_| Self::Err::InvalidBuildpackApi(String::from(value)))?; - - return Ok(Self { major, minor }); - } - } +impl TryFrom<&str> for BuildpackApi { + type Error = BuildpackApiError; - Err(Self::Err::InvalidBuildpackApi(String::from(value))) + fn try_from(value: &str) -> Result { + // We're not using the `semver` crate, since it only supports non-range versions of form `X.Y.Z`. + // If no minor version is specified, it defaults to `0`. + let (major, minor) = value.split_once('.').unwrap_or((value, "0")); + Ok(Self { + major: major + .parse() + .map_err(|_| Self::Error::InvalidBuildpackApi(String::from(value)))?, + minor: minor + .parse() + .map_err(|_| Self::Error::InvalidBuildpackApi(String::from(value)))?, + }) } } @@ -67,38 +40,82 @@ impl Display for BuildpackApi { #[derive(thiserror::Error, Debug)] pub enum BuildpackApiError { - #[error("Found `{0}` but value MUST be in the form `.` or `` and only contain numbers.")] + #[error("Invalid Buildpack API version: `{0}`")] InvalidBuildpackApi(String), } #[cfg(test)] mod tests { + use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; + use super::*; #[test] - fn buildpack_api_from_str_major_minor() { - let result = BuildpackApi::from_str("0.4"); - assert!(result.is_ok()); - if let Ok(api) = result { - assert_eq!(0, api.major); - assert_eq!(4, api.minor); - } + fn deserialize_valid_api_versions() { + assert_de_tokens( + &BuildpackApi { major: 1, minor: 3 }, + &[Token::BorrowedStr("1.3")], + ); + assert_de_tokens( + &BuildpackApi { major: 0, minor: 0 }, + &[Token::BorrowedStr("0.0")], + ); + assert_de_tokens( + &BuildpackApi { + major: 2020, + minor: 10, + }, + &[Token::BorrowedStr("2020.10")], + ); + assert_de_tokens( + &BuildpackApi { major: 2, minor: 0 }, + &[Token::BorrowedStr("2")], + ); } #[test] - fn buildpack_api_from_str_major() { - let result = BuildpackApi::from_str("1"); - assert!(result.is_ok()); - if let Ok(api) = result { - assert_eq!(1, api.major); - assert_eq!(0, api.minor); - } + fn reject_invalid_api_versions() { + assert_de_tokens_error::( + &[Token::BorrowedStr("1.2.3")], + "Invalid Buildpack API version: `1.2.3`", + ); + assert_de_tokens_error::( + &[Token::BorrowedStr("1.2-dev")], + "Invalid Buildpack API version: `1.2-dev`", + ); + assert_de_tokens_error::( + &[Token::BorrowedStr("-1")], + "Invalid Buildpack API version: `-1`", + ); + assert_de_tokens_error::( + &[Token::BorrowedStr(".1")], + "Invalid Buildpack API version: `.1`", + ); + assert_de_tokens_error::( + &[Token::BorrowedStr("1.")], + "Invalid Buildpack API version: `1.`", + ); + assert_de_tokens_error::( + &[Token::BorrowedStr("1..2")], + "Invalid Buildpack API version: `1..2`", + ); + assert_de_tokens_error::( + &[Token::BorrowedStr("")], + "Invalid Buildpack API version: ``", + ); } #[test] fn buildpack_api_display() { assert_eq!(BuildpackApi { major: 1, minor: 0 }.to_string(), "1.0"); assert_eq!(BuildpackApi { major: 1, minor: 2 }.to_string(), "1.2"); - assert_eq!(BuildpackApi { major: 0, minor: 5 }.to_string(), "0.5"); + assert_eq!( + BuildpackApi { + major: 0, + minor: 10 + } + .to_string(), + "0.10" + ); } } diff --git a/libcnb-data/src/buildpack/id.rs b/libcnb-data/src/buildpack/id.rs index f3740fdc..25efd92f 100644 --- a/libcnb-data/src/buildpack/id.rs +++ b/libcnb-data/src/buildpack/id.rs @@ -40,23 +40,46 @@ libcnb_newtype!( /// ``` BuildpackId, BuildpackIdError, - r"^(?!app$|config$)[[:alnum:]./-]+$" + r"^(?!(app|config)$)[[:alnum:]./-]+$" ); #[cfg(test)] mod tests { use super::*; - use std::str::FromStr; #[test] - fn buildpack_id_does_not_allow_app() { - let result = BuildpackId::from_str("app"); - assert!(result.is_err()); + fn buildpack_id_validation_valid() { + assert!("heroku/jvm".parse::().is_ok()); + assert!("Abc123./-".parse::().is_ok()); + assert!("app-foo".parse::().is_ok()); + assert!("foo-app".parse::().is_ok()); } #[test] - fn buildpack_id_does_not_allow_config() { - let result = BuildpackId::from_str("config"); - assert!(result.is_err()); + fn buildpack_id_validation_invalid() { + assert_eq!( + "heroku_jvm".parse::(), + Err(BuildpackIdError::InvalidValue(String::from("heroku_jvm"))) + ); + assert_eq!( + "heroku:jvm".parse::(), + Err(BuildpackIdError::InvalidValue(String::from("heroku:jvm"))) + ); + assert_eq!( + "heroku jvm".parse::(), + Err(BuildpackIdError::InvalidValue(String::from("heroku jvm"))) + ); + assert_eq!( + "app".parse::(), + Err(BuildpackIdError::InvalidValue(String::from("app"))) + ); + assert_eq!( + "config".parse::(), + Err(BuildpackIdError::InvalidValue(String::from("config"))) + ); + assert_eq!( + "".parse::(), + Err(BuildpackIdError::InvalidValue(String::new())) + ); } } diff --git a/libcnb-data/src/buildpack/mod.rs b/libcnb-data/src/buildpack/mod.rs index be3a2f70..8017440e 100644 --- a/libcnb-data/src/buildpack/mod.rs +++ b/libcnb-data/src/buildpack/mod.rs @@ -45,7 +45,6 @@ use serde::Deserialize; /// ``` #[derive(Deserialize, Debug)] pub struct BuildpackToml { - // MUST be in form . or , where is equivalent to .0. pub api: BuildpackApi, pub buildpack: Buildpack, pub stacks: Vec, @@ -244,45 +243,4 @@ id = "*" ] ); } - - #[test] - fn stacks_invalid_stack_id() { - let raw = r#" -api = "0.6" - -[buildpack] -id = "foo/bar" -name = "Bar Buildpack" -version = "0.0.1" - -[[stacks]] -id = "io.buildpacks.stacks.*" -"#; - - let err = toml::from_str::(raw).unwrap_err(); - assert!(err - .to_string() - .contains("Invalid Stack ID: Invalid Value: io.buildpacks.stacks.*")); - } - - #[test] - fn stacks_invalid_any_stack() { - let raw = r#" -api = "0.6" - -[buildpack] -id = "foo/bar" -name = "Bar Buildpack" -version = "0.0.1" - -[[stacks]] -id = "*" -mixins = ["foo", "bar"] -"#; - - let err = toml::from_str::(raw).unwrap_err(); - assert!(err - .to_string() - .contains("Stack with id `*` MUST NOT contain mixins, however the following mixins were specified: `foo`, `bar`")); - } } diff --git a/libcnb-data/src/buildpack/stack.rs b/libcnb-data/src/buildpack/stack.rs index f519cc14..1569c6b6 100644 --- a/libcnb-data/src/buildpack/stack.rs +++ b/libcnb-data/src/buildpack/stack.rs @@ -48,3 +48,69 @@ pub enum StackError { #[error("Invalid Stack ID: {0}")] InvalidStackId(#[from] StackIdError), } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn deserialize_specific_stack_without_mixins() { + let toml_str = r#" +id = "heroku-20" +"#; + assert_eq!( + toml::from_str::(toml_str), + Ok(Stack::Specific { + // Cannot use the `stack_id!` macro due to: https://github.com/Malax/libcnb.rs/issues/179 + id: "heroku-20".parse().unwrap(), + mixins: Vec::new() + }), + ); + } + + #[test] + fn deserialize_specific_stack_with_mixins() { + let toml_str = r#" +id = "io.buildpacks.stacks.focal" +mixins = ["build:jq", "wget"] +"#; + assert_eq!( + toml::from_str::(toml_str), + Ok(Stack::Specific { + id: "io.buildpacks.stacks.focal".parse().unwrap(), + mixins: vec![String::from("build:jq"), String::from("wget")] + }), + ); + } + + #[test] + fn deserialize_any_stack() { + let toml_str = r#" +id = "*" +"#; + assert_eq!(toml::from_str::(toml_str), Ok(Stack::Any)); + } + + #[test] + fn reject_specific_stack_with_invalid_name() { + let toml_str = r#" +id = "io.buildpacks.stacks.*" +"#; + let err = toml::from_str::(toml_str).unwrap_err(); + assert!(err + .to_string() + .contains("Invalid Stack ID: Invalid Value: io.buildpacks.stacks.*")); + } + + #[test] + fn reject_any_stack_with_mixins() { + let toml_str = r#" +id = "*" +mixins = ["build:jq", "wget"] +"#; + let err = toml::from_str::(toml_str).unwrap_err(); + assert!(err + .to_string() + .contains("Stack with id `*` MUST NOT contain mixins, however the following mixins were specified: `build:jq`, `wget`")); + } +} diff --git a/libcnb-data/src/buildpack/stack_id.rs b/libcnb-data/src/buildpack/stack_id.rs index c8a7d492..47bbf638 100644 --- a/libcnb-data/src/buildpack/stack_id.rs +++ b/libcnb-data/src/buildpack/stack_id.rs @@ -41,3 +41,34 @@ libcnb_newtype!( StackIdError, r"^[[:alnum:]./-]+$" ); + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn stack_id_validation_valid() { + assert!("heroku-20".parse::().is_ok()); + assert!("Abc123./-".parse::().is_ok()); + } + + #[test] + fn stack_id_validation_invalid() { + assert_eq!( + "heroku_20".parse::(), + Err(StackIdError::InvalidValue(String::from("heroku_20"))) + ); + assert_eq!( + "heroku:20".parse::(), + Err(StackIdError::InvalidValue(String::from("heroku:20"))) + ); + assert_eq!( + "heroku 20".parse::(), + Err(StackIdError::InvalidValue(String::from("heroku 20"))) + ); + assert_eq!( + "".parse::(), + Err(StackIdError::InvalidValue(String::new())) + ); + } +} diff --git a/libcnb-data/src/launch.rs b/libcnb-data/src/launch.rs index cad4b278..f7502950 100644 --- a/libcnb-data/src/launch.rs +++ b/libcnb-data/src/launch.rs @@ -128,32 +128,36 @@ libcnb_newtype!( /// ``` ProcessType, ProcessTypeError, - r"^[[:alnum:]\._-]+$" + r"^[[:alnum:]._-]+$" ); #[cfg(test)] mod tests { use super::*; - use std::str::FromStr; #[test] - fn test_process_type_eq() { - assert_eq!( - ProcessType::from_str("web").unwrap(), - ProcessType::from_str("web").unwrap() - ); - assert_ne!( - ProcessType::from_str("web").unwrap(), - ProcessType::from_str("nope").unwrap() - ); + fn process_type_validation_valid() { + assert!("web".parse::().is_ok()); + assert!("Abc123._-".parse::().is_ok()); } #[test] - fn test_process_type_with_special_chars() { - assert!(ProcessType::from_str("java_jar").is_ok()); - assert!(ProcessType::from_str("java-jar").is_ok()); - assert!(ProcessType::from_str("java.jar").is_ok()); - - assert!(ProcessType::from_str("java~jar").is_err()); + fn process_type_validation_invalid() { + assert_eq!( + "worker/foo".parse::(), + Err(ProcessTypeError::InvalidValue(String::from("worker/foo"))) + ); + assert_eq!( + "worker:foo".parse::(), + Err(ProcessTypeError::InvalidValue(String::from("worker:foo"))) + ); + assert_eq!( + "worker foo".parse::(), + Err(ProcessTypeError::InvalidValue(String::from("worker foo"))) + ); + assert_eq!( + "".parse::(), + Err(ProcessTypeError::InvalidValue(String::new())) + ); } } diff --git a/libcnb-data/src/layer.rs b/libcnb-data/src/layer.rs index a45e9c22..a184badd 100644 --- a/libcnb-data/src/layer.rs +++ b/libcnb-data/src/layer.rs @@ -47,11 +47,15 @@ mod tests { use super::*; #[test] - fn layer_name_validation() { - assert!("abc 123_!".parse::().is_ok()); + fn layer_name_validation_valid() { + assert!("gems".parse::().is_ok()); + assert!("Abc 123.-_!".parse::().is_ok()); assert!("build-foo".parse::().is_ok()); assert!("foo-build".parse::().is_ok()); + } + #[test] + fn layer_name_validation_invalid() { assert_eq!( "build".parse::(), Err(LayerNameError::InvalidValue(String::from("build"))) diff --git a/libcnb-data/src/newtypes.rs b/libcnb-data/src/newtypes.rs index 46d329e8..94745a0a 100644 --- a/libcnb-data/src/newtypes.rs +++ b/libcnb-data/src/newtypes.rs @@ -53,7 +53,7 @@ macro_rules! libcnb_newtype { $error_name:ident, $regex:expr ) => { - #[derive(Debug, Eq, PartialEq, ::serde::Deserialize, ::serde::Serialize, Clone)] + #[derive(Debug, Eq, PartialEq, ::serde::Serialize, Clone)] $(#[$type_attributes])* pub struct $name(String); @@ -89,6 +89,14 @@ macro_rules! libcnb_newtype { } } + impl<'de> ::serde::Deserialize<'de> for $name { + fn deserialize>(d: D) -> Result { + String::deserialize(d)? + .parse::<$name>() + .map_err(::serde::de::Error::custom) + } + } + impl ::std::borrow::Borrow for $name { fn borrow(&self) -> &String { &self.0 @@ -143,6 +151,7 @@ pub(crate) use libcnb_newtype; #[cfg(test)] mod tests { use super::libcnb_newtype; + use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; libcnb_newtype!( newtypes::tests, @@ -167,6 +176,18 @@ mod tests { ); } + #[test] + fn test_type_eq() { + assert_eq!( + "Katrin".parse::(), + "Katrin".parse::() + ); + assert_ne!( + "Katrin".parse::(), + "Manuel".parse::() + ); + } + #[test] fn test_literal_macro_success() { assert_eq!("Jonas", capitalized_name!("Jonas").as_ref()); @@ -181,4 +202,13 @@ mod tests { let name = "Johanna".parse::().unwrap(); foo(&name); } + + #[test] + fn test_deserialize() { + assert_de_tokens(&capitalized_name!("Jonas"), &[Token::Str("Jonas")]); + assert_de_tokens(&capitalized_name!("Johanna"), &[Token::Str("Johanna")]); + + assert_de_tokens_error::(&[Token::Str("Manuel")], "Invalid Value: Manuel"); + assert_de_tokens_error::(&[Token::Str("katrin")], "Invalid Value: katrin"); + } } diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index 99a26f35..605558a7 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -62,7 +62,7 @@ impl BuildContext { /// &example_layer.content_metadata.metadata.monologue /// ); /// - /// Ok(BuildResultBuilder::new().build()) + /// BuildResultBuilder::new().build() /// } /// } /// @@ -130,13 +130,13 @@ pub(crate) enum InnerBuildResult { /// /// # Examples: /// ``` -/// use libcnb::build::BuildResultBuilder; +/// use libcnb::build::{BuildResultBuilder, BuildResult}; /// use libcnb::data::launch::{Launch, Process}; /// use libcnb::data::process_type; /// -/// let simple = BuildResultBuilder::new().build(); +/// let simple: Result = BuildResultBuilder::new().build(); /// -/// let with_launch = BuildResultBuilder::new() +/// let with_launch: Result = BuildResultBuilder::new() /// .launch(Launch::new().process(Process::new(process_type!("type"), "command", vec!["-v"], false, false))) /// .build(); /// ``` @@ -155,7 +155,18 @@ impl BuildResultBuilder { } impl BuildResultBuilder { - pub fn build(self) -> BuildResult { + /// Builds the final [`BuildResult`]. + /// + /// This method returns the [`BuildResult`] wrapped in a [`Result`] even though its technically + /// not fallible. This is done to simplify using this method in the context it's most often used + /// in: a buildpack's [build method](crate::Buildpack::build). + /// + /// See [`build_unwrapped`](Self::build_unwrapped) for an unwrapped version of this method. + pub fn build(self) -> Result { + Ok(self.build_unwrapped()) + } + + pub fn build_unwrapped(self) -> BuildResult { BuildResult(InnerBuildResult::Pass { launch: self.launch, store: self.store, diff --git a/libcnb/src/detect.rs b/libcnb/src/detect.rs index 687ad1df..b2ee7a3c 100644 --- a/libcnb/src/detect.rs +++ b/libcnb/src/detect.rs @@ -33,13 +33,13 @@ pub(crate) enum InnerDetectResult { /// /// # Examples: /// ``` -/// use libcnb::detect::DetectResultBuilder; +/// use libcnb::detect::{DetectResultBuilder, DetectResult}; /// use libcnb_data::build_plan::{BuildPlan, BuildPlanBuilder}; /// -/// let simple_pass = DetectResultBuilder::pass().build(); -/// let simple_fail = DetectResultBuilder::fail().build(); +/// let simple_pass: Result = DetectResultBuilder::pass().build(); +/// let simple_fail: Result = DetectResultBuilder::fail().build(); /// -/// let with_build_plan = DetectResultBuilder::pass() +/// let with_build_plan: Result = DetectResultBuilder::pass() /// .build_plan(BuildPlanBuilder::new().provides("something").build()) /// .build(); /// ``` @@ -62,7 +62,18 @@ pub struct PassDetectResultBuilder { } impl PassDetectResultBuilder { - pub fn build(self) -> DetectResult { + /// Builds the final [`DetectResult`]. + /// + /// This method returns the [`DetectResult`] wrapped in a [`Result`] even though its technically + /// not fallible. This is done to simplify using this method in the context it's most often used + /// in: a buildpack's [detect method](crate::Buildpack::detect). + /// + /// See [`build_unwrapped`](Self::build_unwrapped) for an unwrapped version of this method. + pub fn build(self) -> Result { + Ok(self.build_unwrapped()) + } + + pub fn build_unwrapped(self) -> DetectResult { DetectResult(InnerDetectResult::Pass { build_plan: self.build_plan, }) @@ -79,8 +90,19 @@ impl PassDetectResultBuilder { pub struct FailDetectResultBuilder; impl FailDetectResultBuilder { + /// Builds the final [`DetectResult`]. + /// + /// This method returns the [`DetectResult`] wrapped in a [`Result`] even though its technically + /// not fallible. This is done to simplify using this method in the context it's most often used + /// in: a buildpack's [detect method](crate::Buildpack::detect). + /// + /// See [`build_unwrapped`](Self::build_unwrapped) for an unwrapped version of this method. + pub fn build(self) -> Result { + Ok(self.build_unwrapped()) + } + #[allow(clippy::unused_self)] - pub fn build(self) -> DetectResult { + pub fn build_unwrapped(self) -> DetectResult { DetectResult(InnerDetectResult::Fail) } } diff --git a/libcnb/src/layer/public_interface.rs b/libcnb/src/layer/public_interface.rs index 7ff5189a..49987f87 100644 --- a/libcnb/src/layer/public_interface.rs +++ b/libcnb/src/layer/public_interface.rs @@ -187,10 +187,22 @@ impl LayerResultBuilder { self } + /// Builds the final [`LayerResult`]. + /// + /// This method returns the [`LayerResult`] wrapped in a [`Result`] even though its technically + /// not fallible. This is done to simplify using this method in the contexts it's most often + /// used in: a layer's [create](crate::layer::Layer::create) and/or + /// [update](crate::layer::Layer::update) methods. + /// + /// See [`build_unwrapped`](Self::build_unwrapped) for an unwrapped version of this method. pub fn build(self) -> Result, E> { - Ok(LayerResult { + Ok(self.build_unwrapped()) + } + + pub fn build_unwrapped(self) -> LayerResult { + LayerResult { metadata: self.metadata, env: self.env, - }) + } } } diff --git a/libcnb/src/layer_env.rs b/libcnb/src/layer_env.rs index 3171ce33..141d9a09 100644 --- a/libcnb/src/layer_env.rs +++ b/libcnb/src/layer_env.rs @@ -562,7 +562,7 @@ impl LayerEnvDelta { let file_extension = match modification_behavior { ModificationBehavior::Append => ".append", ModificationBehavior::Default => ".default", - ModificationBehavior::Delimiter => ".delimiter", + ModificationBehavior::Delimiter => ".delim", ModificationBehavior::Override => ".override", ModificationBehavior::Prepend => ".prepend", }; diff --git a/libcnb/src/lib.rs b/libcnb/src/lib.rs index aba79b65..7b43f1f0 100644 --- a/libcnb/src/lib.rs +++ b/libcnb/src/lib.rs @@ -62,11 +62,11 @@ const LIBCNB_SUPPORTED_BUILDPACK_API: data::buildpack::BuildpackApi = /// type Error = GenericError; /// /// fn detect(&self, context: DetectContext) -> libcnb::Result { -/// Ok(DetectResultBuilder::pass().build()) +/// DetectResultBuilder::pass().build() /// } /// /// fn build(&self, context: BuildContext) -> libcnb::Result { -/// Ok(BuildResultBuilder::new().build()) +/// BuildResultBuilder::new().build() /// } /// } ///