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}