Crate confik

source ·
Expand description

§confik

confik is a library for reading application configuration split across multiple sources.

§Example

Assume that config.toml contains:

host=google.com
username=root

and the environment contains:

PASSWORD=hunter2

then:

use confik::{Configuration, EnvSource, FileSource, TomlSource};

#[derive(Debug, PartialEq, Configuration)]
struct Config {
    host: String,
    username: String,

    #[confik(secret)]
    password: String,
}

let config = Config::builder()
    .override_with(FileSource::new("config.toml"))
    .override_with(EnvSource::new().allow_secrets())
    .try_build()
    .unwrap();

assert_eq!(
    config,
    Config {
        host: "google.com".to_string(),
        username: "root".to_string(),
        password: "hunter2".to_string(),
    }
);

§Sources

A Source is any type that can create ConfigurationBuilders. This crate implements the following sources:

  • EnvSource: Loads configuration from environment variables using the envious crate. Requires the env feature. (Enabled by default.)
  • FileSource: Loads configuration from a file, detecting json or toml files based on the file extension. Requires the json and toml feature respectively. (toml is enabled by default.)
  • TomlSource: Loads configuration from a TOML string literal. Requires the toml feature. (Enabled by default.)
  • JsonSource: Loads configuration from a JSON string literal. Requires the json feature.

§Secrets

Fields annotated with #[confik(secret)] will only be read from secure sources. This serves as a runtime check that no secrets have been stored in insecure places such as world-readable files.

If a secret is found in an insecure source, an error will be returned. You can opt into loading secrets on a source-by-source basis.

§Macro usage

The derive macro is called Configuration and is used as normal:

#[derive(confik::Configuration)]
struct Config {
    data: usize,
}

§Forwarding Attributes To Deserialize

The serde attributes used for customizing a Deserialize derive typically are achieved by adding #[confik(forward_serde(...)) attributes.

For example:

#[derive(confik::Configuration)]
struct Config {
    #[confik(forward_serde(rename = "other_data"))]
    data: usize,
}

§Defaults

Defaults are specified on a per-field basis.

  • Defaults only apply if no data has been read for that field. E.g., if data in the below example has one value read in, it will return an error.

    use confik::{Configuration, TomlSource};
    
    #[derive(Debug, Configuration)]
    struct Data {
        a: usize,
        b: usize,
    }
    
    #[derive(Debug, Configuration)]
    struct Config {
        #[confik(default = Data  { a: 1, b: 2 })]
        data: Data
    }
    
    // Data is not specified, the default is used.
    let config = Config::builder()
        .try_build()
        .unwrap();
    assert_eq!(config.data.a, 1);
    
    let toml = r#"
        [data]
        a = 1234
    "#;
    
    // Data is partially specified, but is insufficient to create it. The default is not used
    // and an error is returned.
    let config = Config::builder()
        .override_with(TomlSource::new(toml))
        .try_build()
        .unwrap_err();
    
    let toml = r#"
        [data]
        a = 1234
        b = 4321
    "#;
    
    // Data is fully specified and the default is not used.
    let config = Config::builder()
        .override_with(TomlSource::new(toml))
        .try_build()
        .unwrap();
    assert_eq!(config.data.a, 1234);
  • Defaults can be given by any rust expression, and have Into::into run over them. E.g.,

    const DEFAULT_VALUE: u8 = 4;
    
    #[derive(confik::Configuration)]
    struct Config {
        #[confik(default = DEFAULT_VALUE)]
        a: u32,
        #[confik(default = "hello world")]
        b: String,
        #[confik(default = 5f32)]
        c: f32,
    }
  • Alternatively, a default without a given value called Default::default. E.g.,

    use confik::{Configuration};
    
    #[derive(Configuration)]
    struct Config {
        #[confik(default)]
        a: usize
    }
    
    let config = Config::builder().try_build().unwrap();
    assert_eq!(config.a, 0);

§Handling Foreign Types

This crate provides implementations of Configuration for a number of std types and the following third-party crates. Implementations for third-party crates are feature gated.

  • bigdecimal: v0.4
  • bytesize: v1
  • camino: v1
  • chrono: v0.4
  • ipnetwork: v0.20
  • rust_decimal: v1
  • secrecy: v0.8 (Note that #[config(secret)] is not needed, although it is harmless, for these types as they are always treated as secrets.)
  • url: v1
  • uuid: v1

If there’s another foreign type used in your config, then you will not be able to implement Configuration for it. Instead any type that implements Into or TryInto can be used.

struct ForeignType {
    data: usize,
}

#[derive(confik::Configuration)]
struct MyForeignTypeCopy {
    data: usize
}

impl From<MyForeignTypeCopy> for ForeignType {
    fn from(copy: MyForeignTypeCopy) -> Self {
        Self {
            data: copy.data,
        }
    }
}

#[derive(confik::Configuration)]
struct MyForeignTypeIsize {
    data: isize
}

impl TryFrom<MyForeignTypeIsize> for ForeignType {
    type Error = <usize as TryFrom<isize>>::Error;

    fn try_from(copy: MyForeignTypeIsize) -> Result<Self, Self::Error> {
        Ok(Self {
            data: copy.data.try_into()?,
        })
    }
}

#[derive(confik::Configuration)]
struct Config {
    #[confik(from = MyForeignTypeCopy)]
    foreign_data: ForeignType,

    #[confik(try_from = MyForeignTypeIsize)]
    foreign_data_isized: ForeignType,
}

§Macro Limitations

§Custom Deserialize Implementations

If you’re using a custom Deserialize implementation, then you cannot use the Configuration derive macro. Instead, define the necessary config implementation manually like so:

#[derive(Debug, serde_with::DeserializeFromStr)]
enum MyEnum {
    Foo,
    Bar,
};

impl std::str::FromStr for MyEnum {
    // ...
}

impl confik::Configuration for MyEnum {
    type Builder = Option<Self>;
}

Note that the Option<Self> builder type only works for simple types. For more info, see the docs on Configuration and ConfigurationBuilder.

Modules§

  • commoncommon
    Useful configuration types that services will likely otherwise re-implement.

Structs§

  • Used to accumulate ordered sources from which its Target is to be built.
  • A Source referring to environment variables.
  • Captures the path and error of a failed conversion.
  • A Source referring to a file path.
  • A Source containing raw JSON data.
  • Captures the path of a missing value.
  • Wrapper type for carrying secrets, auto-applied to builders when using the #[config(secret)] attribute.
  • Builder for trivial types that always contain secrets, regardless of the presence of #[confik(secret)] annotations.
  • A Source containing raw TOML data.
  • Captures the path of a secret found in a non-secret source.

Enums§

  • Possible error values.

Traits§