pub struct Figment { /* private fields */ }
Expand description
Combiner of Provider
s for configuration value extraction.
Overview
A Figment
combines providers by merging or joining their provided data.
The combined value or a subset of the combined value can be extracted into
any type that implements Deserialize
. Additionally, values can be nested
in profiles, and a profile can be selected via Figment::select()
for
extraction; the profile to be extracted can be retrieved with
Figment::profile()
and defaults to Profile::Default
. The top-level
docs contain a broad overview of these topics.
Conflict Resolution
Conflicts arising from two providers providing values for the same key are
resolved via one of four strategies: join
, adjoin
, merge
, and
admerge
. In general, join
and adjoin
prefer existing values while
merge
and admerge
prefer later values. The ad-
strategies additionally
concatenate conflicting arrays whereas the non-ad-
strategies treat arrays
as non-composite values.
The table below summarizes these strategies and their behavior, with the column label referring to the type of the value pointed to by the conflicting keys:
Strategy | Dictionaries | Arrays | All Others |
---|---|---|---|
join | Union, Recurse | Keep Existing | Keep Existing |
adjoin | Union, Recurse | Concatenate | Keep Existing |
merge | Union, Recurse | Use Incoming | Use Incoming |
admerge | Union, Recurse | Concatenate | Use Incoming |
Description
If both keys point to a dictionary, the dictionaries are always unioned, irrespective of the strategy, and conflict resolution proceeds recursively with each key in the union.
If both keys point to an array:
join
uses the existing valuemerge
uses the incoming valueadjoin
andadmerge
concatenate the arrays
If both keys point to a non-composite (String
, Num
, etc.) or values
of different kinds (i.e, array and num):
join
andadjoin
use the existing valuemerge
andadmerge
use the incoming value
For examples, refer to each strategy’s documentation.
Extraction
The configuration or a subset thereof can be extracted from a Figment
in
one of several ways:
Figment::extract()
, which extracts the complete value into anyT: Deserialize
.Figment::extract_inner()
, which extracts a subset of the value for a given key path.Figment::find_value()
, which returns the raw, serializedValue
for a given key path.
A “key path” is a string of the form a.b.c
(e.g, item
, item.fruits
,
etc.) where each component delimited by a .
is a key for the dictionary of
the preceding key in the path, or the root dictionary if it is the first key
in the path. See Value::find()
for examples.
Metadata
Every value collected by a Figment
is accompanied by the metadata produced
by the value’s provider. Additionally, Metadata::provide_location
is set
by from
, merge
and join
to the caller’s location. Metadata
can be
retrieved in one of several ways:
Figment::metadata()
, which returns an iterator over all of the metadata for all values.Figment::find_metadata()
, which returns the metadata for a value at a given key path.Figment::get_metadata()
, which returns the metadata for a givenTag
, itself retrieved viaTagged
orValue::tag()
.
Implementations§
source§impl Figment
impl Figment
sourcepub fn new() -> Self
pub fn new() -> Self
Creates a new Figment
with the default profile selected and no
providers.
use figment::Figment;
let figment = Figment::new();
assert_eq!(figment.metadata().count(), 0);
sourcepub fn from<T: Provider>(provider: T) -> Self
pub fn from<T: Provider>(provider: T) -> Self
Creates a new Figment
with the default profile selected and an initial
provider
.
use figment::Figment;
use figment::providers::Env;
let figment = Figment::from(Env::raw());
assert_eq!(figment.metadata().count(), 1);
sourcepub fn join<T: Provider>(self, provider: T) -> Self
pub fn join<T: Provider>(self, provider: T) -> Self
Joins provider
into the current figment.
See conflict resolution for details.
use figment::Figment;
use figment::util::map;
use figment::value::{Dict, Map};
let figment = Figment::new()
.join(("string", "original"))
.join(("vec", vec!["item 1"]))
.join(("map", map!["string" => "inner original"]));
let new_figment = Figment::new()
.join(("string", "replaced"))
.join(("vec", vec!["item 2"]))
.join(("map", map!["string" => "inner replaced", "new" => "value"]))
.join(("new", "value"));
let figment = figment.join(new_figment); // **join**
let string: String = figment.extract_inner("string").unwrap();
assert_eq!(string, "original"); // existing value retained
let vec: Vec<String> = figment.extract_inner("vec").unwrap();
assert_eq!(vec, vec!["item 1"]); // existing value retained
let map: Map<String, String> = figment.extract_inner("map").unwrap();
assert_eq!(map, map! {
"string".into() => "inner original".into(), // existing value retained
"new".into() => "value".into(), // new key added
});
let new: String = figment.extract_inner("new").unwrap();
assert_eq!(new, "value"); // new key added
sourcepub fn adjoin<T: Provider>(self, provider: T) -> Self
pub fn adjoin<T: Provider>(self, provider: T) -> Self
Joins provider
into the current figment while concatenating vectors.
See conflict resolution for details.
use figment::Figment;
use figment::util::map;
use figment::value::{Dict, Map};
let figment = Figment::new()
.join(("string", "original"))
.join(("vec", vec!["item 1"]))
.join(("map", map!["vec" => vec!["inner item 1"]]));
let new_figment = Figment::new()
.join(("string", "replaced"))
.join(("vec", vec!["item 2"]))
.join(("map", map!["vec" => vec!["inner item 2"], "new" => vec!["value"]]))
.join(("new", "value"));
let figment = figment.adjoin(new_figment); // **adjoin**
let string: String = figment.extract_inner("string").unwrap();
assert_eq!(string, "original"); // existing value retained
let vec: Vec<String> = figment.extract_inner("vec").unwrap();
assert_eq!(vec, vec!["item 1", "item 2"]); // arrays concatenated
let map: Map<String, Vec<String>> = figment.extract_inner("map").unwrap();
assert_eq!(map, map! {
"vec".into() => vec!["inner item 1".into(), "inner item 2".into()], // arrays concatenated
"new".into() => vec!["value".into()], // new key added
});
let new: String = figment.extract_inner("new").unwrap();
assert_eq!(new, "value"); // new key added
sourcepub fn merge<T: Provider>(self, provider: T) -> Self
pub fn merge<T: Provider>(self, provider: T) -> Self
Merges provider
into the current figment.
See conflict resolution for details.
use figment::Figment;
use figment::util::map;
use figment::value::{Dict, Map};
let figment = Figment::new()
.join(("string", "original"))
.join(("vec", vec!["item 1"]))
.join(("map", map!["string" => "inner original"]));
let new_figment = Figment::new()
.join(("string", "replaced"))
.join(("vec", vec!["item 2"]))
.join(("map", map!["string" => "inner replaced", "new" => "value"]))
.join(("new", "value"));
let figment = figment.merge(new_figment); // **merge**
let string: String = figment.extract_inner("string").unwrap();
assert_eq!(string, "replaced"); // incoming value replaced existing
let vec: Vec<String> = figment.extract_inner("vec").unwrap();
assert_eq!(vec, vec!["item 2"]); // incoming value replaced existing
let map: Map<String, String> = figment.extract_inner("map").unwrap();
assert_eq!(map, map! {
"string".into() => "inner replaced".into(), // incoming value replaced existing
"new".into() => "value".into(), // new key added
});
let new: String = figment.extract_inner("new").unwrap();
assert_eq!(new, "value"); // new key added
sourcepub fn admerge<T: Provider>(self, provider: T) -> Self
pub fn admerge<T: Provider>(self, provider: T) -> Self
Merges provider
into the current figment while concatenating vectors.
See conflict resolution for details.
use figment::Figment;
use figment::util::map;
use figment::value::{Dict, Map};
let figment = Figment::new()
.join(("string", "original"))
.join(("vec", vec!["item 1"]))
.join(("map", map!["vec" => vec!["inner item 1"]]));
let new_figment = Figment::new()
.join(("string", "replaced"))
.join(("vec", vec!["item 2"]))
.join(("map", map!["vec" => vec!["inner item 2"], "new" => vec!["value"]]))
.join(("new", "value"));
let figment = figment.admerge(new_figment); // **admerge**
let string: String = figment.extract_inner("string").unwrap();
assert_eq!(string, "replaced"); // incoming value replaced existing
let vec: Vec<String> = figment.extract_inner("vec").unwrap();
assert_eq!(vec, vec!["item 1", "item 2"]); // arrays concatenated
let map: Map<String, Vec<String>> = figment.extract_inner("map").unwrap();
assert_eq!(map, map! {
"vec".into() => vec!["inner item 1".into(), "inner item 2".into()], // arrays concatenated
"new".into() => vec!["value".into()], // new key added
});
let new: String = figment.extract_inner("new").unwrap();
assert_eq!(new, "value"); // new key added
sourcepub fn select<P: Into<Profile>>(self, profile: P) -> Self
pub fn select<P: Into<Profile>>(self, profile: P) -> Self
Sets the profile to extract from to profile
.
Example
use figment::Figment;
let figment = Figment::new().select("staging");
assert_eq!(figment.profile(), "staging");
sourcepub fn focus(&self, key: &str) -> Self
pub fn focus(&self, key: &str) -> Self
Returns a new Figment
containing only the sub-dictionaries at key
.
This “sub-figment” is a focusing of self
with the property that:
self.find(key + ".sub")
<=>focused.find("sub")
In other words, all values in self
with a key starting with key
are
in focused
without the prefix and vice-versa.
Example
use figment::{Figment, providers::{Format, Toml}};
figment::Jail::expect_with(|jail| {
jail.create_file("Config.toml", r#"
cat = [1, 2, 3]
dog = [4, 5, 6]
[subtree]
cat = "meow"
dog = "woof!"
[subtree.bark]
dog = true
cat = false
"#)?;
let root = Figment::from(Toml::file("Config.toml"));
assert_eq!(root.extract_inner::<Vec<u8>>("cat").unwrap(), vec![1, 2, 3]);
assert_eq!(root.extract_inner::<Vec<u8>>("dog").unwrap(), vec![4, 5, 6]);
assert_eq!(root.extract_inner::<String>("subtree.cat").unwrap(), "meow");
assert_eq!(root.extract_inner::<String>("subtree.dog").unwrap(), "woof!");
let subtree = root.focus("subtree");
assert_eq!(subtree.extract_inner::<String>("cat").unwrap(), "meow");
assert_eq!(subtree.extract_inner::<String>("dog").unwrap(), "woof!");
assert_eq!(subtree.extract_inner::<bool>("bark.cat").unwrap(), false);
assert_eq!(subtree.extract_inner::<bool>("bark.dog").unwrap(), true);
let bark = subtree.focus("bark");
assert_eq!(bark.extract_inner::<bool>("cat").unwrap(), false);
assert_eq!(bark.extract_inner::<bool>("dog").unwrap(), true);
let not_a_dict = root.focus("cat");
assert!(not_a_dict.extract_inner::<bool>("cat").is_err());
assert!(not_a_dict.extract_inner::<bool>("dog").is_err());
Ok(())
});
sourcepub fn extract<'a, T: Deserialize<'a>>(&self) -> Result<T>
pub fn extract<'a, T: Deserialize<'a>>(&self) -> Result<T>
Deserializes the collected value into T
.
Example
use serde::Deserialize;
use figment::{Figment, providers::{Format, Toml, Json, Env}};
#[derive(Debug, PartialEq, Deserialize)]
struct Config {
name: String,
numbers: Option<Vec<usize>>,
debug: bool,
}
figment::Jail::expect_with(|jail| {
jail.create_file("Config.toml", r#"
name = "test"
numbers = [1, 2, 3, 10]
"#)?;
jail.set_env("config_name", "env-test");
jail.create_file("Config.json", r#"
{
"name": "json-test",
"debug": true
}
"#)?;
let config: Config = Figment::new()
.merge(Toml::file("Config.toml"))
.merge(Env::prefixed("CONFIG_"))
.join(Json::file("Config.json"))
.extract()?;
assert_eq!(config, Config {
name: "env-test".into(),
numbers: vec![1, 2, 3, 10].into(),
debug: true
});
Ok(())
});
sourcepub fn extract_inner<'a, T: Deserialize<'a>>(&self, key: &str) -> Result<T>
pub fn extract_inner<'a, T: Deserialize<'a>>(&self, key: &str) -> Result<T>
Deserializes the value at the key
path in the collected value into
T
.
Example
use figment::{Figment, providers::{Format, Toml, Json}};
figment::Jail::expect_with(|jail| {
jail.create_file("Config.toml", r#"
numbers = [1, 2, 3, 10]
"#)?;
jail.create_file("Config.json", r#"{ "debug": true } "#)?;
let numbers: Vec<usize> = Figment::new()
.merge(Toml::file("Config.toml"))
.join(Json::file("Config.json"))
.extract_inner("numbers")?;
assert_eq!(numbers, vec![1, 2, 3, 10]);
Ok(())
});
sourcepub fn metadata(&self) -> impl Iterator<Item = &Metadata>
pub fn metadata(&self) -> impl Iterator<Item = &Metadata>
Returns an iterator over the metadata for all of the collected values in
the order in which they were added to self
.
Example
use figment::{Figment, providers::{Format, Toml, Json}};
let figment = Figment::new()
.merge(Toml::file("Config.toml"))
.join(Json::file("Config.json"));
assert_eq!(figment.metadata().count(), 2);
for (i, md) in figment.metadata().enumerate() {
match i {
0 => assert!(md.name.starts_with("TOML")),
1 => assert!(md.name.starts_with("JSON")),
_ => unreachable!(),
}
}
sourcepub fn profile(&self) -> &Profile
pub fn profile(&self) -> &Profile
Returns the selected profile.
Example
use figment::Figment;
let figment = Figment::new();
assert_eq!(figment.profile(), "default");
let figment = figment.select("staging");
assert_eq!(figment.profile(), "staging");
sourcepub fn profiles(&self) -> impl Iterator<Item = &Profile>
pub fn profiles(&self) -> impl Iterator<Item = &Profile>
Returns an iterator over profiles with valid configurations in this figment. Note: this may not include the selected profile if the selected profile has no configured values.
Example
use figment::{Figment, providers::Serialized};
let figment = Figment::new();
let profiles = figment.profiles().collect::<Vec<_>>();
assert_eq!(profiles.len(), 0);
let figment = Figment::new()
.join(Serialized::default("key", "hi"))
.join(Serialized::default("key", "hey").profile("debug"));
let mut profiles = figment.profiles().collect::<Vec<_>>();
profiles.sort();
assert_eq!(profiles, &["debug", "default"]);
let figment = Figment::new()
.join(Serialized::default("key", "hi").profile("release"))
.join(Serialized::default("key", "hi").profile("testing"))
.join(Serialized::default("key", "hey").profile("staging"))
.select("debug");
let mut profiles = figment.profiles().collect::<Vec<_>>();
profiles.sort();
assert_eq!(profiles, &["release", "staging", "testing"]);
sourcepub fn find_value(&self, key: &str) -> Result<Value>
pub fn find_value(&self, key: &str) -> Result<Value>
Finds the value at key
path in the combined value. See
Value::find()
for details on the syntax for key
.
Example
use serde::Deserialize;
use figment::{Figment, providers::{Format, Toml, Json, Env}};
figment::Jail::expect_with(|jail| {
jail.create_file("Config.toml", r#"
name = "test"
[package]
name = "my-package"
"#)?;
jail.create_file("Config.json", r#"
{
"author": { "name": "Bob" }
}
"#)?;
let figment = Figment::new()
.merge(Toml::file("Config.toml"))
.join(Json::file("Config.json"));
let name = figment.find_value("name")?;
assert_eq!(name.as_str(), Some("test"));
let package_name = figment.find_value("package.name")?;
assert_eq!(package_name.as_str(), Some("my-package"));
let author_name = figment.find_value("author.name")?;
assert_eq!(author_name.as_str(), Some("Bob"));
Ok(())
});
sourcepub fn find_metadata(&self, key: &str) -> Option<&Metadata>
pub fn find_metadata(&self, key: &str) -> Option<&Metadata>
Finds the metadata for the value at key
path. See Value::find()
for details on the syntax for key
.
Example
use serde::Deserialize;
use figment::{Figment, providers::{Format, Toml, Json, Env}};
figment::Jail::expect_with(|jail| {
jail.create_file("Config.toml", r#" name = "test" "#)?;
jail.set_env("CONF_AUTHOR", "Bob");
let figment = Figment::new()
.merge(Toml::file("Config.toml"))
.join(Env::prefixed("CONF_").only(&["author"]));
let name_md = figment.find_metadata("name").unwrap();
assert!(name_md.name.starts_with("TOML"));
let author_md = figment.find_metadata("author").unwrap();
assert!(author_md.name.contains("CONF_"));
assert!(author_md.name.contains("environment"));
Ok(())
});
sourcepub fn get_metadata(&self, tag: Tag) -> Option<&Metadata>
pub fn get_metadata(&self, tag: Tag) -> Option<&Metadata>
Returns the metadata with the given tag
if this figment contains a
value with said metadata.
Example
use serde::Deserialize;
use figment::{Figment, providers::{Format, Toml, Json, Env}};
figment::Jail::expect_with(|jail| {
jail.create_file("Config.toml", r#" name = "test" "#)?;
jail.create_file("Config.json", r#" { "author": "Bob" } "#)?;
let figment = Figment::new()
.merge(Toml::file("Config.toml"))
.join(Json::file("Config.json"));
let name = figment.find_value("name").unwrap();
let metadata = figment.get_metadata(name.tag()).unwrap();
assert!(metadata.name.starts_with("TOML"));
let author = figment.find_value("author").unwrap();
let metadata = figment.get_metadata(author.tag()).unwrap();
assert!(metadata.name.starts_with("JSON"));
Ok(())
});
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for Figment
impl Send for Figment
impl Sync for Figment
impl Unpin for Figment
impl !UnwindSafe for Figment
Blanket Implementations§
source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere T: ?Sized,
source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Paint for Twhere
T: ?Sized,
impl<T> Paint for Twhere T: ?Sized,
§fn fg(&self, value: Color) -> Painted<&T>
fn fg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self
with the foreground set to
value
.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like red()
and
green()
, which have the same functionality but are
pithier.
Example
Set foreground color to white using fg()
:
use yansi::{Paint, Color};
painted.fg(Color::White);
Set foreground color to white using white()
.
use yansi::Paint;
painted.white();
§fn bright_black(&self) -> Painted<&T>
fn bright_black(&self) -> Painted<&T>
§fn bright_red(&self) -> Painted<&T>
fn bright_red(&self) -> Painted<&T>
§fn bright_green(&self) -> Painted<&T>
fn bright_green(&self) -> Painted<&T>
§fn bright_yellow(&self) -> Painted<&T>
fn bright_yellow(&self) -> Painted<&T>
§fn bright_blue(&self) -> Painted<&T>
fn bright_blue(&self) -> Painted<&T>
§fn bright_magenta(&self) -> Painted<&T>
fn bright_magenta(&self) -> Painted<&T>
§fn bright_cyan(&self) -> Painted<&T>
fn bright_cyan(&self) -> Painted<&T>
§fn bright_white(&self) -> Painted<&T>
fn bright_white(&self) -> Painted<&T>
§fn bg(&self, value: Color) -> Painted<&T>
fn bg(&self, value: Color) -> Painted<&T>
Returns a styled value derived from self
with the background set to
value
.
This method should be used rarely. Instead, prefer to use color-specific
builder methods like on_red()
and
on_green()
, which have the same functionality but
are pithier.
Example
Set background color to red using fg()
:
use yansi::{Paint, Color};
painted.bg(Color::Red);
Set background color to red using on_red()
.
use yansi::Paint;
painted.on_red();
§fn on_primary(&self) -> Painted<&T>
fn on_primary(&self) -> Painted<&T>
§fn on_magenta(&self) -> Painted<&T>
fn on_magenta(&self) -> Painted<&T>
§fn on_bright_black(&self) -> Painted<&T>
fn on_bright_black(&self) -> Painted<&T>
§fn on_bright_red(&self) -> Painted<&T>
fn on_bright_red(&self) -> Painted<&T>
§fn on_bright_green(&self) -> Painted<&T>
fn on_bright_green(&self) -> Painted<&T>
§fn on_bright_yellow(&self) -> Painted<&T>
fn on_bright_yellow(&self) -> Painted<&T>
§fn on_bright_blue(&self) -> Painted<&T>
fn on_bright_blue(&self) -> Painted<&T>
§fn on_bright_magenta(&self) -> Painted<&T>
fn on_bright_magenta(&self) -> Painted<&T>
§fn on_bright_cyan(&self) -> Painted<&T>
fn on_bright_cyan(&self) -> Painted<&T>
§fn on_bright_white(&self) -> Painted<&T>
fn on_bright_white(&self) -> Painted<&T>
§fn attr(&self, value: Attribute) -> Painted<&T>
fn attr(&self, value: Attribute) -> Painted<&T>
Enables the styling [Attribute
] value
.
This method should be used rarely. Instead, prefer to use
attribute-specific builder methods like bold()
and
underline()
, which have the same functionality
but are pithier.
Example
Make text bold using attr()
:
use yansi::{Paint, Attribute};
painted.attr(Attribute::Bold);
Make text bold using using bold()
.
use yansi::Paint;
painted.bold();
§fn rapid_blink(&self) -> Painted<&T>
fn rapid_blink(&self) -> Painted<&T>
§fn quirk(&self, value: Quirk) -> Painted<&T>
fn quirk(&self, value: Quirk) -> Painted<&T>
Enables the yansi
[Quirk
] value
.
This method should be used rarely. Instead, prefer to use quirk-specific
builder methods like mask()
and
wrap()
, which have the same functionality but are
pithier.
Example
Enable wrapping using .quirk()
:
use yansi::{Paint, Quirk};
painted.quirk(Quirk::Wrap);
Enable wrapping using wrap()
.
use yansi::Paint;
painted.wrap();
§fn whenever(&self, value: Condition) -> Painted<&T>
fn whenever(&self, value: Condition) -> Painted<&T>
Conditionally enable styling based on whether the [Condition
] value
applies. Replaces any previous condition.
See the crate level docs for more details.
Example
Enable styling painted
only when both stdout
and stderr
are TTYs:
use yansi::{Paint, Condition};
painted.red().on_yellow().whenever(Condition::STDOUTERR_ARE_TTY);