1#![doc = include_str!("./lib.md")]
2#![deny(rust_2018_idioms, nonstandard_style, future_incompatible)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5use std::{borrow::Cow, error::Error as StdError, ops::Not};
6
7#[doc(hidden)]
8pub use confik_macros::*;
9use serde::de::DeserializeOwned;
10
11#[doc(hidden)]
12pub mod __exports {
13 pub use serde as __serde;
21}
22
23#[allow(unused_extern_crates)] extern crate self as confik;
26
27mod builder;
28#[cfg(feature = "common")]
29pub mod common;
30mod errors;
31pub mod helpers;
32mod path;
33#[cfg(feature = "reloading")]
34mod reloading;
35mod secrets;
36mod sources;
37mod std_impls;
38mod third_party;
39
40use self::path::Path;
41#[cfg(feature = "reloading")]
42pub use self::reloading::{ReloadCallback, ReloadableConfig, ReloadingConfig};
43#[cfg(feature = "env")]
44pub use self::sources::env_source::EnvSource;
45#[cfg(feature = "json")]
46pub use self::sources::json_source::JsonSource;
47#[cfg(feature = "toml")]
48pub use self::sources::toml_source::TomlSource;
49#[cfg(feature = "humantime")]
50pub use self::third_party::humantime;
51pub use self::{
52 builder::ConfigBuilder,
53 errors::Error,
54 secrets::{SecretBuilder, SecretOption, UnexpectedSecret},
55 sources::{file_source::FileSource, offset_source::OffsetSource, Source},
56};
57
58#[derive(Debug, Default, thiserror::Error)]
60#[error("Missing value for path `{0}`")]
61pub struct MissingValue(Path);
62
63impl MissingValue {
64 #[must_use]
66 pub fn prepend(mut self, path_segment: impl Into<Cow<'static, str>>) -> Self {
67 self.0 .0.push(path_segment.into());
68 self
69 }
70}
71
72#[derive(Debug, thiserror::Error)]
74#[error("Failed try_into for path `{0}`")]
75pub struct FailedTryInto(Path, #[source] Box<dyn StdError + Send + Sync>);
76
77impl FailedTryInto {
78 pub fn new(err: impl StdError + Send + Sync + 'static) -> Self {
80 Self(Path::new(), Box::new(err))
81 }
82
83 #[must_use]
85 pub fn prepend(mut self, path_segment: impl Into<Cow<'static, str>>) -> Self {
86 self.0 .0.push(path_segment.into());
87 self
88 }
89}
90
91fn build_from_sources<'a, Target, Iter>(sources: Iter) -> Result<Target, Error>
94where
95 Target: Configuration,
96 Iter: IntoIterator<Item = Box<dyn Source<Target::Builder> + 'a>>,
97{
98 sources
99 .into_iter()
100 .map::<Result<Target::Builder, Error>, _>(
102 |source: Box<dyn Source<Target::Builder> + 'a>| {
103 let debug = || format!("{source:?}");
104 let res = source.provide().map_err(|e| Error::Source(e, debug()))?;
105 if source.allows_secrets().not() {
106 res.contains_non_secret_data()
107 .map_err(|e| Error::UnexpectedSecret(e, debug()))?;
108 }
109 Ok(res)
110 },
111 )
112 .reduce(|first, second| Ok(Target::Builder::merge(first?, second?)))
114 .ok_or_else(|| Error::MissingValue(MissingValue::default()))??
116 .try_build()
117}
118
119pub trait Configuration: Sized {
141 type Builder: ConfigurationBuilder<Target = Self>;
143
144 #[must_use]
146 fn builder<'a>() -> ConfigBuilder<'a, Self> {
147 ConfigBuilder::<Self>::default()
148 }
149}
150
151pub trait ConfigurationBuilder: Default + DeserializeOwned {
160 type Target;
162
163 #[must_use]
165 fn merge(self, other: Self) -> Self;
166
167 fn try_build(self) -> Result<Self::Target, Error>;
170
171 fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret>;
178}
179
180impl<T> ConfigurationBuilder for Option<T>
185where
186 T: DeserializeOwned + Configuration,
187{
188 type Target = T;
189
190 fn merge(self, other: Self) -> Self {
191 self.or(other)
192 }
193
194 fn try_build(self) -> Result<Self::Target, Error> {
195 self.ok_or_else(|| Error::MissingValue(MissingValue::default()))
196 }
197
198 fn contains_non_secret_data(&self) -> Result<bool, UnexpectedSecret> {
201 Ok(self.is_some())
202 }
203}