Struct Directory

Source
pub struct Directory<F> { /* private fields */ }
Expand description

A Provider that sources values from a (possibly nested) directory of files in a given Format.

§Constructing

A Directory provider is typically constructed indirectly via a type that implements the Format trait via the FormatExt::directory() method which in-turn defers Directory::new() by default:

// The `FormatExt` trait must be in-scope to use its methods.
use figment::providers::{Format, Toml};
use figment_directory::{Directory, FormatExt as _};

// These two are equivalent, except the former requires the explicit type.
let json_directory = Directory::<Toml>::new("foo");
let json_directory = Toml::directory("foo");

§Provider Details

  • Profile

    This provider does not set a profile.

  • Metadata

    This provider is named ${NAME} directory, where ${NAME} is Format::NAME. The directories file’s paths are specified as file Source. Path interpolation is unchanged from the default.

  • Data (Unnested, default)

    When nesting is not specified, the source files in the given directory are read and parsed, and the parsed dictionary is emitted into the profile configurable via Directory::profile(), which defaults to Profile::Default. If the source dictionary is not present an empty dictionary is emitted.

  • Data (Nested)

    When nesting is specified, the directory is expected to contain files and or subdirectories named after your profiles. These subdirectories and files are parsed and emitted into the corresponding profiles.

    /root /root/default.toml | /root/default/foo.toml |– these get put into the “default” profile /root/default/bar.toml |

    /root/development.toml | /root/development/foo.toml | – these get put into the “development” profile

  • Conflict Resolution Per default, values in files that are higher up in the directory tree override values in deeply nested files. As an example, take these two files:

    # /root/a.toml
    [b]
    c = 1
    # /root/a/b.toml
    c = 2

    The provider will prefer the value in a.toml, since it is higher up than a/b.toml. Therefore, c = 1 will “win”.

    This strategy corresponds to the “Join” strategy in the figment docs on conflict resolution. The behaviour can be changed by using the methods on Directory corresponding to the available strategies: Directory::merge, Directory::adjoin, Directory::admerge and Directory::join (if you like to be explicit).

Implementations§

Source§

impl<F> Directory<F>

Source

pub fn new<P: Into<PathBuf>>(path: P) -> Self

Source

pub fn nested(self) -> Self

Enables nesting on self, which results in top-level keys of the sourced data being treated as profiles.

use serde::Deserialize;
use figment::{Figment, Jail, providers::{Format, Toml}, value::Map};
use figment_directory::FormatExt as _;

#[derive(Debug, PartialEq, Deserialize)]
struct Config {
    numbers: Vec<usize>,
    untyped: Map<String, usize>,
}

