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=rootand the environment contains:
PASSWORD=hunter2then:
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(),
}
);§Hot Reloading
Configuration can be made hot-reloadable using the ReloadingConfig wrapper. This allows you to atomically swap configuration at runtime without restarting your application. Requires the reloading feature.
use confik::{Configuration, ReloadableConfig, FileSource};
#[derive(Debug, Configuration)]
struct AppConfig {
host: String,
port: u16,
}
impl ReloadableConfig for AppConfig {
type Error = confik::Error;
fn build() -> Result<Self, Self::Error> {
Self::builder()
.override_with(FileSource::new("config.toml"))
.try_build()
}
}
// Create a reloading config
let config = AppConfig::reloading().unwrap();
// Access the current config (cheap, non-blocking)
let current = config.load();
println!("Host: {}", current.host);
// Reload from sources
config.reload().unwrap();
// Add a callback for reload notifications
let config = config.with_on_update(|| {
println!("Config reloaded!");
});§Signal Handling
When the signal feature is enabled (requires reloading), you can also set up automatic reloading on SIGHUP:
let config = AppConfig::reloading().unwrap();
let handle = config.spawn_signal_handler().unwrap();
// Config will now reload when receiving SIGHUP
// Send SIGHUP: kill -HUP <pid>When the tracing feature is enabled, reload errors in the signal handler will be automatically logged with tracing::error!.
§Sources
A Source is any type that can create ConfigurationBuilders. This crate implements the following sources:
EnvSource: Loads configuration from environment variables using theenviouscrate. Requires theenvfeature. (Enabled by default.)FileSource: Loads configuration from a file, detectingjsonortomlfiles based on the file extension. Requires thejsonandtomlfeature respectively. (tomlis enabled by default.)TomlSource: Loads configuration from a TOML string literal. Requires thetomlfeature. (Enabled by default.)JsonSource: Loads configuration from a JSON string literal. Requires thejsonfeature.OffsetSource: Loads configuration from an inner source that is provided to it, but applied to a particular offset of the root configuration builder.
§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
datain 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::intorun 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.
ahash: v0.8bigdecimal: v0.4bytesize: v2camino: v1chrono: v0.4ipnetwork: v0.21js_option: v0.1rust_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.
use confik::helpers::BuilderOf;
mod config {
#[derive(confik::Configuration)]
pub struct Config {
pub data: usize,
}
}
let _ = BuilderOf::<config::Config> { data: Default::default() };§Skipping fields
Fields can be skipped if necessary. This allows having types that cannot implement Configuration or be deserializable. However the field must have a confik(default) or confik(default = ...) attribute, otherwise it can’t be built. E.g.
#[derive(confik::Configuration)]
struct Config {
#[confik(skip, default = Instant::now())]
loaded_at: Instant,
}§Specifying confik Base
Specify a path to the confik crate instance to use when referring to confik APIs from generated code. This is normally only applicable when invoking re-exported confik derives from a public macro in a different crate or when renaming confik in your Cargo manifest.
#[derive(confik::Configuration)]
#[confik(crate = reexported_confik)]
struct Config {
// ...
}§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.
§Manual implementations
It is strongly recommended to use the derive macro where possible. However, there may be cases where this is not possible. For some cases there are additional attributes available in the derive macro to tweak the behaviour, see the section on Handling Foreign Types.
If you would like to manually implement Configuration for a type anyway, then this can mostly be broken down to three cases.
§Simple cases
If your type cannot be partial specified (e.g. usize, String), then a simple Option<Self> builder can be used.
#[derive(Debug, serde_with::DeserializeFromStr)]
enum MyEnum {
Foo,
Bar,
};
impl std::str::FromStr for MyEnum {
// ...
}
impl confik::Configuration for MyEnum {
type Builder = Option<Self>;
}§Containers
Unless your container holds another container, which already implements Configuration, you’ll likely need to implement Configuration yourself, instead of with a derive. There are two type of containers that may need to be handled here.
§Keyed Containers
Keyed containers have their contents separate from their keys. Examples of these are HashMap and BTreeMap. Whilst the implementations can be provided fully, there are helpers available. These are the KeyedContainerBuilder type and the KeyedContainer trait.
A type which implements all of KeyedContainer, Deserialize, FromIterator, Default, and IntoIterator (for both the type and a reference to the type) can then use KeyedContainerBuilder as their builder. See KeyedContainerBuilder for an example.
Note that the key needs to implement Display so that an accurate error stack can be generated.
§Unkeyed Containers
Unkeyed containers are types without a separate key. This includes Vec, but also types like HashSet. Whilst the implementations can be provided fully, there is a helper available. This is the UnkeyedContainerBuilder.
A type which implements all of Deserialize, FromIterator, Default, and IntoIterator (for both the type and a reference to the type) can then use UnkeyedContainerBuilder as their builder. See UnkeyedContainerBuilder for an example.
§Other complex cases
For other complex cases, where derives cannot work, the type is not simple enough to use an Option<Self> builder, and is not a container, there is currently no additional support. Please read through the Configuration and ConfigurationBuilder traits and implement them as appropriate.
If you believe your type is following a common pattern where we could provide more support, please raise an issue (or even better an MR).
Modules§
- common
common - Useful configuration types that services will likely otherwise re-implement.
- helpers
- Utilities for manual implementations of
Configuration.
Structs§
- Config
Builder - Used to accumulate ordered sources from which its
Targetis to be built. - EnvSource
env - A
Sourcereferring to environment variables. - Failed
TryInto - Captures the path and error of a failed conversion.
- File
Source - A
Sourcereferring to a file path. - Json
Source json - A
Sourcecontaining raw JSON data. - Missing
Value - Captures the path of a missing value.
- Offset
Source - A
Sourcecontaining another source that can build the target at an offset determined by the provided path. - Reloading
Config reloading - An instance of config that may reload itself.
- 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
Sourcecontaining 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.
- Reload
Callback reloading - Trait for invoking reload callbacks.
- Reloadable
Config reloading - Defines how to create a new instance of
ReloadingConfig. - Source
- A source of configuration data.