#![doc = include_str!("./lib.md")]
#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
use std::{borrow::Cow, error::Error as StdError, ops::Not};
#[doc(hidden)]
pub use confik_macros::*;
use serde::de::DeserializeOwned;
#[doc(hidden)]
pub mod __exports {
pub use serde as __serde;
}
#[allow(unused_extern_crates)] extern crate self as confik;
mod builder;
#[cfg(feature = "common")]
pub mod common;
mod errors;
mod path;
mod secrets;
mod sources;
mod std_impls;
mod third_party;
#[cfg(feature = "env")]
pub use self::sources::env_source::EnvSource;
#[cfg(feature = "json")]
pub use self::sources::json_source::JsonSource;
#[cfg(feature = "toml")]
pub use self::sources::toml_source::TomlSource;
pub use self::{
builder::ConfigBuilder,
errors::Error,
secrets::{SecretBuilder, SecretOption, UnexpectedSecret},
sources::{file_source::FileSource, Source},
};
use self::{path::Path, sources::DynSource};
#[derive(Debug, Default, thiserror::Error)]
#[error("Missing value for path `{0}`")]
pub struct MissingValue(Path);
impl MissingValue {
#[must_use]
pub fn prepend(mut self, path_segment: impl Into<Cow<'static, str>>) -> Self {
self.0 .0.push(path_segment.into());
self
}
}
#[derive(Debug, thiserror::Error)]
#[error("Failed try_into for path `{0}`")]
pub struct FailedTryInto(Path, #[source] Box<dyn StdError + Send + Sync>);
impl FailedTryInto {
pub fn new(err: impl StdError + Send + Sync + 'static) -> Self {
Self(Path::new(), Box::new(err))
}
#[must_use]
pub fn prepend(mut self, path_segment: impl Into<Cow<'static, str>>) -> Self {
self.0 .0.push(path_segment.into());
self
}
}
fn build_from_sources<'a, Target, Iter>(sources: Iter) -> Result<Target, Error>
where
Target: Configuration,
Iter: IntoIterator<Item = Box<dyn DynSource<Target::Builder> + 'a>>,
{
sources
.into_iter()
.map::<Result<Target::Builder, Error>, _>(
|source: Box<dyn DynSource<Target::Builder> + 'a>| {
let debug = || format!("{source:?}");
let res = source.provide().map_err(|e| Error::Source(e, debug()))?;
if source.allows_secrets().not() {
res.contains_non_secret_data()
.map_err(|e| Error::UnexpectedSecret(e, debug()))?;
}
Ok(res)
},
)
.reduce(|first, second| Ok(Target::Builder::merge(first?, second?)))
.ok_or_else(|| Error::MissingValue(MissingValue::default()))??
.try_build()
.map_err(Into::into)
}
pub trait Configuration: Sized {
type Builder: ConfigurationBuilder<Target = Self>;
#[must_use]
fn builder<'a>() -> ConfigBuilder<'a, Self> {
ConfigBuilder::<Self>::default()
}
}
pub trait ConfigurationBuilder: Default + DeserializeOwned {
type Target;
#[must_use]
fn merge(self, other: Self) -> Self;
fn try_build(self) -> Result<Self::Target, Error>;
fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret>;
}
impl<T> ConfigurationBuilder for Option<T>
where
T: DeserializeOwned + Configuration,
{
type Target = T;
fn merge(self, other: Self) -> Self {
self.or(other)
}
fn try_build(self) -> Result<Self::Target, Error> {
self.ok_or_else(|| Error::MissingValue(MissingValue::default()))
}
fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret> {
Ok(self.is_some())
}
}