figment/
figment.rs

1use std::panic::Location;
2
3use serde::de::Deserialize;
4
5use crate::{Profile, Provider, Metadata};
6use crate::error::{Kind, Result};
7use crate::value::{Value, Map, Dict, Tag, ConfiguredValueDe, DefaultInterpreter, LossyInterpreter};
8use crate::coalesce::{Coalescible, Order};
9
10/// Combiner of [`Provider`]s for configuration value extraction.
11///
12/// # Overview
13///
14/// A `Figment` combines providers by merging or joining their provided data.
15/// The combined value or a subset of the combined value can be extracted into
16/// any type that implements [`Deserialize`]. Additionally, values can be nested
17/// in _profiles_, and a profile can be selected via [`Figment::select()`] for
18/// extraction; the profile to be extracted can be retrieved with
19/// [`Figment::profile()`] and defaults to [`Profile::Default`]. The [top-level
20/// docs](crate) contain a broad overview of these topics.
21///
22/// ## Conflict Resolution
23///
24/// Conflicts arising from two providers providing values for the same key are
25/// resolved via one of four strategies: [`join`], [`adjoin`], [`merge`], and
26/// [`admerge`]. In general, `join` and `adjoin` prefer existing values while
27/// `merge` and `admerge` prefer later values. The `ad-` strategies additionally
28/// concatenate conflicting arrays whereas the non-`ad-` strategies treat arrays
29/// as non-composite values.
30///
31/// The table below summarizes these strategies and their behavior, with the
32/// column label referring to the type of the value pointed to by the
33/// conflicting keys:
34///
35/// | Strategy    | Dictionaries   | Arrays        | All Others    |
36/// |-------------|----------------|---------------|---------------|
37/// | [`join`]    | Union, Recurse | Keep Existing | Keep Existing |
38/// | [`adjoin`]  | Union, Recurse | Concatenate   | Keep Existing |
39/// | [`merge`]   | Union, Recurse | Use Incoming  | Use Incoming  |
40/// | [`admerge`] | Union, Recurse | Concatenate   | Use Incoming  |
41///
42/// ### Description
43///
44/// If both keys point to a **dictionary**, the dictionaries are always unioned,
45/// irrespective of the strategy, and conflict resolution proceeds recursively
46/// with each key in the union.
47///
48/// If both keys point to an **array**:
49///
50///   * `join` uses the existing value
51///   * `merge` uses the incoming value
52///   * `adjoin` and `admerge` concatenate the arrays
53///
54/// If both keys point to a **non-composite** (`String`, `Num`, etc.) or values
55/// of different kinds (i.e, **array** and **num**):
56///
57///   * `join` and `adjoin` use the existing value
58///   * `merge` and `admerge` use the incoming value
59///
60/// [`join`]: Figment::join()
61/// [`adjoin`]: Figment::adjoin()
62/// [`merge`]: Figment::merge()
63/// [`admerge`]: Figment::admerge()
64///
65/// For examples, refer to each strategy's documentation.
66///
67/// ## Extraction
68///
69/// The configuration or a subset thereof can be extracted from a `Figment` in
70/// one of several ways:
71///
72///   * [`Figment::extract()`], which extracts the complete value into any `T:
73///     Deserialize`.
74///   * [`Figment::extract_inner()`], which extracts a subset of the value for a
75///     given key path.
76///   * [`Figment::find_value()`], which returns the raw, serialized [`Value`]
77///     for a given key path.
78///
79/// A "key path" is a string of the form `a.b.c` (e.g, `item`, `item.fruits`,
80/// etc.) where each component delimited by a `.` is a key for the dictionary of
81/// the preceding key in the path, or the root dictionary if it is the first key
82/// in the path. See [`Value::find()`] for examples.
83///
84/// ## Metadata
85///
86/// Every value collected by a `Figment` is accompanied by the metadata produced
87/// by the value's provider. Additionally, [`Metadata::provide_location`] is set
88/// by `from`, `merge` and `join` to the caller's location. `Metadata` can be
89/// retrieved in one of several ways:
90///
91///   * [`Figment::metadata()`], which returns an iterator over all of the
92///     metadata for all values.
93///   * [`Figment::find_metadata()`], which returns the metadata for a value at
94///     a given key path.
95///   * [`Figment::get_metadata()`], which returns the metadata for a given
96///     [`Tag`], itself retrieved via [`Tagged`] or [`Value::tag()`].
97///
98/// [`Tagged`]: crate::value::magic::Tagged
99#[derive(Clone, Debug)]
100pub struct Figment {
101    pub(crate) profile: Profile,
102    pub(crate) metadata: Map<Tag, Metadata>,
103    pub(crate) value: Result<Map<Profile, Dict>>,
104}
105
106impl Figment {
107    /// Creates a new `Figment` with the default profile selected and no
108    /// providers.
109    ///
110    /// ```rust
111    /// use figment::Figment;
112    ///
113    /// let figment = Figment::new();
114    /// # assert_eq!(figment.profile(), "default");
115    /// assert_eq!(figment.metadata().count(), 0);
116    /// ```
117    pub fn new() -> Self {
118        Figment {
119            metadata: Map::new(),
120            profile: Profile::Default,
121            value: Ok(Map::new()),
122        }
123    }
124
125    /// Creates a new `Figment` with the default profile selected and an initial
126    /// `provider`.
127    ///
128    /// ```rust
129    /// use figment::Figment;
130    /// use figment::providers::Env;
131    ///
132    /// let figment = Figment::from(Env::raw());
133    /// # assert_eq!(figment.profile(), "default");
134    /// assert_eq!(figment.metadata().count(), 1);
135    /// ```
136    #[track_caller]
137    pub fn from<T: Provider>(provider: T) -> Self {
138        Figment::new().merge(provider)
139    }
140
141    #[track_caller]
142    fn provide<T: Provider>(mut self, provider: T, order: Order) -> Self {
143        if let Some(map) = provider.__metadata_map() {
144            self.metadata.extend(map);
145        }
146
147        if let Some(profile) = provider.profile() {
148            self.profile = self.profile.coalesce(profile, order);
149        }
150
151        let mut metadata = provider.metadata();
152        metadata.provide_location = Some(Location::caller());
153
154        let tag = Tag::next();
155        self.metadata.insert(tag, metadata);
156        self.value = match (provider.data(), self.value) {
157            (Ok(_), e@Err(_)) => e,
158            (Err(e), Ok(_)) => Err(e.retagged(tag)),
159            (Err(e), Err(prev)) => Err(e.retagged(tag).chain(prev)),
160            (Ok(mut new), Ok(old)) => {
161                new.iter_mut()
162                    .map(|(p, map)| std::iter::repeat(p).zip(map.values_mut()))
163                    .flatten()
164                    .for_each(|(p, v)| v.map_tag(|t| *t = tag.for_profile(p)));
165
166                Ok(old.coalesce(new, order))
167            }
168        };
169
170        self
171    }
172
173    /// Joins `provider` into the current figment.
174    /// See [conflict resolution](#conflict-resolution) for details.
175    ///
176    /// ```rust
177    /// use figment::Figment;
178    /// use figment::util::map;
179    /// use figment::value::{Dict, Map};
180    ///
181    /// let figment = Figment::new()
182    ///     .join(("string", "original"))
183    ///     .join(("vec", vec!["item 1"]))
184    ///     .join(("map", map!["string" => "inner original"]));
185    ///
186    /// let new_figment = Figment::new()
187    ///     .join(("string", "replaced"))
188    ///     .join(("vec", vec!["item 2"]))
189    ///     .join(("map", map!["string" => "inner replaced", "new" => "value"]))
190    ///     .join(("new", "value"));
191    ///
192    /// let figment = figment.join(new_figment); // **join**
193    ///
194    /// let string: String = figment.extract_inner("string").unwrap();
195    /// assert_eq!(string, "original"); // existing value retained
196    ///
197    /// let vec: Vec<String> = figment.extract_inner("vec").unwrap();
198    /// assert_eq!(vec, vec!["item 1"]); // existing value retained
199    ///
200    /// let map: Map<String, String> = figment.extract_inner("map").unwrap();
201    /// assert_eq!(map, map! {
202    ///     "string".into() => "inner original".into(), // existing value retained
203    ///     "new".into() => "value".into(), // new key added
204    /// });
205    ///
206    /// let new: String = figment.extract_inner("new").unwrap();
207    /// assert_eq!(new, "value"); // new key added
208    /// ```
209    #[track_caller]
210    pub fn join<T: Provider>(self, provider: T) -> Self {
211        self.provide(provider, Order::Join)
212    }
213
214    /// Joins `provider` into the current figment while concatenating vectors.
215    /// See [conflict resolution](#conflict-resolution) for details.
216    ///
217    /// ```rust
218    /// use figment::Figment;
219    /// use figment::util::map;
220    /// use figment::value::{Dict, Map};
221    ///
222    /// let figment = Figment::new()
223    ///     .join(("string", "original"))
224    ///     .join(("vec", vec!["item 1"]))
225    ///     .join(("map", map!["vec" => vec!["inner item 1"]]));
226    ///
227    /// let new_figment = Figment::new()
228    ///     .join(("string", "replaced"))
229    ///     .join(("vec", vec!["item 2"]))
230    ///     .join(("map", map!["vec" => vec!["inner item 2"], "new" => vec!["value"]]))
231    ///     .join(("new", "value"));
232    ///
233    /// let figment = figment.adjoin(new_figment); // **adjoin**
234    ///
235    /// let string: String = figment.extract_inner("string").unwrap();
236    /// assert_eq!(string, "original"); // existing value retained
237    ///
238    /// let vec: Vec<String> = figment.extract_inner("vec").unwrap();
239    /// assert_eq!(vec, vec!["item 1", "item 2"]); // arrays concatenated
240    ///
241    /// let map: Map<String, Vec<String>> = figment.extract_inner("map").unwrap();
242    /// assert_eq!(map, map! {
243    ///     "vec".into() => vec!["inner item 1".into(), "inner item 2".into()], // arrays concatenated
244    ///     "new".into() => vec!["value".into()], // new key added
245    /// });
246    ///
247    /// let new: String = figment.extract_inner("new").unwrap();
248    /// assert_eq!(new, "value"); // new key added
249    /// ```
250    #[track_caller]
251    pub fn adjoin<T: Provider>(self, provider: T) -> Self {
252        self.provide(provider, Order::Adjoin)
253    }
254
255    /// Merges `provider` into the current figment.
256    /// See [conflict resolution](#conflict-resolution) for details.
257    ///
258    /// ```rust
259    /// use figment::Figment;
260    /// use figment::util::map;
261    /// use figment::value::{Dict, Map};
262    ///
263    /// let figment = Figment::new()
264    ///     .join(("string", "original"))
265    ///     .join(("vec", vec!["item 1"]))
266    ///     .join(("map", map!["string" => "inner original"]));
267    ///
268    /// let new_figment = Figment::new()
269    ///     .join(("string", "replaced"))
270    ///     .join(("vec", vec!["item 2"]))
271    ///     .join(("map", map!["string" => "inner replaced", "new" => "value"]))
272    ///     .join(("new", "value"));
273    ///
274    /// let figment = figment.merge(new_figment); // **merge**
275    ///
276    /// let string: String = figment.extract_inner("string").unwrap();
277    /// assert_eq!(string, "replaced"); // incoming value replaced existing
278    ///
279    /// let vec: Vec<String> = figment.extract_inner("vec").unwrap();
280    /// assert_eq!(vec, vec!["item 2"]); // incoming value replaced existing
281    ///
282    /// let map: Map<String, String> = figment.extract_inner("map").unwrap();
283    /// assert_eq!(map, map! {
284    ///     "string".into() => "inner replaced".into(), // incoming value replaced existing
285    ///     "new".into() => "value".into(), // new key added
286    /// });
287    ///
288    /// let new: String = figment.extract_inner("new").unwrap();
289    /// assert_eq!(new, "value"); // new key added
290    /// ```
291    #[track_caller]
292    pub fn merge<T: Provider>(self, provider: T) -> Self {
293        self.provide(provider, Order::Merge)
294    }
295
296    /// Merges `provider` into the current figment while concatenating vectors.
297    /// See [conflict resolution](#conflict-resolution) for details.
298    ///
299    /// ```rust
300    /// use figment::Figment;
301    /// use figment::util::map;
302    /// use figment::value::{Dict, Map};
303    ///
304    /// let figment = Figment::new()
305    ///     .join(("string", "original"))
306    ///     .join(("vec", vec!["item 1"]))
307    ///     .join(("map", map!["vec" => vec!["inner item 1"]]));
308    ///
309    /// let new_figment = Figment::new()
310    ///     .join(("string", "replaced"))
311    ///     .join(("vec", vec!["item 2"]))
312    ///     .join(("map", map!["vec" => vec!["inner item 2"], "new" => vec!["value"]]))
313    ///     .join(("new", "value"));
314    ///
315    /// let figment = figment.admerge(new_figment); // **admerge**
316    ///
317    /// let string: String = figment.extract_inner("string").unwrap();
318    /// assert_eq!(string, "replaced"); // incoming value replaced existing
319    ///
320    /// let vec: Vec<String> = figment.extract_inner("vec").unwrap();
321    /// assert_eq!(vec, vec!["item 1", "item 2"]); // arrays concatenated
322    ///
323    /// let map: Map<String, Vec<String>> = figment.extract_inner("map").unwrap();
324    /// assert_eq!(map, map! {
325    ///     "vec".into() => vec!["inner item 1".into(), "inner item 2".into()], // arrays concatenated
326    ///     "new".into() => vec!["value".into()], // new key added
327    /// });
328    ///
329    /// let new: String = figment.extract_inner("new").unwrap();
330    /// assert_eq!(new, "value"); // new key added
331    /// ```
332    #[track_caller]
333    pub fn admerge<T: Provider>(self, provider: T) -> Self {
334        self.provide(provider, Order::Admerge)
335    }
336
337    /// Sets the profile to extract from to `profile`.
338    ///
339    /// # Example
340    ///
341    /// ```
342    /// use figment::Figment;
343    ///
344    /// let figment = Figment::new().select("staging");
345    /// assert_eq!(figment.profile(), "staging");
346    /// ```
347    pub fn select<P: Into<Profile>>(mut self, profile: P) -> Self {
348        self.profile = profile.into();
349        self
350    }
351
352    /// Merges the selected profile with the default and global profiles.
353    fn merged(&self) -> Result<Value> {
354        let mut map = self.value.clone().map_err(|e| e.resolved(self))?;
355        let def = map.remove(&Profile::Default).unwrap_or_default();
356        let global = map.remove(&Profile::Global).unwrap_or_default();
357
358        let map = match map.remove(&self.profile) {
359            Some(v) if self.profile.is_custom() => def.merge(v).merge(global),
360            _ => def.merge(global)
361        };
362
363        Ok(Value::Dict(Tag::Default, map))
364    }
365
366    /// Returns a new `Figment` containing only the sub-dictionaries at `key`.
367    ///
368    /// This "sub-figment" is a _focusing_ of `self` with the property that:
369    ///
370    ///   * `self.find(key + ".sub")` <=> `focused.find("sub")`
371    ///
372    /// In other words, all values in `self` with a key starting with `key` are
373    /// in `focused` _without_ the prefix and vice-versa.
374    ///
375    /// # Example
376    ///
377    /// ```rust
378    /// use figment::{Figment, providers::{Format, Toml}};
379    ///
380    /// figment::Jail::expect_with(|jail| {
381    ///     jail.create_file("Config.toml", r#"
382    ///         cat = [1, 2, 3]
383    ///         dog = [4, 5, 6]
384    ///
385    ///         [subtree]
386    ///         cat = "meow"
387    ///         dog = "woof!"
388    ///
389    ///         [subtree.bark]
390    ///         dog = true
391    ///         cat = false
392    ///     "#)?;
393    ///
394    ///     let root = Figment::from(Toml::file("Config.toml"));
395    ///     assert_eq!(root.extract_inner::<Vec<u8>>("cat").unwrap(), vec![1, 2, 3]);
396    ///     assert_eq!(root.extract_inner::<Vec<u8>>("dog").unwrap(), vec![4, 5, 6]);
397    ///     assert_eq!(root.extract_inner::<String>("subtree.cat").unwrap(), "meow");
398    ///     assert_eq!(root.extract_inner::<String>("subtree.dog").unwrap(), "woof!");
399    ///
400    ///     let subtree = root.focus("subtree");
401    ///     assert_eq!(subtree.extract_inner::<String>("cat").unwrap(), "meow");
402    ///     assert_eq!(subtree.extract_inner::<String>("dog").unwrap(), "woof!");
403    ///     assert_eq!(subtree.extract_inner::<bool>("bark.cat").unwrap(), false);
404    ///     assert_eq!(subtree.extract_inner::<bool>("bark.dog").unwrap(), true);
405    ///
406    ///     let bark = subtree.focus("bark");
407    ///     assert_eq!(bark.extract_inner::<bool>("cat").unwrap(), false);
408    ///     assert_eq!(bark.extract_inner::<bool>("dog").unwrap(), true);
409    ///
410    ///     let not_a_dict = root.focus("cat");
411    ///     assert!(not_a_dict.extract_inner::<bool>("cat").is_err());
412    ///     assert!(not_a_dict.extract_inner::<bool>("dog").is_err());
413    ///
414    ///     Ok(())
415    /// });
416    /// ```
417    pub fn focus(&self, key: &str) -> Self {
418        fn try_focus(figment: &Figment, key: &str) -> Result<Map<Profile, Dict>> {
419            let map = figment.value.clone().map_err(|e| e.resolved(figment))?;
420            let new_map = map.into_iter()
421                .filter_map(|(k, v)| {
422                    let focused = Value::Dict(Tag::Default, v).find(key)?;
423                    let dict = focused.into_dict()?;
424                    Some((k, dict))
425                })
426                .collect();
427
428            Ok(new_map)
429        }
430
431        Figment {
432            profile: self.profile.clone(),
433            metadata: self.metadata.clone(),
434            value: try_focus(self, key)
435        }
436    }
437
438    /// Deserializes the collected value into `T`.
439    ///
440    /// # Example
441    ///
442    /// ```rust
443    /// use serde::Deserialize;
444    ///
445    /// use figment::{Figment, providers::{Format, Toml, Json, Env}};
446    ///
447    /// #[derive(Debug, PartialEq, Deserialize)]
448    /// struct Config {
449    ///     name: String,
450    ///     numbers: Option<Vec<usize>>,
451    ///     debug: bool,
452    /// }
453    ///
454    /// figment::Jail::expect_with(|jail| {
455    ///     jail.create_file("Config.toml", r#"
456    ///         name = "test"
457    ///         numbers = [1, 2, 3, 10]
458    ///     "#)?;
459    ///
460    ///     jail.set_env("config_name", "env-test");
461    ///
462    ///     jail.create_file("Config.json", r#"
463    ///         {
464    ///             "name": "json-test",
465    ///             "debug": true
466    ///         }
467    ///     "#)?;
468    ///
469    ///     let config: Config = Figment::new()
470    ///         .merge(Toml::file("Config.toml"))
471    ///         .merge(Env::prefixed("CONFIG_"))
472    ///         .join(Json::file("Config.json"))
473    ///         .extract()?;
474    ///
475    ///     assert_eq!(config, Config {
476    ///         name: "env-test".into(),
477    ///         numbers: vec![1, 2, 3, 10].into(),
478    ///         debug: true
479    ///     });
480    ///
481    ///     Ok(())
482    /// });
483    /// ```
484    pub fn extract<'a, T: Deserialize<'a>>(&self) -> Result<T> {
485        let value = self.merged()?;
486        T::deserialize(ConfiguredValueDe::<'_, DefaultInterpreter>::from(self, &value))
487    }
488
489    /// As [`extract`](Figment::extract_lossy), but interpret numbers and
490    /// booleans more flexibly.
491    ///
492    /// See [`Value::to_bool_lossy`] and [`Value::to_num_lossy`] for a full
493    /// explanation of the imputs accepted.
494    ///
495    ///
496    /// # Example
497    ///
498    /// ```rust
499    /// use serde::Deserialize;
500    ///
501    /// use figment::{Figment, providers::{Format, Toml, Json, Env}};
502    ///
503    /// #[derive(Debug, PartialEq, Deserialize)]
504    /// struct Config {
505    ///     name: String,
506    ///     numbers: Option<Vec<usize>>,
507    ///     debug: bool,
508    /// }
509    ///
510    /// figment::Jail::expect_with(|jail| {
511    ///     jail.create_file("Config.toml", r#"
512    ///         name = "test"
513    ///         numbers = ["1", "2", "3", "10"]
514    ///     "#)?;
515    ///
516    ///     jail.set_env("config_name", "env-test");
517    ///
518    ///     jail.create_file("Config.json", r#"
519    ///         {
520    ///             "name": "json-test",
521    ///             "debug": "yes"
522    ///         }
523    ///     "#)?;
524    ///
525    ///     let config: Config = Figment::new()
526    ///         .merge(Toml::file("Config.toml"))
527    ///         .merge(Env::prefixed("CONFIG_"))
528    ///         .join(Json::file("Config.json"))
529    ///         .extract_lossy()?;
530    ///
531    ///     assert_eq!(config, Config {
532    ///         name: "env-test".into(),
533    ///         numbers: vec![1, 2, 3, 10].into(),
534    ///         debug: true
535    ///     });
536    ///
537    ///     Ok(())
538    /// });
539    /// ```
540    pub fn extract_lossy<'a, T: Deserialize<'a>>(&self) -> Result<T> {
541        let value = self.merged()?;
542        T::deserialize(ConfiguredValueDe::<'_, LossyInterpreter>::from(self, &value))
543    }
544
545    /// Deserializes the value at the `key` path in the collected value into
546    /// `T`.
547    ///
548    /// # Example
549    ///
550    /// ```rust
551    /// use figment::{Figment, providers::{Format, Toml, Json}};
552    ///
553    /// figment::Jail::expect_with(|jail| {
554    ///     jail.create_file("Config.toml", r#"
555    ///         numbers = [1, 2, 3, 10]
556    ///     "#)?;
557    ///
558    ///     jail.create_file("Config.json", r#"{ "debug": true } "#)?;
559    ///
560    ///     let numbers: Vec<usize> = Figment::new()
561    ///         .merge(Toml::file("Config.toml"))
562    ///         .join(Json::file("Config.json"))
563    ///         .extract_inner("numbers")?;
564    ///
565    ///     assert_eq!(numbers, vec![1, 2, 3, 10]);
566    ///
567    ///     Ok(())
568    /// });
569    /// ```
570    pub fn extract_inner<'a, T: Deserialize<'a>>(&self, path: &str) -> Result<T> {
571        let value = self.find_value(path)?;
572        let de = ConfiguredValueDe::<'_, DefaultInterpreter>::from(self, &value);
573        T::deserialize(de).map_err(|e| e.with_path(path))
574    }
575
576    /// As [`extract`](Figment::extract_lossy), but interpret numbers and
577    /// booleans more flexibly.
578    ///
579    /// See [`Value::to_bool_lossy`] and [`Value::to_num_lossy`] for a full
580    /// explanation of the imputs accepted.
581    ///
582    /// # Example
583    ///
584    /// ```rust
585    /// use figment::{Figment, providers::{Format, Toml, Json}};
586    ///
587    /// figment::Jail::expect_with(|jail| {
588    ///     jail.create_file("Config.toml", r#"
589    ///         numbers = ["1", "2", "3", "10"]
590    ///     "#)?;
591    ///
592    ///     jail.create_file("Config.json", r#"{ "debug": true } "#)?;
593    ///
594    ///     let numbers: Vec<usize> = Figment::new()
595    ///         .merge(Toml::file("Config.toml"))
596    ///         .join(Json::file("Config.json"))
597    ///         .extract_inner_lossy("numbers")?;
598    ///
599    ///     assert_eq!(numbers, vec![1, 2, 3, 10]);
600    ///
601    ///     Ok(())
602    /// });
603    /// ```
604    pub fn extract_inner_lossy<'a, T: Deserialize<'a>>(&self, path: &str) -> Result<T> {
605        let value = self.find_value(path)?;
606        let de = ConfiguredValueDe::<'_, LossyInterpreter>::from(self, &value);
607        T::deserialize(de).map_err(|e| e.with_path(path))
608    }
609
610    /// Returns an iterator over the metadata for all of the collected values in
611    /// the order in which they were added to `self`.
612    ///
613    /// # Example
614    ///
615    /// ```rust
616    /// use figment::{Figment, providers::{Format, Toml, Json}};
617    ///
618    /// let figment = Figment::new()
619    ///     .merge(Toml::file("Config.toml"))
620    ///     .join(Json::file("Config.json"));
621    ///
622    /// assert_eq!(figment.metadata().count(), 2);
623    /// for (i, md) in figment.metadata().enumerate() {
624    ///     match i {
625    ///         0 => assert!(md.name.starts_with("TOML")),
626    ///         1 => assert!(md.name.starts_with("JSON")),
627    ///         _ => unreachable!(),
628    ///     }
629    /// }
630    /// ```
631    // In fact, the order in which they were added globally. Why? Because
632    // `BTreeMap` returns values in order of keys, and we generate a new ID,
633    // monotonically greater than the previous, each time a new item is
634    // provided. It's important that the IDs are unique globally since we can
635    // allow combining `Figment`s.
636    pub fn metadata(&self) -> impl Iterator<Item = &Metadata> {
637        self.metadata.values()
638    }
639
640    /// Returns the selected profile.
641    ///
642    /// # Example
643    ///
644    /// ```
645    /// use figment::Figment;
646    ///
647    /// let figment = Figment::new();
648    /// assert_eq!(figment.profile(), "default");
649    ///
650    /// let figment = figment.select("staging");
651    /// assert_eq!(figment.profile(), "staging");
652    /// ```
653    pub fn profile(&self) -> &Profile {
654        &self.profile
655    }
656
657    /// Returns an iterator over profiles with valid configurations in this
658    /// figment. **Note:** this may not include the selected profile if the
659    /// selected profile has no configured values.
660    ///
661    /// # Example
662    ///
663    /// ```
664    /// use figment::{Figment, providers::Serialized};
665    ///
666    /// let figment = Figment::new();
667    /// let profiles = figment.profiles().collect::<Vec<_>>();
668    /// assert_eq!(profiles.len(), 0);
669    ///
670    /// let figment = Figment::new()
671    ///     .join(Serialized::default("key", "hi"))
672    ///     .join(Serialized::default("key", "hey").profile("debug"));
673    ///
674    /// let mut profiles = figment.profiles().collect::<Vec<_>>();
675    /// profiles.sort();
676    /// assert_eq!(profiles, &["debug", "default"]);
677    ///
678    /// let figment = Figment::new()
679    ///     .join(Serialized::default("key", "hi").profile("release"))
680    ///     .join(Serialized::default("key", "hi").profile("testing"))
681    ///     .join(Serialized::default("key", "hey").profile("staging"))
682    ///     .select("debug");
683    ///
684    /// let mut profiles = figment.profiles().collect::<Vec<_>>();
685    /// profiles.sort();
686    /// assert_eq!(profiles, &["release", "staging", "testing"]);
687    /// ```
688    pub fn profiles(&self) -> impl Iterator<Item = &Profile> {
689        self.value.as_ref()
690            .ok()
691            .map(|v| v.keys())
692            .into_iter()
693            .flatten()
694    }
695
696    /// Finds the value at `path` in the combined value.
697    ///
698    /// If there is an error evaluating the combined figment, that error is
699    /// returned. Otherwise if there is a value at `path`, returns `Ok(value)`,
700    /// and if there is no value at `path`, returns `Err` of kind
701    /// `MissingField`.
702    ///
703    /// See [`Value::find()`] for details on the syntax for `path`.
704    ///
705    /// # Example
706    ///
707    /// ```rust
708    /// use serde::Deserialize;
709    ///
710    /// use figment::{Figment, providers::{Format, Toml, Json, Env}};
711    ///
712    /// figment::Jail::expect_with(|jail| {
713    ///     jail.create_file("Config.toml", r#"
714    ///         name = "test"
715    ///
716    ///         [package]
717    ///         name = "my-package"
718    ///     "#)?;
719    ///
720    ///     jail.create_file("Config.json", r#"
721    ///         {
722    ///             "author": { "name": "Bob" }
723    ///         }
724    ///     "#)?;
725    ///
726    ///     let figment = Figment::new()
727    ///         .merge(Toml::file("Config.toml"))
728    ///         .join(Json::file("Config.json"));
729    ///
730    ///     let name = figment.find_value("name")?;
731    ///     assert_eq!(name.as_str(), Some("test"));
732    ///
733    ///     let package_name = figment.find_value("package.name")?;
734    ///     assert_eq!(package_name.as_str(), Some("my-package"));
735    ///
736    ///     let author_name = figment.find_value("author.name")?;
737    ///     assert_eq!(author_name.as_str(), Some("Bob"));
738    ///
739    ///     Ok(())
740    /// });
741    /// ```
742    pub fn find_value(&self, path: &str) -> Result<Value> {
743        self.merged()?
744            .find(path)
745            .ok_or_else(|| Kind::MissingField(path.to_string().into()).into())
746    }
747
748    /// Returns `true` if the combined figment evaluates successfully and
749    /// contains a value at `path`.
750    ///
751    /// See [`Value::find()`] for details on the syntax for `path`.
752    ///
753    /// # Example
754    ///
755    /// ```rust
756    /// use serde::Deserialize;
757    ///
758    /// use figment::{Figment, providers::{Format, Toml, Json, Env}};
759    ///
760    /// figment::Jail::expect_with(|jail| {
761    ///     jail.create_file("Config.toml", r#"
762    ///         name = "test"
763    ///
764    ///         [package]
765    ///         name = "my-package"
766    ///     "#)?;
767    ///
768    ///     jail.create_file("Config.json", r#"
769    ///         {
770    ///             "author": { "name": "Bob" }
771    ///         }
772    ///     "#)?;
773    ///
774    ///     let figment = Figment::new()
775    ///         .merge(Toml::file("Config.toml"))
776    ///         .join(Json::file("Config.json"));
777    ///
778    ///     assert!(figment.contains("name"));
779    ///     assert!(figment.contains("package"));
780    ///     assert!(figment.contains("package.name"));
781    ///     assert!(figment.contains("author"));
782    ///     assert!(figment.contains("author.name"));
783    ///     assert!(!figment.contains("author.title"));
784    ///     Ok(())
785    /// });
786    /// ```
787    pub fn contains(&self, path: &str) -> bool {
788        self.merged().map_or(false, |v| v.find_ref(path).is_some())
789    }
790
791    /// Finds the metadata for the value at `key` path. See [`Value::find()`]
792    /// for details on the syntax for `key`.
793    ///
794    /// # Example
795    ///
796    /// ```rust
797    /// use serde::Deserialize;
798    ///
799    /// use figment::{Figment, providers::{Format, Toml, Json, Env}};
800    ///
801    /// figment::Jail::expect_with(|jail| {
802    ///     jail.create_file("Config.toml", r#" name = "test" "#)?;
803    ///     jail.set_env("CONF_AUTHOR", "Bob");
804    ///
805    ///     let figment = Figment::new()
806    ///         .merge(Toml::file("Config.toml"))
807    ///         .join(Env::prefixed("CONF_").only(&["author"]));
808    ///
809    ///     let name_md = figment.find_metadata("name").unwrap();
810    ///     assert!(name_md.name.starts_with("TOML"));
811    ///
812    ///     let author_md = figment.find_metadata("author").unwrap();
813    ///     assert!(author_md.name.contains("CONF_"));
814    ///     assert!(author_md.name.contains("environment"));
815    ///
816    ///     Ok(())
817    /// });
818    /// ```
819    pub fn find_metadata(&self, key: &str) -> Option<&Metadata> {
820        self.metadata.get(&self.find_value(key).ok()?.tag())
821    }
822
823    /// Returns the metadata with the given `tag` if this figment contains a
824    /// value with said metadata.
825    ///
826    /// # Example
827    ///
828    /// ```rust
829    /// use serde::Deserialize;
830    ///
831    /// use figment::{Figment, providers::{Format, Toml, Json, Env}};
832    ///
833    /// figment::Jail::expect_with(|jail| {
834    ///     jail.create_file("Config.toml", r#" name = "test" "#)?;
835    ///     jail.create_file("Config.json", r#" { "author": "Bob" } "#)?;
836    ///
837    ///     let figment = Figment::new()
838    ///         .merge(Toml::file("Config.toml"))
839    ///         .join(Json::file("Config.json"));
840    ///
841    ///     let name = figment.find_value("name").unwrap();
842    ///     let metadata = figment.get_metadata(name.tag()).unwrap();
843    ///     assert!(metadata.name.starts_with("TOML"));
844    ///
845    ///     let author = figment.find_value("author").unwrap();
846    ///     let metadata = figment.get_metadata(author.tag()).unwrap();
847    ///     assert!(metadata.name.starts_with("JSON"));
848    ///
849    ///     Ok(())
850    /// });
851    /// ```
852    pub fn get_metadata(&self, tag: Tag) -> Option<&Metadata> {
853        self.metadata.get(&tag)
854    }
855}
856
857impl Provider for Figment {
858    fn metadata(&self) -> Metadata { Metadata::default() }
859
860    fn data(&self) -> Result<Map<Profile, Dict>> { self.value.clone() }
861
862    fn profile(&self) -> Option<Profile> {
863        Some(self.profile.clone())
864    }
865
866    fn __metadata_map(&self) -> Option<Map<Tag, Metadata>> {
867        Some(self.metadata.clone())
868    }
869}
870
871impl Default for Figment {
872    fn default() -> Self {
873        Figment::new()
874    }
875}
876
877#[test]
878#[cfg(test)]
879fn is_send_sync() {
880    fn check_for_send_sync<T: Send + Sync>() {}
881    check_for_send_sync::<Figment>();
882}