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 ConfigurationBuilder
s. This crate implements the following sources:
EnvSource
: Loads configuration from environment variables using theenvious
crate. Requires theenv
feature. (Enabled by default.)FileSource
: Loads configuration from a file, detectingjson
ortoml
files based on the file extension. Requires thejson
andtoml
feature respectively. (toml
is enabled by default.)TomlSource
: Loads configuration from a TOML string literal. Requires thetoml
feature. (Enabled by default.)JsonSource
: Loads configuration from a JSON string literal. Requires thejson
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
This allows forwarding any kind of attribute on to the builder.
§Serde
The serde attributes used for customizing a Deserialize
derive are achieved by adding #[confik(forward(serde(...)))]
attributes.
For example:
#[derive(Configuration, Debug, PartialEq, Eq)]
struct Field {
#[confik(forward(serde(rename = "other_name")))]
field1: usize,
}
§Derives
If you need additional derives for your type, these can be added via #[confik(forward(derive...))]
attributes.
For example:
#[derive(Debug, Configuration, Hash, Eq, PartialEq)]
#[confik(forward(derive(Hash, Eq, PartialEq)))]
struct Value {
inner: String,
}
§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.4bytesize
: v2camino
: v1chrono
: v0.4ipnetwork
: v0.21rust_decimal
: v1secrecy
: v0.10 (Note that#[config(secret)]
is not needed, although it is harmless, for these types as they are always treated as secrets.)url
: v1uuid
: 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,
}
§Named builders
If you want to directly access the builders, you can provide them with a name. This will also place the builder in the local module, to ensure there’s a known path with which to reference them.
#[derive(confik::Configuration)]
#[confik(name = Builder)]
struct Config {
data: usize,
}
let _ = Builder { data: Default::default() };
§Field and Builder visibility
Field and builder visibility are directly inherited from the underlying type. E.g.
mod config {
#[derive(confik::Configuration)]
pub struct Config {
pub data: usize,
}
}
// Required as you can't use this syntax for struct initialisation.
type Builder = <config::Config as confik::Configuration>::Builder;
let _ = Builder { data: Default::default() };
§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§
- common
common
- Useful configuration types that services will likely otherwise re-implement.
Structs§
- Config
Builder - Used to accumulate ordered sources from which its
Target
is to be built. - EnvSource
env
- A
Source
referring to environment variables. - Failed
TryInto - Captures the path and error of a failed conversion.
- File
Source - A
Source
referring to a file path. - Json
Source json
- A
Source
containing raw JSON data. - Missing
Value - Captures the path of a missing value.
- Secret
Builder - Wrapper type for carrying secrets, auto-applied to builders when using the
#[config(secret)]
attribute. - Secret
Option - Builder for trivial types that always contain secrets, regardless of the presence of
#[confik(secret)]
annotations. - Toml
Source toml
- A
Source
containing raw TOML data. - Unexpected
Secret - Captures the path of a secret found in a non-secret source.
Enums§
- Error
- Possible error values.
Traits§
- Configuration
- The target to be deserialized from multiple sources.
- Configuration
Builder - A builder for a multi-source config deserialization.
- Source
- A source of configuration data.