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> {
	// ...
}
}