Skip to main content

provcfg/sources/
cli.rs

1use crate::{Category, Source};
2
3/// A [`Source`] wrapping a pre-built `*Partial`, typically produced by a CLI
4/// argument parser (clap via the `provcfg-clap` `ClapArgs` derive, or by hand).
5/// Reports [`Category::Cli`].
6///
7/// Internally serializes the partial to JSON, then re-deserializes through the
8/// same path as every other source. The round-trip keeps [`Source`] strictly
9/// format-agnostic.
10///
11/// ```
12/// use provcfg::{Category, Config, Configurable};
13/// use provcfg::sources::CliSource;
14///
15/// # #[derive(Configurable, serde::Deserialize, Clone, Default)]
16/// # struct Settings { host: String }
17/// // `SettingsPartial` is generated by `#[derive(Configurable)]`; a CLI parser
18/// // fills in the fields it saw on the command line.
19/// let from_cli = SettingsPartial { host: Some("cli.example".to_string()) };
20///
21/// let settings = Config::new()
22///     .add_source(CliSource::new("cli", from_cli))
23///     .build::<SettingsProv>()
24///     .unwrap();
25///
26/// assert_eq!(settings.host.value(), "cli.example");
27/// assert_eq!(settings.host.source().category(), Category::Cli);
28/// ```
29pub struct CliSource<P> {
30    name: String,
31    partial: P,
32}
33
34impl<P> CliSource<P>
35where
36    P: serde::Serialize + Send + Sync + 'static,
37{
38    /// Create a CLI source. `name` is a human-readable label used in error
39    /// messages; `partial` is the pre-built `*Partial` to layer in.
40    pub fn new(name: impl Into<String>, partial: P) -> Self {
41        Self {
42            name: name.into(),
43            partial,
44        }
45    }
46}
47
48impl<P> Source for CliSource<P>
49where
50    P: serde::Serialize + Send + Sync + 'static,
51{
52    fn name(&self) -> &str {
53        &self.name
54    }
55
56    fn category(&self) -> Category {
57        Category::Cli
58    }
59
60    fn deserialize(
61        &self,
62        seed: &mut dyn for<'de> FnMut(
63            &mut dyn erased_serde::Deserializer<'de>,
64        ) -> Result<(), erased_serde::Error>,
65    ) -> Result<(), erased_serde::Error> {
66        let bytes = serde_json::to_vec(&self.partial)
67            .map_err(<erased_serde::Error as serde::de::Error>::custom)?;
68        let mut json_de = serde_json::Deserializer::from_slice(&bytes);
69        let mut erased = <dyn erased_serde::Deserializer>::erase(&mut json_de);
70        seed(&mut erased)
71    }
72}