figment/providers/
serialized.rs

1use std::panic::Location;
2
3use serde::Serialize;
4
5use crate::{Profile, Provider, Metadata};
6use crate::error::{Error, Kind::InvalidType};
7use crate::value::{Value, Map, Dict};
8
9/// A `Provider` that sources values directly from a serialize type.
10///
11/// # Provider Details
12///
13///   * **Profile**
14///
15///     This provider does not set a profile.
16///
17///   * **Metadata**
18///
19///     This provider is named `T` (via [`std::any::type_name`]). The source
20///     location is set to the call site of the constructor.
21///
22///   * **Data (Unkeyed)**
23///
24///     When data is not keyed, `T` is expected to serialize to a [`Dict`] and
25///     is emitted directly as the value for the configured profile.
26///
27///   * **Data (Keyed)**
28///
29///     When keyed ([`Serialized::default()`], [`Serialized::global()`],
30///     [`Serialized::key()`]), `T` can serialize to any [`Value`] and is
31///     emitted as the value of the configured `key` key path. Nested
32///     dictionaries are created for every path component delimited by `.` in
33///     the `key` string, each dictionary mapping the path component to the
34///     child, with the leaf mapping to the serialized `T`. For instance,
35///     `a.b.c` results in `{ a: { b: { c: T }}}`.
36#[derive(Debug, Clone)]
37pub struct Serialized<T> {
38    /// The value to be serialized and used as the provided data.
39    pub value: T,
40    /// The key path (`a.b.c`) to emit the value to or the root if `None`.
41    pub key: Option<String>,
42    /// The profile to emit the value to. Defaults to [`Profile::Default`].
43    pub profile: Profile,
44    loc: &'static Location<'static>,
45}
46
47impl<T> Serialized<T> {
48    /// Constructs an (unkeyed) provider that emits `value`, which must
49    /// serialize to a `dict`, to the `profile`.
50    ///
51    /// ```rust
52    /// use serde::Deserialize;
53    /// use figment::{Figment, Jail, providers::Serialized, util::map};
54    ///
55    /// #[derive(Debug, PartialEq, Deserialize)]
56    /// struct Config {
57    ///     numbers: Vec<usize>,
58    /// }
59    ///
60    /// Jail::expect_with(|jail| {
61    ///     let map = map!["numbers" => &[1, 2, 3]];
62    ///
63    ///     // This is also `Serialized::defaults(&map)`;
64    ///     let figment = Figment::from(Serialized::from(&map, "default"));
65    ///     let config: Config = figment.extract()?;
66    ///     assert_eq!(config, Config { numbers: vec![1, 2, 3] });
67    ///
68    ///     // This is also `Serialized::defaults(&map).profile("debug")`;
69    ///     let figment = Figment::from(Serialized::from(&map, "debug"));
70    ///     let config: Config = figment.select("debug").extract()?;
71    ///     assert_eq!(config, Config { numbers: vec![1, 2, 3] });
72    ///
73    ///     Ok(())
74    /// });
75    /// ```
76    #[track_caller]
77    pub fn from<P: Into<Profile>>(value: T, profile: P) -> Serialized<T> {
78        Serialized {
79            value,
80            key: None,
81            profile: profile.into(),
82            loc: Location::caller()
83        }
84    }
85
86    /// Emits `value`, which must serialize to a [`Dict`], to the `Default`
87    /// profile.
88    ///
89    /// Equivalent to `Serialized::from(value, Profile::Default)`.
90    ///
91    /// See [`Serialized::from()`].
92    #[track_caller]
93    pub fn defaults(value: T) -> Serialized<T> {
94        Self::from(value, Profile::Default)
95    }
96
97    /// Emits `value`, which must serialize to a [`Dict`], to the `Global`
98    /// profile.
99    ///
100    /// Equivalent to `Serialized::from(value, Profile::Global)`.
101    ///
102    /// See [`Serialized::from()`].
103    #[track_caller]
104    pub fn globals(value: T) -> Serialized<T> {
105        Self::from(value, Profile::Global)
106    }
107
108    /// Emits a nested dictionary to the `Default` profile keyed by `key`
109    /// key path with the final key mapping to `value`.
110    ///
111    /// See [Data (keyed)](#provider-details) for key path details.
112    ///
113    /// Equivalent to `Serialized::from(value, Profile::Default).key(key)`.
114    ///
115    /// See [`Serialized::from()`] and [`Serialized::key()`].
116    #[track_caller]
117    pub fn default(key: &str, value: T) -> Serialized<T> {
118        Self::from(value, Profile::Default).key(key)
119    }
120
121    /// Emits a nested dictionary to the `Global` profile keyed by `key` with
122    /// the final key mapping to `value`.
123    ///
124    /// See [Data (keyed)](#provider-details) for key path details.
125    ///
126    /// Equivalent to `Serialized::from(value, Profile::Global).key(key)`.
127    ///
128    /// See [`Serialized::from()`] and [`Serialized::key()`].
129    #[track_caller]
130    pub fn global(key: &str, value: T) -> Serialized<T> {
131        Self::from(value, Profile::Global).key(key)
132    }
133
134    /// Sets the profile to emit the serialized value to.
135    ///
136    /// ```rust
137    /// use figment::{Figment, Jail, providers::Serialized};
138    ///
139    /// Jail::expect_with(|jail| {
140    ///     // This is also `Serialized::defaults(&map)`;
141    ///     let figment = Figment::new()
142    ///         .join(Serialized::default("key", "hey").profile("debug"))
143    ///         .join(Serialized::default("key", "hi"));
144    ///
145    ///     let value: String = figment.extract_inner("key")?;
146    ///     assert_eq!(value, "hi");
147    ///
148    ///     let value: String = figment.select("debug").extract_inner("key")?;
149    ///     assert_eq!(value, "hey");
150    ///
151    ///     Ok(())
152    /// });
153    /// ```
154    pub fn profile<P: Into<Profile>>(mut self, profile: P) -> Self {
155        self.profile = profile.into();
156        self
157    }
158
159    /// Sets the key path to emit the serialized value to.
160    ///
161    /// See [Data (keyed)](#provider-details) for key path details.
162    ///
163    /// ```rust
164    /// use figment::{Figment, Jail, providers::Serialized};
165    ///
166    /// Jail::expect_with(|jail| {
167    ///     // This is also `Serialized::defaults(&map)`;
168    ///     let figment = Figment::new()
169    ///         .join(Serialized::default("key", "hey").key("other"))
170    ///         .join(Serialized::default("key", "hi"));
171    ///
172    ///     let value: String = figment.extract_inner("key")?;
173    ///     assert_eq!(value, "hi");
174    ///
175    ///     let value: String = figment.extract_inner("other")?;
176    ///     assert_eq!(value, "hey");
177    ///
178    ///     Ok(())
179    /// });
180    /// ```
181    pub fn key(mut self, key: &str) -> Self {
182        self.key = Some(key.into());
183        self
184    }
185}
186
187impl<T: Serialize> Provider for Serialized<T> {
188    fn metadata(&self) -> Metadata {
189        Metadata::from(std::any::type_name::<T>(), self.loc)
190    }
191
192    fn data(&self) -> Result<Map<Profile, Dict>, Error> {
193        let value = Value::serialize(&self.value)?;
194        let error = InvalidType(value.to_actual(), "map".into());
195        let dict = match &self.key {
196            Some(key) => crate::util::nest(key, value).into_dict().ok_or(error)?,
197            None => value.into_dict().ok_or(error)?,
198        };
199
200        Ok(self.profile.clone().collect(dict))
201    }
202}