Transforming values
Sometimes a value is configured by a user, but it needs to be transformed in some way to be usable, for example, expanding file system paths to absolute from relative.
This can be achieved with the #[setting(transform)]
attribute field, which requires a reference to
a function to call. Only values with a defined value are transformed, while optional values remain
None
.
#![allow(unused)] fn main() { #[derive(Config)] struct AppConfig { #[setting(transform = make_absolute)] pub env_file: Option<PathBuf>, } }
Transformations happen during the finalize phase, after environment variables are inherited, and before it is validated.
Transform handler function
When defining a custom transform
function, the defined value and context are
passed as arguments, and the function must return the transformed result.
Here’s an example of the transform function above.
#![allow(unused)] fn main() { fn make_absolute(value: PathBuf, context: &Context) -> TransformResult<PathBuf> { Ok(if value.is_absolute() { value } else { context.root.join(value) }) } }
Nested values
Transformers can also be used on nested configs, but when defining the transformer function, the value being transformed is the partial nested config, not the final one. For example:
#![allow(unused)] fn main() { fn transform_nested(value: PartialChildConfig, context: &Context) -> TransformResult<PartialChildConfig> { Ok(value) } #[derive(Config)] struct ParentConfig { #[setting(nested, transform = transform_nested)] pub child: ChildConfig, } }
Context handling
If you’re not using context, you can use ()
as the context type, or rely on
generic inferrence.
#![allow(unused)] fn main() { fn using_unit_type<T>(value: T, _: &()) -> TransformResult<T> { // ... } fn using_generics<T, C>(value: T, _: &C) -> TransformResult<T> { // ... } }