From 1d6754f0b2f25aa32ecc9baf8b48ef0263352c80 Mon Sep 17 00:00:00 2001 From: Manuel Fuchs Date: Thu, 13 Jun 2024 12:25:02 +0200 Subject: [PATCH] Polish --- libcnb/src/build.rs | 268 ++++++++++++++++++++++-- libcnb/src/layer/struct_api/handling.rs | 39 ++-- libcnb/src/layer/struct_api/mod.rs | 104 ++++++--- libcnb/src/layer/trait_api/mod.rs | 12 +- 4 files changed, 357 insertions(+), 66 deletions(-) diff --git a/libcnb/src/build.rs b/libcnb/src/build.rs index aade6553..e220405e 100644 --- a/libcnb/src/build.rs +++ b/libcnb/src/build.rs @@ -104,7 +104,7 @@ impl BuildContext { /// } /// } /// ``` - #[deprecated = "The Layer trait API was replaced by LayerDefinitions. Use `cached_layer` and `uncached_layer`."] + #[deprecated = "The Layer trait API was replaced by a struct based API. Use `cached_layer` and `uncached_layer`."] #[allow(deprecated)] pub fn handle_layer>( &self, @@ -119,46 +119,278 @@ impl BuildContext { }) } - pub fn uncached_layer( + /// Creates a cached layer, potentially re-using a previously cached version. + /// + /// Buildpack code uses this function to create a cached layer and will get back a reference to + /// the layer directory on disk. Intricacies of the CNB spec are automatically handled such as + /// the maintenance of TOML files. Buildpack code can also specify a callback for cached layer + /// invalidation. + /// + /// Users of this function pass in a [`CachedLayerDefinition`] that describes the desired layer + /// and the returned `LayerRef` can then be used to modify the layer like any other path. This + /// allows users to be flexible when and how the layer is modified and to abstract layer + /// creation away if necessary. + /// + /// # Basic Example + /// ```rust + /// # use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; + /// # use libcnb::detect::{DetectContext, DetectResult}; + /// # use libcnb::generic::GenericPlatform; + /// # use libcnb::layer::{ + /// # CachedLayerDefinition, InspectExistingAction, InvalidMetadataAction, LayerContents, + /// # }; + /// # use libcnb::layer_env::{LayerEnv, ModificationBehavior, Scope}; + /// # use libcnb::Buildpack; + /// # use libcnb_data::generic::GenericMetadata; + /// # use libcnb_data::layer_name; + /// # use std::fs; + /// # + /// # struct ExampleBuildpack; + /// # + /// # #[derive(Debug)] + /// # enum ExampleBuildpackError { + /// # WriteDataError(std::io::Error), + /// # } + /// # + /// # impl Buildpack for ExampleBuildpack { + /// # type Platform = GenericPlatform; + /// # type Metadata = GenericMetadata; + /// # type Error = ExampleBuildpackError; + /// # + /// # fn detect(&self, context: DetectContext) -> libcnb::Result { + /// # unimplemented!() + /// # } + /// # + /// # fn build(&self, context: BuildContext) -> libcnb::Result { + /// let layer_ref = context.cached_layer( + /// layer_name!("example_layer"), + /// CachedLayerDefinition { + /// build: false, + /// launch: false, + /// // Will be called if a cached version of the layer was found, but the metadata + /// // could not be parsed. In this example, we instruct libcnb to always delete the + /// // existing layer in such a case. But we can implement any logic here if we want. + /// invalid_metadata: &|_| InvalidMetadataAction::DeleteLayer, + /// // Will be called if a cached version of the layer was found. This allows us to + /// // inspect the contents and metadata to decide if we want to keep the existing + /// // layer or let libcnb delete the existing layer and create a new one for us. + /// // This is libcnb's method to implement cache invalidations for layers. + /// inspect_existing: &|_: &GenericMetadata, _| InspectExistingAction::KeepLayer, + /// }, + /// )?; + /// + /// // At this point, a layer exists on disk. It might contain cached data or might be empty. + /// // Since we need to conditionally work with the layer contents based on its state, we can + /// // inspect the `contents` field of the layer reference to get detailed information about + /// // the current layer contents and the cause(s) for the state. + /// // + /// // In the majority of cases, we don't need more details beyond if it's empty or not and can + /// // ignore the details. This is what we do in this example. See the later example for a more + /// // complex situation. + /// match layer_ref.contents { + /// LayerContents::Empty { .. } => { + /// println!("Creating new example layer!"); + /// + /// // Modify the layer contents with regular Rust functions: + /// fs::write( + /// layer_ref.path().join("data.txt"), + /// "Here is some example data", + /// ) + /// .map_err(ExampleBuildpackError::WriteDataError)?; + /// + /// // Use functions on LayerRef for common CNB specific layer modifications: + /// layer_ref.replace_env(LayerEnv::new().chainable_insert( + /// Scope::All, + /// ModificationBehavior::Append, + /// "PLANET", + /// "LV-246", + /// ))?; + /// } + /// LayerContents::Cached { .. } => { + /// println!("Reusing example layer from previous run!"); + /// } + /// } + /// # + /// # BuildResultBuilder::new().build() + /// # } + /// # } + /// # + /// # impl From for libcnb::Error { + /// # fn from(value: ExampleBuildpackError) -> Self { + /// # Self::BuildpackError(value) + /// # } + /// # } + /// ``` + /// + /// # More complex example + /// ```rust + /// # use libcnb::build::{BuildContext, BuildResult, BuildResultBuilder}; + /// # use libcnb::detect::{DetectContext, DetectResult}; + /// # use libcnb::generic::GenericPlatform; + /// # use libcnb::layer::{ + /// # CachedLayerDefinition, EmptyLayerCause, InspectExistingAction, InvalidMetadataAction, + /// # LayerContents, + /// # }; + /// # use libcnb::Buildpack; + /// # use libcnb_data::generic::GenericMetadata; + /// # use libcnb_data::layer_name; + /// # use serde::{Deserialize, Serialize}; + /// # use std::fs; + /// # + /// # struct ExampleBuildpack; + /// # + /// # #[derive(Debug)] + /// # enum ExampleBuildpackError { + /// # UnexpectedIoError(std::io::Error), + /// # } + /// # + /// #[derive(Deserialize, Serialize)] + /// struct ExampleLayerMetadata { + /// lang_runtime_version: String, + /// } + /// + /// enum CustomCause { + /// Ok, + /// LegacyVersion, + /// HasBrokenModule, + /// MissingModulesFile, + /// } + /// + /// # impl Buildpack for ExampleBuildpack { + /// # type Platform = GenericPlatform; + /// # type Metadata = GenericMetadata; + /// # type Error = ExampleBuildpackError; + /// # + /// # fn detect(&self, _: DetectContext) -> libcnb::Result { + /// # unimplemented!() + /// # } + /// # + /// fn build(&self, context: BuildContext) -> libcnb::Result { + /// let layer_ref = context.cached_layer( + /// layer_name!("example_layer"), + /// CachedLayerDefinition { + /// build: false, + /// launch: false, + /// invalid_metadata: &|_| InvalidMetadataAction::DeleteLayer, + /// inspect_existing: &|metadata: &ExampleLayerMetadata, layer_dir| { + /// if metadata.lang_runtime_version.starts_with("0.") { + /// Ok(( + /// InspectExistingAction::DeleteLayer, + /// CustomCause::LegacyVersion, + /// )) + /// } else { + /// let file_path = layer_dir.join("modules.txt"); + /// + /// if file_path.is_file() { + /// // This is a fallible operation where an unexpected IO error occurs + /// // during operation. In this example, we chose not to map it to + /// // a layer action but let it automatically "bubble up". This error will + /// // end up in the regular libcnb buildpack on_error. + /// let file_contents = fs::read_to_string(&file_path) + /// .map_err(ExampleBuildpackError::UnexpectedIoError)?; + /// + /// if file_contents == "known-broken-0.1c" { + /// Ok(( + /// InspectExistingAction::DeleteLayer, + /// CustomCause::HasBrokenModule, + /// )) + /// } else { + /// Ok((InspectExistingAction::KeepLayer, CustomCause::Ok)) + /// } + /// } else { + /// Ok(( + /// InspectExistingAction::DeleteLayer, + /// CustomCause::MissingModulesFile, + /// )) + /// } + /// } + /// }, + /// }, + /// )?; + /// + /// match layer_ref.contents { + /// LayerContents::Empty { ref cause } => { + /// // Since the cause is just a regular Rust value, we can match it with regular + /// // Rust syntax and be as complex or simple as we need. + /// let message = match cause { + /// EmptyLayerCause::Inspect { + /// cause: CustomCause::LegacyVersion, + /// } => "Re-installing language runtime (legacy cached version)", + /// EmptyLayerCause::Inspect { + /// cause: CustomCause::HasBrokenModule | CustomCause::MissingModulesFile, + /// } => "Re-installing language runtime (broken modules detected)", + /// _ => "Installing language runtime", + /// }; + /// + /// println!("{message}"); + /// + /// // Code to install the language runtime would go here + /// + /// layer_ref.replace_metadata(ExampleLayerMetadata { + /// lang_runtime_version: String::from("1.0.0"), + /// })?; + /// } + /// LayerContents::Cached { .. } => { + /// println!("Re-using cached language runtime"); + /// } + /// } + /// + /// BuildResultBuilder::new().build() + /// } + /// # } + /// # + /// # impl From for libcnb::Error { + /// # fn from(value: ExampleBuildpackError) -> Self { + /// # Self::BuildpackError(value) + /// # } + /// # } + /// ``` + pub fn cached_layer<'a, M, MA, EA, MAC, EAC>( &self, layer_name: LayerName, - layer_definition: impl Borrow, - ) -> crate::Result, B::Error> { + layer_definition: impl Borrow>, + ) -> crate::Result, B::Error> + where + M: 'a + Serialize + DeserializeOwned, + MA: 'a + IntoAction, MAC, B::Error>, + EA: 'a + IntoAction, + { let layer_definition = layer_definition.borrow(); crate::layer::struct_api::handling::handle_layer( LayerTypes { launch: layer_definition.launch, build: layer_definition.build, - cache: false, + cache: true, }, - &|_| InvalidMetadataAction::DeleteLayer, - &|_: &GenericMetadata, _| InspectExistingAction::Delete, + layer_definition.invalid_metadata, + layer_definition.inspect_existing, layer_name, &self.layers_dir, ) } - pub fn cached_layer<'a, M, X, Y, O, I>( + /// Creates an uncached layer. + /// + /// If the layer already exists because it was cached in a previous buildpack run, the existing + /// data will be deleted. + /// + /// This function is essentially the same as [`BuildContext::uncached_layer`] but simpler. + pub fn uncached_layer( &self, layer_name: LayerName, - layer_definition: impl Borrow>, - ) -> crate::Result, B::Error> - where - M: 'a + Serialize + DeserializeOwned, - O: 'a + IntoAction, X, B::Error>, - I: 'a + IntoAction, - { + layer_definition: impl Borrow, + ) -> crate::Result, B::Error> { let layer_definition = layer_definition.borrow(); crate::layer::struct_api::handling::handle_layer( LayerTypes { launch: layer_definition.launch, build: layer_definition.build, - cache: true, + cache: false, }, - layer_definition.invalid_metadata, - layer_definition.inspect_existing, + &|_| InvalidMetadataAction::DeleteLayer, + &|_: &GenericMetadata, _| InspectExistingAction::DeleteLayer, layer_name, &self.layers_dir, ) diff --git a/libcnb/src/layer/struct_api/handling.rs b/libcnb/src/layer/struct_api/handling.rs index f8e00c53..0f037e15 100644 --- a/libcnb/src/layer/struct_api/handling.rs +++ b/libcnb/src/layer/struct_api/handling.rs @@ -3,7 +3,7 @@ use crate::layer::shared::{ WriteLayerError, }; use crate::layer::{ - EmptyReason, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerContents, + EmptyLayerCause, InspectExistingAction, IntoAction, InvalidMetadataAction, LayerContents, LayerError, LayerRef, }; use crate::Buildpack; @@ -16,38 +16,43 @@ use serde::Serialize; use std::marker::PhantomData; use std::path::{Path, PathBuf}; -pub(crate) fn handle_layer( +pub(crate) fn handle_layer( layer_types: LayerTypes, invalid_metadata: &dyn Fn(&GenericMetadata) -> MA, - inspect_existing: &dyn Fn(&M, &Path) -> IA, + inspect_existing: &dyn Fn(&M, &Path) -> EA, layer_name: LayerName, layers_dir: &Path, -) -> crate::Result, B::Error> +) -> crate::Result, B::Error> where B: Buildpack + ?Sized, M: Serialize + DeserializeOwned, - MA: IntoAction, MC, B::Error>, - IA: IntoAction, + MA: IntoAction, MAC, B::Error>, + EA: IntoAction, { match read_layer::(layers_dir, &layer_name) { - Ok(None) => create_layer(layer_types, &layer_name, layers_dir, EmptyReason::Uncached), + Ok(None) => create_layer( + layer_types, + &layer_name, + layers_dir, + EmptyLayerCause::Uncached, + ), Ok(Some(layer_data)) => { let inspect_action = inspect_existing(&layer_data.metadata.metadata, &layer_data.path) .into_action() .map_err(crate::Error::BuildpackError)?; match inspect_action { - (InspectExistingAction::Delete, cause) => { + (InspectExistingAction::DeleteLayer, cause) => { delete_layer(layers_dir, &layer_name).map_err(LayerError::DeleteLayerError)?; create_layer( layer_types, &layer_name, layers_dir, - EmptyReason::Inspect(cause), + EmptyLayerCause::Inspect { cause }, ) } - (InspectExistingAction::Keep, cause) => { + (InspectExistingAction::KeepLayer, cause) => { // Always write the layer types as: // a) they might be different from what is currently on disk // b) the cache field will be removed by CNB lifecycle on cache restore @@ -59,7 +64,7 @@ where name: layer_data.name, layers_dir: PathBuf::from(layers_dir), buildpack: PhantomData, - contents: LayerContents::Cached(cause), + contents: LayerContents::Cached { cause }, }) } } @@ -82,7 +87,7 @@ where layer_types, &layer_name, layers_dir, - EmptyReason::MetadataInvalid(cause), + EmptyLayerCause::MetadataInvalid { cause }, ) } (InvalidMetadataAction::ReplaceMetadata(metadata), _) => { @@ -104,12 +109,12 @@ where } } -fn create_layer( +fn create_layer( layer_types: LayerTypes, layer_name: &LayerName, layers_dir: &Path, - empty_reason: EmptyReason, -) -> Result, crate::Error> + empty_layer_cause: EmptyLayerCause, +) -> Result, crate::Error> where B: Buildpack + ?Sized, { @@ -131,6 +136,8 @@ where name: layer_data.name, layers_dir: PathBuf::from(layers_dir), buildpack: PhantomData, - contents: LayerContents::Empty(empty_reason), + contents: LayerContents::Empty { + cause: empty_layer_cause, + }, }) } diff --git a/libcnb/src/layer/struct_api/mod.rs b/libcnb/src/layer/struct_api/mod.rs index faf42354..27009abe 100644 --- a/libcnb/src/layer/struct_api/mod.rs +++ b/libcnb/src/layer/struct_api/mod.rs @@ -1,5 +1,8 @@ pub(crate) mod handling; +// BuildContext is only used in RustDoc (https://github.com/rust-lang/rust/issues/79542) +#[allow(unused)] +use crate::build::BuildContext; use crate::layer::shared::{replace_layer_exec_d_programs, replace_layer_sboms, WriteLayerError}; use crate::layer::LayerError; use crate::layer_env::LayerEnv; @@ -8,11 +11,14 @@ use crate::Buildpack; use libcnb_data::generic::GenericMetadata; use libcnb_data::layer::LayerName; use serde::Serialize; +use std::borrow::Borrow; use std::collections::HashMap; use std::marker::PhantomData; use std::path::{Path, PathBuf}; /// A definition for a cached layer. +/// +/// Refer to the docs of [`BuildContext::cached_layer`] for usage examples. pub struct CachedLayerDefinition<'a, M, MA, EA> { /// Whether the layer is intended for build. pub build: bool, @@ -29,6 +35,8 @@ pub struct CachedLayerDefinition<'a, M, MA, EA> { } /// A definition for an uncached layer. +/// +/// Refer to the docs of [`BuildContext::uncached_layer`] for usage examples. pub struct UncachedLayerDefinition { /// Whether the layer is intended for build. pub build: bool, @@ -37,93 +45,130 @@ pub struct UncachedLayerDefinition { } /// The action to take when the layer metadata is invalid. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum InvalidMetadataAction { + /// Delete the existing layer. DeleteLayer, + /// Keep the layer, but replace the metadata. Commonly used to migrate to a newer + /// metadata format. ReplaceMetadata(M), } /// The action to take after inspecting existing layer data. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum InspectExistingAction { - Delete, - Keep, + /// Delete the existing layer. + DeleteLayer, + /// Keep the layer as-is. + KeepLayer, } -pub enum LayerContents { +/// Framework metadata about the layer contents. +/// +/// See: [`BuildContext::cached_layer`] and [`BuildContext::uncached_layer`] +#[derive(Copy, Clone, Debug)] +pub enum LayerContents { /// The layer contains validated cached contents from a previous buildpack run. /// /// See: `inspect_existing` in [`CachedLayerDefinition`]. - Cached(Y), - /// The layer is empty. Inspect the contained [`EmptyReason`] for details why. - Empty(EmptyReason), + Cached { cause: EAC }, + /// The layer is empty. Inspect the contained [`EmptyLayerCause`] for the cause. + Empty { cause: EmptyLayerCause }, } -pub enum EmptyReason { - /// The layer wasn't cached in a previous buildpack run. +/// The cause of a layer being empty. +#[derive(Copy, Clone, Debug)] +pub enum EmptyLayerCause { + /// The layer wasn't cached in a previous buildpack run and was freshly created. Uncached, /// The layer was cached in a previous buildpack run, but the metadata was invalid and couldn't /// be converted into a valid form. Subsequently, the layer was deleted entirely. /// /// See: `invalid_metadata` in [`CachedLayerDefinition`]. - MetadataInvalid(X), + MetadataInvalid { cause: MAC }, /// The layer was cached in a previous buildpack run, but the `inspect_existing` function - /// rejected the contents. + /// rejected the contents and/or metadata. /// /// See: `inspect_existing` in [`CachedLayerDefinition`]. - Inspect(Y), + Inspect { cause: EAC }, } /// A value-to-value conversion for layer actions. /// /// Similar to [`Into`], but specialized. Allowing it to also be implemented for /// values in the standard library such as [`Result`]. +/// +/// Implement this trait if you want to use your own types as actions. +/// +/// libcnb ships with generic implementations for the majority of the use-cases: +/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] directly. +/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] directly, wrapped in a Result. +/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] with a cause value in a tuple. +/// - Using [`InspectExistingAction`] or [`InvalidMetadataAction`] with a cause value in a tuple, wrapped in a Result. pub trait IntoAction { fn into_action(self) -> Result<(T, C), E>; } +// Allows to use the layer actions directly. impl IntoAction for T { fn into_action(self) -> Result<(T, ()), E> { Ok((self, ())) } } +// Allows to use the layer actions directly wrapped in a Result. +impl IntoAction for Result { + fn into_action(self) -> Result<(T, ()), E> { + self.map(|value| (value, ())) + } +} + +// Allows to use the layer actions directly with a cause as a tuple. impl IntoAction for (T, C) { fn into_action(self) -> Result<(T, C), E> { Ok(self) } } +// Allows to use the layer actions directly with a cause as a tuple wrapped in a Result. impl IntoAction for Result<(T, C), E> { fn into_action(self) -> Result<(T, C), E> { self } } -impl IntoAction for Result { - fn into_action(self) -> Result<(T, ()), E> { - self.map(|value| (value, ())) - } -} - -pub struct LayerRef +/// A reference to an existing layer on disk. +/// +/// Provides functions to modify the layer such as replacing its metadata, environment, SBOMs or +/// exec.d programs. +/// +/// To obtain a such a reference, use [`BuildContext::cached_layer`] or [`BuildContext::uncached_layer`]. +pub struct LayerRef where B: Buildpack + ?Sized, { name: LayerName, + // Technically not part of the layer itself. However, the functions that modify the layer + // will need a reference to the layers directory as they will also modify files outside the + // actual layer directory. To make LayerRef nice to use, we bite the bullet and include + // the layers_dir here. layers_dir: PathBuf, buildpack: PhantomData, - pub contents: LayerContents, + pub contents: LayerContents, } -impl LayerRef +impl LayerRef where B: Buildpack, { + /// Returns the path to the layer on disk. pub fn path(&self) -> PathBuf { self.layers_dir.join(self.name.as_str()) } + /// Replaces the existing layer metadata with a new value. + /// + /// The new value does not have to be of the same type as the existing metadata. pub fn replace_metadata(&self, metadata: M) -> crate::Result<(), B::Error> where M: Serialize, @@ -136,12 +181,18 @@ where }) } - pub fn replace_env(&self, env: &LayerEnv) -> crate::Result<(), B::Error> { - env.write_to_layer_dir(self.path()).map_err(|error| { - crate::Error::LayerError(LayerError::WriteLayerError(WriteLayerError::IoError(error))) - }) + /// Replaces the existing layer environment with a new one. + pub fn replace_env(&self, env: impl Borrow) -> crate::Result<(), B::Error> { + env.borrow() + .write_to_layer_dir(self.path()) + .map_err(|error| { + crate::Error::LayerError(LayerError::WriteLayerError(WriteLayerError::IoError( + error, + ))) + }) } + /// Replace all existing layer SBOMs with new ones. pub fn replace_sboms(&self, sboms: &[Sbom]) -> crate::Result<(), B::Error> { replace_layer_sboms(&self.layers_dir, &self.name, sboms).map_err(|error| { crate::Error::LayerError(LayerError::WriteLayerError( @@ -150,6 +201,7 @@ where }) } + /// Replace all existing layer exec.d programs with new ones. pub fn replace_exec_d_programs(&self, programs: P) -> crate::Result<(), B::Error> where S: Into, diff --git a/libcnb/src/layer/trait_api/mod.rs b/libcnb/src/layer/trait_api/mod.rs index dca2c777..e6c305c2 100644 --- a/libcnb/src/layer/trait_api/mod.rs +++ b/libcnb/src/layer/trait_api/mod.rs @@ -24,7 +24,7 @@ mod tests; /// depending on its state. To use a `Layer` implementation during build, use /// [`BuildContext::handle_layer`](crate::build::BuildContext::handle_layer). #[allow(unused_variables)] -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "The Layer trait API was replaced by a struct based API. Use CachedLayerDefinition or UncachedLayerDefinition."] pub trait Layer { /// The buildpack this layer is used with. type Buildpack: Buildpack; @@ -144,7 +144,7 @@ pub trait Layer { /// The result of a [`Layer::existing_layer_strategy`] call. #[derive(Eq, PartialEq, Clone, Copy, Debug)] -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub enum ExistingLayerStrategy { /// The existing layer should not be modified. Keep, @@ -155,7 +155,7 @@ pub enum ExistingLayerStrategy { } /// The result of a [`Layer::migrate_incompatible_metadata`] call. -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub enum MetadataMigration { /// The layer should be recreated entirely. RecreateLayer, @@ -164,7 +164,7 @@ pub enum MetadataMigration { } /// Information about an existing CNB layer. -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub struct LayerData { pub name: LayerName, /// The layer's path, should not be modified outside of a [`Layer`] implementation. @@ -177,7 +177,7 @@ pub struct LayerData { /// /// Essentially, this carries additional metadata about a layer this later persisted according /// to the CNB spec by libcnb. -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub struct LayerResult { pub metadata: M, pub env: Option, @@ -186,7 +186,7 @@ pub struct LayerResult { } /// A builder that simplifies the creation of [`LayerResult`] values. -#[deprecated = "The Layer trait API was replaced by LayerDefinitions and will be removed soon."] +#[deprecated = "Part of the Layer trait API that was replaced by a struct based API."] pub struct LayerResultBuilder { metadata: M, env: Option,