provcfg 0.1.0

Configuration library that keeps track of the origin and history of all config values.
Documentation
use crate::{Category, Source};

/// A [`Source`] wrapping a pre-built `*Partial`, typically produced by a CLI
/// argument parser (clap via the `provcfg-clap` `ClapArgs` derive, or by hand).
/// Reports [`Category::Cli`].
///
/// Internally serializes the partial to JSON, then re-deserializes through the
/// same path as every other source. The round-trip keeps [`Source`] strictly
/// format-agnostic.
///
/// ```
/// use provcfg::{Category, Config, Configurable};
/// use provcfg::sources::CliSource;
///
/// # #[derive(Configurable, serde::Deserialize, Clone, Default)]
/// # struct Settings { host: String }
/// // `SettingsPartial` is generated by `#[derive(Configurable)]`; a CLI parser
/// // fills in the fields it saw on the command line.
/// let from_cli = SettingsPartial { host: Some("cli.example".to_string()) };
///
/// let settings = Config::new()
///     .add_source(CliSource::new("cli", from_cli))
///     .build::<SettingsProv>()
///     .unwrap();
///
/// assert_eq!(settings.host.value(), "cli.example");
/// assert_eq!(settings.host.source().category(), Category::Cli);
/// ```
pub struct CliSource<P> {
    name: String,
    partial: P,
}

impl<P> CliSource<P>
where
    P: serde::Serialize + Send + Sync + 'static,
{
    /// Create a CLI source. `name` is a human-readable label used in error
    /// messages; `partial` is the pre-built `*Partial` to layer in.
    pub fn new(name: impl Into<String>, partial: P) -> Self {
        Self {
            name: name.into(),
            partial,
        }
    }
}

impl<P> Source for CliSource<P>
where
    P: serde::Serialize + Send + Sync + 'static,
{
    fn name(&self) -> &str {
        &self.name
    }

    fn category(&self) -> Category {
        Category::Cli
    }

    fn deserialize(
        &self,
        seed: &mut dyn for<'de> FnMut(
            &mut dyn erased_serde::Deserializer<'de>,
        ) -> Result<(), erased_serde::Error>,
    ) -> Result<(), erased_serde::Error> {
        let bytes = serde_json::to_vec(&self.partial)
            .map_err(<erased_serde::Error as serde::de::Error>::custom)?;
        let mut json_de = serde_json::Deserializer::from_slice(&bytes);
        let mut erased = <dyn erased_serde::Deserializer>::erase(&mut json_de);
        seed(&mut erased)
    }
}