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:
- Resolve the root config path lexically.
- Load the first
.envfile found by walking upward from the root config directory. - Load each config file as a partial layer to discover includes.
- Build a Figment graph from the discovered config files.
- Merge the
ConfiqueEnvProviderwith higher priority than files. - Optionally merge application-specific CLI overrides.
- Extract a
confiquelayer from Figment. - Apply
confiquecode defaults. - 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:
- Body-only split files (for example
children.yamlcontaining[...]) merge intochildren: { items: [...] }. - Single-file
children: [...]normalizes to the same inner shape. - 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:
.yamland.ymluse YAML..tomluses TOML..jsonand.json5use 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.