#partial #configuration #config-file #layered #source #env-var #variables

bin+lib partial_config

A Rust crate to implement layered partial configuration

14 releases (6 breaking)

new 0.7.1 Nov 13, 2024
0.7.0-rc1 Sep 9, 2024
0.6.1 Apr 26, 2024
0.5.12 Mar 25, 2024

#19 in #layered

Download history 75/week @ 2024-07-28 46/week @ 2024-08-04 69/week @ 2024-08-11 87/week @ 2024-08-18 48/week @ 2024-08-25 75/week @ 2024-09-01 142/week @ 2024-09-08 69/week @ 2024-09-15 160/week @ 2024-09-22 77/week @ 2024-09-29 111/week @ 2024-10-06 368/week @ 2024-10-13 105/week @ 2024-10-20 188/week @ 2024-10-27 245/week @ 2024-11-03 329/week @ 2024-11-10

884 downloads per month

Apache-2.0

30KB
294 lines

Partial Config

This is a WIP crate for providing a generic interface to configure one's application.

It is typical to see a configuration be composed from multiple sources: the command line, the environment variables, a config file, sometimes even through a web server.

This crate provides a generic way to do so.

The Partial trait

Imagine that you have a web service, that also needs to be configurable at runtime. The solution is simple, create a layered structure with Options everywhere that can be built from a different sources, and collapse them into one. This trait and the corresponding derive macro HasPartial do that work for you.

The trait gives you three functions: build collapses a partial layer into a complete structure, the <Self as Partial>::Target, and reports errors such as conversion failures, required fields that are missing, and does so properly, for example, if you have multiple missing fields, they will all be reported at once as opposed to one-by-one.

Then you have override_with, used like you would expect:

bottom_layer.override_with(top_layer);

it takes two layers, and applies fields specified in the top_layer overriding the bottom_layer, if present. Fields absent in top_layer are inherited from the bottom_layer. You'd be surprised how many times have I had to fix this logic.

Finally you get source, which we shall talk about later.

The HasPartial derive macro

Say you have a configuration structure:

pub struct Configuration {
	file_name: String,
	port: u16,
	// Many fields...
	configuration_file: Option<String>,
}

You created a partial layer, that keeps track of which fields are required (e.g. file_name, port) and which fields are optional: configuration_file. If you add or rename a new field, you would need to track that change into the layered structure, and if obtaining the layer is done via some other mechanism, e.g. serde from a toml file, keeping the two in sync is a lot of work. Fortunately, partial_config::HasPartial can generate the layered structure for you.

You can forward derive annotations with #[partial_derives(serde::Deserialize)] in case your intermediate layers need to implement a trait, and doing so manually is too much work. By default the partial layer is called Partial<YourStructName>, but it can be changed with the #[partial_rename(CustomNameForYourIntermediateLayer)] annotation.

Source(s)

This is the main attraction of this package. If you implement Source<Configuration> you now have access to the wonderful source method in the structure's partial representation. This allows you to do what ought to be simple for a CLI application to be genuinely simple:

let configuration = PartialConfiguration::default()
	.source("default_config.toml")?
	.source(EnvVars::new())?
	.source(Args::parse())?
	.build()?;

This does what you think it does, and in the order that you think it does: the configuration file has lowest priority, environment variables override that, and CLI arguments override everything. Useful for when you're testing your program inside docker.

So how do you implement Source? That's the neat part!

serde

If you want a quick and dirty way to obtain fields from a configuration file, just derive(serde::Deserialize) on the Configuration and you get source("path_to.toml") for free.

Dependencies

~0.1–1MB
~20K SLoC