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

Runtime Loading

English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands

Runtime loading is intentionally split between Figment and confique:

figment:
  runtime file loading
  runtime environment loading
  runtime source metadata

confique:
  schema metadata
  defaults
  validation
  config templates

The main API is:

#![allow(unused)]
fn main() {
use rust_config_tree::load_config;

let config = load_config::<AppConfig>("config.yaml")?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

Use load_config_with_figment when the application needs source metadata:

#![allow(unused)]
fn main() {
use rust_config_tree::load_config_with_figment;

let (config, figment) = load_config_with_figment::<AppConfig>("config.yaml")?;
let _ = (config, figment);
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

Loading Steps

The high-level loader performs these steps:

  1. Resolve the root config path lexically.
  2. Load the first .env file found by walking upward from the root config directory.
  3. Load each config file as a partial layer to discover includes.
  4. Build a Figment graph from the discovered config files.
  5. Merge the ConfiqueEnvProvider with higher priority than files.
  6. Optionally merge application-specific CLI overrides.
  7. Extract a confique layer from Figment.
  8. Apply confique code defaults.
  9. Validate and construct the final schema.

load_config and load_config_with_figment perform steps 1-5 and 7-9. Step 6 is application-specific because this crate cannot infer how a CLI flag maps to a schema field.

Transparent Array Section Adaptation

When the schema marks a section with x-tree-transparent-array, the loader adapts YAML shapes after the Figment merge and before confique deserialization:

  1. Body-only split files (for example children.yaml containing [...]) merge into children: { items: [...] }.
  2. Single-file children: [...] normalizes to the same inner shape.
  3. When the section is omitted entirely at runtime, the library injects { items: [] } so template defaults do not leak in as phantom entries.

Applications call load_config directly and do not need post-normalize logic. See Transparent Array Sections for details.

File Formats

The runtime file provider is selected from the config path extension:

  • .yaml and .yml use YAML.
  • .toml uses TOML.
  • .json and .json5 use JSON.
  • unknown or missing extensions use YAML.

Template generation still uses confique’s template renderers for YAML, TOML, and JSON5-compatible output.

Include Priority

The high-level loader merges file providers so included files are lower priority than the file that included them. The root config file has the highest file priority.

Environment variables have higher priority than all config files. confique defaults are only used for values that are not supplied by runtime providers.

When CLI overrides are merged after build_config_figment, the full precedence is:

command-line overrides
  > environment variables
    > config files
      > confique code defaults

The command-line syntax is not defined by rust-config-tree. A flag like --server-port can override server.port if the application maps that parsed value into a nested serialized provider. A dotted --server.port or a.b.c syntax only exists if the application implements it.

This means CLI precedence applies only to keys present in the application’s override provider. Use it for operational values that are frequently changed for a single run. Leave durable configuration in files.

Use the ConfigOverrides derive macro to build an override provider from parsed CLI flags:

#![allow(unused)]
fn main() {
use clap::Parser;
use rust_config_tree::{
    ConfigSchema,
    cli::ConfigOverrides,
    config::{build_config_figment, load_config_from_figment},
};

#[derive(Debug, Parser, ConfigOverrides)]
struct Cli {
    /// Config file path
    #[arg(long)]
    config: Option<std::path::PathBuf>,

    /// Override server port
    #[arg(long)]
    #[config_override(path = "server.port")]
    server_port: Option<u16>,

    /// Override log level
    #[arg(long)]
    #[config_override(path = "log.level")]
    log_level: Option<String>,
}

let cli = Cli::parse();
let figment = build_config_figment::<AppConfig>("config.yaml")?
    .merge(cli.config_overrides()?);
let config = load_config_from_figment::<AppConfig>(&figment)?;
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

The #[config_override(path = "...")] attribute maps each CLI flag to a dotted config path. Only provided flags produce override values; omitted flags disappear.