Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Merge multiple providers with figment

figment serde Config

figment assembles configuration from independent providers. A provider can be a TOML, JSON, or YAML file, or a set of environment variables. Figment merges them into a typed value. It is the configuration library behind Rocket, and its Jail makes configuration code testable without touching the real filesystem or environment.

A Figment stacks providers with merge: later providers override keys set by earlier ones. Here Toml::file reads the base settings and Env::prefixed layers APP_-prefixed environment variables on top, so APP_PORT=9000 overrides the file’s port. extract deserializes the merged result into the Config struct.

This recipe requires the env, test and toml feature flags of figment and the derive feature flag of serde to be enabled in Cargo.toml.

use figment::Figment;
use figment::providers::{Env, Format, Toml};
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct Config {
    name: String,
    port: u16,
    workers: usize,
}

fn figment() -> Figment {
    Figment::new()
        .merge(Toml::file("App.toml"))
        .merge(Env::prefixed("APP_"))
}

fn main() -> Result<(), figment::Error> {
    let config: Config = figment().extract()?;
    println!(
        "{} listening on port {} with {} workers",
        config.name, config.port, config.workers
    );
    Ok(())
}

Configuration code is awkward to test when it reads real files and environment variables. Jail::expect_with runs a closure in a temporary directory with a sandboxed environment, so a test can create an App.toml and set variables that disappear when the closure returns:

    Jail::expect_with(|jail| {
        jail.create_file(
            "App.toml",
            r#"
                name = "api"
                port = 8000
                workers = 4
            "#,
        )?;
        jail.set_env("APP_PORT", 9000);

        let config: Config = figment().extract()?;
        assert_eq!(config.name, "api");
        assert_eq!(config.port, 9000);
        assert_eq!(config.workers, 4);
        Ok(())
    });