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}