Jail::expect_with(|jail| {
    jail.create_dir("cfg")?;
    jail.create_file("cfg/default.toml", r#"
        [untyped]
        global = 0
        hi = 7
    "#)?;
    jail.create_file("cfg/staging.toml", r#"
        numbers = [1, 2, 3]
    "#)?;
    jail.create_file("cfg/release.toml", r#"
        numbers = [6, 7, 8]
    "#)?;

    // Enable nesting via `nested()`.
    let figment = Figment::from(Toml::directory("cfg").nested());

    let figment = figment.select("staging");
    let config: Config = figment.extract()?;
    assert_eq!(config, Config {
        numbers: vec![1, 2, 3],
        untyped: figment::util::map!["global".into() => 0, "hi".into() => 7],
    });

    let config: Config = figment.select("release").extract()?;
    assert_eq!(config, Config {
        numbers: vec![6, 7, 8],
        untyped: figment::util::map!["global".into() => 0, "hi".into() => 7],
    });

    Ok(())
});
Source

pub fn profile<P: Into<Profile>>(self, profile: P) -> Self

Set the profile to emit data to when nesting is disabled.

use serde::Deserialize;
use figment::{Figment, Jail, providers::{Format, Toml}, value::Map, Profile};
use figment_directory::FormatExt as _;

#[derive(Debug, PartialEq, Deserialize)]
struct Config { nested: NestedConfig }

#[derive(Debug, PartialEq, Deserialize)]
struct NestedConfig { value: u8 }

Jail::expect_with(|jail| {
    jail.create_dir("cfg")?;
    jail.create_file("cfg/nested.toml", r#"
        value = 123
    "#);
    let provider = Toml::directory("cfg").profile("debug");
    let figment = Figment::from(provider).select("debug");
    let config: Config = figment.extract()?;
    assert_eq!(config.nested, NestedConfig { value: 123 });
    let result: Result<Config, _> = figment.select(Profile::Default).extract();
    assert!(result.is_err(), "extract() should have errored but there was a value in the default profile");

    Ok(())
});
Source

pub fn merge(self) -> Self

Set the conflict resolution strategy to

  • prefer values in files that are lower down in the directory tree
  • override conflicting arrays instead of appending
use serde::Deserialize;
use figment::{Figment, Jail, providers::{Format, Toml}, value::Map};
use figment_directory::FormatExt as _;

#[derive(Debug, PartialEq, Deserialize)]
struct Config {
    numbers: Vec<usize>,
    untyped: Map<String, usize>,
}

#[derive(Debug, PartialEq, Deserialize)]
struct NestLevelOne {
    one: Config,
}

#[derive(Debug, PartialEq, Deserialize)]
struct NestLevelTwo {
    two: NestLevelOne,
}

Jail::expect_with(|jail| {
    jail.create_dir("cfg")?;
    jail.create_file("cfg/two.toml", r#"
        [one.untyped]
        global = 0
        hi = 7
         
        [one]
        numbers = [1, 2, 3]
    "#)?;
    jail.create_dir("cfg/two")?;
    jail.create_file("cfg/two/one.toml", r#"
        numbers = [6, 7, 8]

        [untyped]
        hi = 8
        foo = 42
    "#)?;

    // Set conflict resolution strategy via `merge()`.
    let figment = Figment::from(Toml::directory("cfg").merge());

    let config: NestLevelTwo = figment.extract()?;
    assert_eq!(config.two.one, Config {
        numbers: vec![6, 7, 8],
        untyped: figment::util::map!["global".into() => 0, "hi".into() => 8, "foo".into() => 42],
    });

    Ok(())
});
Source

pub fn join(self) -> Self

Set the conflict resolution strategy to

  • prefer values in files that are higher up in the directory tree
  • override conflicting arrays instead of appending
use serde::Deserialize;
use figment::{Figment, Jail, providers::{Format, Toml}, value::Map};
use figment_directory::FormatExt as _;

#[derive(Debug, PartialEq, Deserialize)]
struct Config {
    numbers: Vec<usize>,
    untyped: Map<String, usize>,
}

#[derive(Debug, PartialEq, Deserialize)]
struct NestLevelOne {
    one: Config,
}

#[derive(Debug, PartialEq, Deserialize)]
struct NestLevelTwo {
    two: NestLevelOne,
}

Jail::expect_with(|jail| {
    jail.create_dir("cfg")?;
    jail.create_file("cfg/two.toml", r#"
        [one.untyped]
        global = 0
        hi = 7
         
        [one]
        numbers = [1, 2, 3]
    "#)?;
    jail.create_dir("cfg/two")?;
    jail.create_file("cfg/two/one.toml", r#"
        numbers = [6, 7, 8]

        [untyped]
        hi = 8
        foo = 42
    "#)?;

    // Set conflict resolution strategy via `join()`.
    let figment = Figment::from(Toml::directory("cfg").join());

    let config: NestLevelTwo = figment.extract()?;
    assert_eq!(config.two.one, Config {
        numbers: vec![1, 2, 3],
        untyped: figment::util::map!["global".into() => 0, "hi".into() => 7, "foo".into() => 42],
    });

    Ok(())
});
Source

pub fn admerge(self) -> Self

Set the conflict resolution strategy to

  • prefer values in files that are lower down in the directory tree
  • append conflicting arrays instead of overriding
use serde::Deserialize;
use figment::{Figment, Jail, providers::{Format, Toml}, value::Map};
use figment_directory::FormatExt as _;

#[derive(Debug, PartialEq, Deserialize)]
struct Config {
    numbers: Vec<usize>,
    untyped: Map<String, usize>,
}

#[derive(Debug, PartialEq, Deserialize)]
struct NestLevelOne {
    one: Config,
}

#[derive(Debug, PartialEq, Deserialize)]
struct NestLevelTwo {
    two: NestLevelOne,
}

Jail::expect_with(|jail| {
    jail.create_dir("cfg")?;
    jail.create_file("cfg/two.toml", r#"
        [one.untyped]
        global = 0
        hi = 7
         
        [one]
        numbers = [1, 2, 3]
    "#)?;
    jail.create_dir("cfg/two")?;
    jail.create_file("cfg/two/one.toml", r#"
        numbers = [6, 7, 8]

        [untyped]
        hi = 8
        foo = 42
    "#)?;

    // Set conflict resolution strategy via `admerge()`.
    let figment = Figment::from(Toml::directory("cfg").admerge());

    let config: NestLevelTwo = figment.extract()?;
    assert_eq!(config.two.one, Config {
        numbers: vec![1, 2, 3, 6, 7, 8],
        untyped: figment::util::map!["global".into() => 0, "hi".into() => 8, "foo".into() => 42],
    });

    Ok(())
});
Source

pub fn adjoin(self) -> Self

Set the conflict resolution strategy to

  • prefer values in files that are higher up in the directory tree
  • append conflicting arrays instead of overriding
use serde::Deserialize;
use figment::{Figment, Jail, providers::{Format, Toml}, value::Map};
use figment_directory::FormatExt as _;

#[derive(Debug, PartialEq, Deserialize)]
struct Config {
    numbers: Vec<usize>,
    untyped: Map<String, usize>,
}

#[derive(Debug, PartialEq, Deserialize)]
struct NestLevelOne {
    one: Config,
}

#[derive(Debug, PartialEq, Deserialize)]
struct NestLevelTwo {
    two: NestLevelOne,
}

Jail::expect_with(|jail| {
    jail.create_dir("cfg")?;
    jail.create_file("cfg/two.toml", r#"
        [one.untyped]
        global = 0
        hi = 7
         
        [one]
        numbers = [1, 2, 3]
    "#)?;
    jail.create_dir("cfg/two")?;
    jail.create_file("cfg/two/one.toml", r#"
        numbers = [6, 7, 8]

        [untyped]
        hi = 8
        foo = 42
    "#)?;

    // Set conflict resolution strategy via `adjoin()`.
    let figment = Figment::from(Toml::directory("cfg").adjoin());

    let config: NestLevelTwo = figment.extract()?;
    assert_eq!(config.two.one, Config {
        numbers: vec![1, 2, 3, 6, 7, 8],
        untyped: figment::util::map!["global".into() => 0, "hi".into() => 7, "foo".into() => 42],
    });

    Ok(())
});

Trait Implementations§

Source§

impl<F> Provider for Directory<F>
where F: Format,

Source§

fn metadata(&self) -> Metadata

Returns the Metadata for this provider, identifying itself and its configuration sources.
Source§

fn data(&self) -> Result<Map<Profile, Dict>, Error>

Returns the configuration data.
Source§

fn profile(&self) -> Option<Profile>

Optionally returns a profile to set on the Figment this provider is merged into. The profile is only set if self is merged.

Auto Trait Implementations§

§

impl<F> Freeze for Directory<F>

§

impl<F> RefUnwindSafe for Directory<F>
where F: RefUnwindSafe,

§

impl<F> Send for Directory<F>
where F: Send,

§

impl<F> Sync for Directory<F>
where F: Sync,

§

impl<F> Unpin for Directory<F>
where F: Unpin,

§

impl<F> UnwindSafe for Directory<F>
where F: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.