Figment

repository·master·Indexed 21 days ago

https://github.com/sergiobenitez/figment

A semi-hierarchical configuration library for Rust that allows developers to compose configuration from multiple sources, including TOML, JSON, YAML, and environment variables. It supports merging and joining providers, profile-based configuration management, and deserialization into typed structures via serde.

Tokens
12.2K
Snippets
37
Records
50
Agent score
74%

What's inside figment

  1. Install Figment via Cargo

    master

    To use Figment in your Rust project, add it to your Cargo.toml dependencies. You must enable specific features for the providers you intend to use (e.g., toml for TOML file support).

    [dependencies]
    figment = { version = "0.10", features = ["toml"] }
  2. What is Metadata and how is it used?

    master

    In Figment, every Value produced is tagged with Metadata. Metadata provides context about where a configuration value originated. It includes:

    • A name for the source (e.g., "TOML File").
    • The Source itself (the specific file path, code location, or custom string).
    • An interpolater, which is a function used to transform a Figment key path (like a.b.c) into a source-native path (like ENV_VAR_A_B_C).
    • A provide_location, which tracks where the provider was added to the Figment instance.

    Metadata is primarily used to generate insightful error messages and to support types like RelativePathBuf that need to know their configuration source.

  3. How to use Metadata interpolation

    master

    Interpolation allows you to convert a standard Figment key path into a format specific to your configuration source. For example, an environment variable provider might want to uppercase all keys.

    You can define a custom interpolater using the .interpolater() method on a Metadata instance. The interpolater function receives the Profile and the slice of keys as arguments.

    use figment::Metadata;
    
    let metadata = Metadata::named("environment variable(s)")
        .interpolater(|profile, path| {
            let keys: Vec<_> = path.iter()
                .map(|k| k.to_ascii_uppercase())
                .collect();
    
            format!("{}.{}", profile, keys.join("."))
        });
    
    let profile = figment::Profile::Default;
    let interpolated = metadata.interpolate(&profile, &["key", "path"]);
    // Result depends on profile implementation, e.g., "Default.KEY.PATH"
  4. Use Interpreters to control value conversion during deserialization

    master

    Figment provides an Interpreter trait that allows you to control how Value types are converted into Rust types during the deserialization process. This is useful for implementing custom logic for boolean or numeric conversions.

    There are two built-in implementations:

    • DefaultInterpreter: Performs standard, strict conversions.
    • LossyInterpreter: Attempts to perform 'lossy' conversions. For example, it can attempt to convert a string or other type into a boolean or number using lossy logic (to_bool_lossy or to_num_lossy). If the conversion fails, it falls back to the original value.

    You can implement your own Interpreter to define custom behavior for interpret_as_bool and interpret_as_num.

    use crate::value::{Interpreter, DefaultInterpreter, LossyInterpreter};
    
    // Use the default behavior
    let interpreter = DefaultInterpreter;
    
    // Use lossy conversion behavior
    let interpreter = LossyInterpreter;
  5. Use Profiles to manage different configuration environments

    master

    Figment uses Profiles to allow different sets of values to coexist within the same source. By default, Figment uses the Default profile. You can switch profiles using .select(name) during extraction.

    Built-in Profiles

    • Default: The fallback profile used if no other profile is selected.
    • Global: A special profile whose values supersede all other profiles (except when a subsequent provider is merged that contains its own global values).

    Nested Providers

    Some providers (like Toml or Json) can be configured as .nested(). This treats top-level keys in the source file as profile names (e.g., a [debug] section in TOML becomes the debug profile).

    use figment::{Figment, providers::Toml};
    
    // Using .nested() makes top-level keys act as profiles
    let figment = Figment::new()
        .merge(Toml::file("Base.toml").nested());
    
    // Extract using the 'debug' profile
    let config = figment.select("debug").extract::<Config>()?;
  6. Use the Serialized provider to source data from types

    master

    The Serialized<T> provider allows you to source configuration values directly from any type T that implements serde::Serialize.

    Unkeyed Data

    If you do not provide a key, the serialized value T is expected to be a dictionary (map). It will be emitted directly at the root of the configured profile.

    Keyed Data

    If you provide a key (e.g., using .key("a.b.c")), the serialized value T can be any valid Value. Figment will automatically create nested dictionaries for every path component delimited by . in the key string. For example, a key of a.b.c results in a structure like { "a": { "b": { "c": T }}}.

    Profiles

    By default, data is emitted to Profile::Default. You can change this using .profile(P) where P implements Into<Profile>.

    use figment::{Figment, providers::Serialized, util::map};
    use serde::Deserialize;
    
    #[derive(Debug, PartialEq, Deserialize)]
    struct Config {
        numbers: Vec<usize>,
    }
    
    // Example: Unkeyed provider (expects a map/dict)
    let map = map!["numbers" => &[1, 2, 3]];
    let figment = Figment::from(Serialized::from(&map, "default"));
    let config: Config = figment.extract().unwrap();
    assert_eq!(config, Config { numbers: vec![1, 2, 3] });
  7. The Value enum: Core configuration data type

    master

    The Value enum is the central representation of all possible configuration data in Figment. It supports various types including strings, characters, booleans, numbers, empty values, dictionaries (maps), and arrays (sequences). Each variant carries a Tag which provides metadata about the value's origin or profile.

    Value implements From<T> for most standard Rust types, allowing for easy creation of configuration values.

    use figment::value::Value;
    
    let v = Value::from("hello");
    assert_eq!(v.as_str(), Some("hello"));
  8. How the Provider trait works

    master

    A Provider is an abstraction that allows Figment to consume configuration from diverse sources (files, environment variables, network, etc.) through a unified interface.

    Relationship with Figment

    • Merging: When a provider is merged into a Figment, its data is combined with existing data. If the provider implements profile(), that profile is applied to the Figment during the merge.
    • Metadata & Interpolation: Providers use Metadata to tell Figment how to represent the source. A key feature is the interpolater, which allows a provider to translate logical key paths (like database.host) into physical paths (like config/database/host.toml or https://api.com/database/host).

    Data Structure

    All providers must return data in the format Map<Profile, Dict>. This structure allows Figment to handle multiple configuration profiles (e.g., default, development, production) within a single provider's output.

  9. How Data providers work with formats

    master

    A Data<F> provider sources configuration values from a file or a string using a specific format F (where F implements the Format trait).

    Construction

    Instead of constructing Data directly, you typically use the static methods provided by a format type (like Json, Toml, or Yaml). These methods return a Data<F> instance.

    Unnested vs Nested Data

    • Unnested (Default): The parsed content is emitted into a single profile (defaults to Profile::Default).
    • Nested: By calling .nested(), the top-level keys of the source data are treated as profiles. This allows you to use Figment::select("profile_name") to extract specific sections of the configuration.

    Metadata and Profiles

    • Metadata: A file provider is named [FORMAT_NAME] file and includes the resolved file path. A string provider is named [FORMAT_NAME] source string.
    // The `Format` trait must be in-scope to use its methods.
    use figment::providers::{Format, Data, Json};
    
    // These two are equivalent, except the former requires the explicit type.
    let json = Data::<Json>::file("foo.json");
    let json = Json::file("foo.json");
  10. Manage configuration layers with `Profile`

    master

    A Profile is a case-insensitive string used to identify and organize configuration layers. Figment uses profiles to distinguish between different configuration contexts (e.g., default, global, or custom profiles like staging).

    Profiles are case-insensitive, meaning Profile::new("staging") is equal to Profile::new("STAGING").

    Key characteristics:

    • Default Profile: Profile::Default (the string "default").
    • Global Profile: Profile::Global (the string "global").
    • Custom Profiles: Any profile that is neither Default nor Global is considered custom (checked via .is_custom()).
    • Case-Insensitivity: Comparisons and prefix checks are performed without regard to case.
    use figment::Profile;
    
    let profile = Profile::new("staging");
    assert_eq!(profile, "staging");
    assert_eq!(profile, "STAGING");
    
    // Check if it is a custom profile
    assert!(profile.is_custom());
    
    // Built-in profiles
    assert!(!Profile::Default.is_custom());
    assert!(!Profile::Global.is_custom());
  11. How Figment works: Providers and Figments

    master

    Figment is a configuration library based on two core concepts:

    1. Providers: Types that implement the Provider trait, representing a single configuration source (e.g., a TOML file, environment variables, or a JSON object).
    2. Figments: The Figment type, which acts as a container that combines multiple Providers using merge() or join(). A Figment is itself a Provider.

    Combining Sources

    • merge(provider): Values from the new provider replace values from previous providers if keys overlap.
    • join(provider): Values from the new provider are added only if the key does not already exist (no replacement occurs).

    Sources are read eagerly; they are processed immediately upon being merged or joined into a Figment.

    use figment::{Figment, providers::{Toml, Env, Json}};
    
    let figment = Figment::new()
        .merge(Toml::file("App.toml")) // Overwrites subsequent merges if keys match
        .merge(Env::prefixed("APP_")) // Overwrites if keys match
        .join(Json::file("App.json")); // Only fills holes; does not overwrite
  12. Understand the `Tag` type for configuration metadata

    master

    A Tag is an opaque, unique identifier used to associate a configuration value with its Metadata and its Profile.

    Key characteristics:

    • Retrieval: You can obtain a Tag via the Tagged wrapper or by calling Value::tag().
    • Metadata Access: Once you have a Tag, you can retrieve the corresponding metadata using Figment::get_metadata().
    • Profile Access: You can determine which profile a value belongs to by calling Tag::profile().
    • Default Tag: Tag::Default represents a tag that has no associated metadata and is associated with the Default profile.
    use figment::value::Tag;
    
    // Check if a tag is the default tag
    assert!(Tag::Default.is_default());
    
    // Check the profile associated with a tag
    // Returns Some(Profile::Default) for a default tag
    assert_eq!(Tag::Default.profile(), Some(figment::Profile::Default));