Merge strategies

A common requirement for configuration is to merge multiple sources/layers into a final result. By default Schematic will replace the previous setting value with the next value if the next value is Some, but sometimes you want far more control, like shallow or deep merging collections.

This can be achieved with the #[setting(merge)] attribute field, which requires a reference to a function to call.

#![allow(unused)]
fn main() {
#[derive(Config)]
struct AppConfig {
	#[setting(merge = schematic::merge::append_vec)]
	pub allowed_hosts: Vec<String>,
}

#[derive(Config)]
enum Projects {
	#[setting(merge = schematic::merge::append_vec)]
	List(Vec<String>),
	// ...
}
}

We provide a handful of built-in merge functions in the merge module.

Merge handler function

You can also define your own function for merging values.

When defining a custom merge function, the previous value, next value, and context are passed as arguments, and the function must return an optional merged result. If None is returned, neither value will be used.

Here’s an example of the merge function above.

#![allow(unused)]
fn main() {
fn append_vec<T>(mut prev: Vec<T>, next: Vec<T>, context: &Context) -> MergeResult<Vec<T>>> {
    prev.extend(next);

    Ok(Some(prev))
}

#[derive(Config)]
struct ExampleConfig {
	#[setting(merge = append_vec)]
	pub field: Vec<String>,
}
}

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>(prev: T, next: T, _: &()) -> MergeResult<T> {
	// ...
}

fn using_generics<T, C>(prev: T, next: T, _: &C) -> MergeResult<T> {
	// ...
}
}