rust-config-tree Manual
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
This is the English manual for rust-config-tree.
Start with Introduction, Quick Start, or the runnable Examples.
Introduction
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
rust-config-tree provides reusable configuration-tree loading and CLI helpers
for Rust applications that use layered config files.
The crate is designed around a small division of responsibilities:
confiqueowns schema definitions, code defaults, validation, and config template generation.figmentowns runtime loading and runtime source metadata.rust-config-treeowns recursive include traversal, include path resolution,.envloading, template target discovery, and reusable clap commands.
The crate is useful when an application wants a natural config file layout such as this:
include:
- config/server.yaml
- config/database.yaml
log:
level: info
Each included file can use the same schema shape, and relative include paths are
resolved from the file that declared them. The final config is still a normal
confique schema value.
Main Features
- Recursive include traversal with cycle detection.
- Relative include paths resolved from the declaring file.
.envloading before environment providers are evaluated.- Schema-declared environment variables without delimiter splitting.
- Figment metadata for runtime source tracking.
- TRACE-level source tracking events through
tracing. - Draft 7 JSON Schema generation for editor completion and basic schema checks.
- Field value validation in application code through
#[config(validate = Self::validate)], executed byload_configorconfig-validate. - YAML, TOML, JSON, and JSON5 template generation.
- TOML
#:schema, YAML Language Server schema modelines, and JSON/JSON5$schemafields for generated templates. - Opt-in YAML template splitting for nested sections marked with
x-tree-split. - Built-in clap subcommands for config templates, JSON Schema, and shell completions.
- A lower-level tree API for callers that do not use
confique.
Public Entry Points
Use these APIs for most applications:
load_config::<S>(path)loads the final schema.load_config_with_figment::<S>(path)loads the schema and returns the Figment graph used for source tracking.write_config_templates::<S>(config_path, output_path)writes the root template and recursively discovered child templates.write_config_schemas::<S>(output_path)writes root and section Draft 7 JSON Schemas.handle_config_command::<Cli, S>(command, config_path)handles built-in clap config commands.
Use load_config_tree when you need the traversal primitive without
confique.
Quick Start
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
Add the crate and the schema/runtime libraries used by your application:
[dependencies]
rust-config-tree = "0.1"
confique = { version = "0.4", features = ["yaml", "toml", "json5"] }
figment = { version = "0.10", features = ["yaml", "toml", "json", "env"] }
schemars = { version = "1", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
clap = { version = "4", features = ["derive"] }
Define a confique schema and implement ConfigSchema for the root type:
#![allow(unused)]
fn main() {
use std::path::PathBuf;
use confique::Config;
use rust_config_tree::ConfigSchema;
#[derive(Debug, Config)]
struct AppConfig {
#[config(default = [])]
include: Vec<PathBuf>,
#[config(nested)]
server: ServerConfig,
}
#[derive(Debug, Config)]
struct ServerConfig {
#[config(default = "127.0.0.1")]
#[config(env = "APP_SERVER_BIND")]
bind: String,
#[config(default = 8080)]
#[config(env = "APP_SERVER_PORT")]
port: u16,
}
impl ConfigSchema for AppConfig {
fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
layer.include.clone().unwrap_or_default()
}
}
}
Load the config:
#![allow(unused)]
fn main() {
use rust_config_tree::load_config;
let config = load_config::<AppConfig>("config.yaml")?;
println!("{config:#?}");
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
Use a root file with recursive includes:
# config.yaml
include:
- config/server.yaml
# config/server.yaml
server:
bind: 0.0.0.0
port: 3000
The default load_config precedence is:
environment variables
> config files, with later merged files overriding earlier files
> confique code defaults
When includes are loaded by the high-level API, the root file has the highest file priority. Included files provide lower-priority values and can be used for defaults or section-specific files.
Command-line arguments are application-specific, so load_config does not read
them automatically. Merge CLI overrides after build_config_figment when the
application has config override flags:
CLI flag names are chosen by the application. They are not automatically
a.b.c config paths. Prefer normal clap flags such as --server-port, then
map them into a nested override structure. The nested serialized shape controls
the config key that is overridden.
Only values represented in the application’s CliOverrides provider override
configuration. This is useful for parameters that are changed frequently for one
run without editing the config file. Stable values should stay in config files.
#![allow(unused)]
fn main() {
use figment::providers::Serialized;
use serde::Serialize;
use rust_config_tree::{build_config_figment, load_config_from_figment};
#[derive(Debug, Serialize)]
struct CliOverrides {
#[serde(skip_serializing_if = "Option::is_none")]
server: Option<CliServerOverrides>,
}
#[derive(Debug, Serialize)]
struct CliServerOverrides {
#[serde(skip_serializing_if = "Option::is_none")]
port: Option<u16>,
}
let cli_overrides = CliOverrides {
server: Some(CliServerOverrides { port: Some(9000) }),
};
let figment = build_config_figment::<AppConfig>("config.yaml")?
.merge(Serialized::defaults(cli_overrides));
let config = load_config_from_figment::<AppConfig>(&figment)?;
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
With CLI overrides merged this way, the full precedence is:
command-line overrides
> environment variables
> config files
> confique code defaults
Configuration Schema
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
Application schemas are normal confique config types. The root schema must
implement ConfigSchema so rust-config-tree can discover recursive includes
from the intermediate confique layer.
#![allow(unused)]
fn main() {
use std::path::PathBuf;
use confique::Config;
use schemars::JsonSchema;
use rust_config_tree::ConfigSchema;
#[derive(Debug, Config, JsonSchema)]
struct AppConfig {
#[config(default = [])]
include: Vec<PathBuf>,
#[config(nested)]
#[schemars(extend("x-tree-split" = true))]
database: DatabaseConfig,
}
#[derive(Debug, Config, JsonSchema)]
struct DatabaseConfig {
#[config(env = "APP_DATABASE_URL")]
url: String,
#[config(default = 16)]
#[config(env = "APP_DATABASE_POOL_SIZE")]
pool_size: u32,
}
impl ConfigSchema for AppConfig {
fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
layer.include.clone().unwrap_or_default()
}
}
}
Include Field
The include field can have any name. rust-config-tree only knows about it
through ConfigSchema::include_paths.
The field should normally have an empty default:
#![allow(unused)]
fn main() {
#[config(default = [])]
include: Vec<PathBuf>,
}
The loader receives a partially loaded layer for each file. That lets it discover child config files before the final schema is merged and validated.
Nested Sections
Use #[config(nested)] for structured sections. Nested sections are always
used for runtime loading. Add #[schemars(extend("x-tree-split" = true))]
when a nested field should also be generated as an independent
*.yaml template and <section>.schema.json schema:
#![allow(unused)]
fn main() {
#[derive(Debug, Config, JsonSchema)]
struct AppConfig {
#[config(nested)]
#[schemars(extend("x-tree-split" = true))]
server: ServerConfig,
}
}
The natural YAML shape is:
server:
bind: 127.0.0.1
port: 8080
Environment-Only Fields
Mark a leaf field with #[schemars(extend("x-env-only" = true))] when the value must be supplied only by an environment variable and should not appear in generated config files. Generated YAML templates and JSON Schemas omit env-only fields, and empty parent objects left behind by those omissions are pruned.
#![allow(unused)]
fn main() {
#[config(env = "APP_SECRET")]
#[schemars(extend("x-env-only" = true))]
secret: String,
}
Field Value Validation
Generated *.schema.json files are for IDE completion and basic editor checks
only. They do not decide whether a concrete field value is legal for the
application.
Implement field value validation in code with
#[config(validate = Self::validate)]. The validator runs when the final config
is loaded through load_config or checked through config-validate.
Template Section Overrides
When a template source has no includes, the crate can derive child template
files from nested schema sections marked with x-tree-split. The default
top-level path is <section>.yaml relative to the root template directory.
Override that path with template_path_for_section:
#![allow(unused)]
fn main() {
impl ConfigSchema for AppConfig {
fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
layer.include.clone().unwrap_or_default()
}
fn template_path_for_section(section_path: &[&str]) -> Option<PathBuf> {
match section_path {
["database"] => Some(PathBuf::from("examples/database.yaml")),
_ => None,
}
}
}
}
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.
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.
#![allow(unused)]
fn main() {
use figment::providers::Serialized;
use serde::Serialize;
use rust_config_tree::{build_config_figment, load_config_from_figment};
#[derive(Debug, Serialize)]
struct CliOverrides {
#[serde(skip_serializing_if = "Option::is_none")]
server: Option<CliServerOverrides>,
}
#[derive(Debug, Serialize)]
struct CliServerOverrides {
#[serde(skip_serializing_if = "Option::is_none")]
port: Option<u16>,
}
let cli_overrides = CliOverrides {
server: Some(CliServerOverrides { port: Some(9000) }),
};
let figment = build_config_figment::<AppConfig>("config.yaml")?
.merge(Serialized::defaults(cli_overrides));
let config = load_config_from_figment::<AppConfig>(&figment)?;
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
Environment Variables
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
Environment variable names are declared in the schema with confique:
#![allow(unused)]
fn main() {
#[derive(Debug, Config)]
struct DatabaseConfig {
#[config(env = "APP_DATABASE_URL")]
url: String,
#[config(default = 16)]
#[config(env = "APP_DATABASE_POOL_SIZE")]
pool_size: u32,
}
}
rust-config-tree reads those names from confique::Config::META and builds a
Figment provider that maps each environment variable to its exact field path.
Do not use delimiter-based Figment environment mapping for this crate:
#![allow(unused)]
fn main() {
// Do not use this pattern for rust-config-tree schemas.
Env::prefixed("APP_").split("_")
Env::prefixed("APP_").split("__")
}
split("_") treats underscores as nested key separators. That makes
APP_DATABASE_POOL_SIZE become a path like database.pool.size, which conflicts
with Rust field names such as pool_size.
With ConfiqueEnvProvider, this mapping is explicit:
APP_DATABASE_POOL_SIZE -> database.pool_size
Single underscores remain part of the environment variable name. Figment does not guess the nesting rule.
Dotenv Loading
Before runtime providers are evaluated, the loader searches for a .env file by
walking upward from the root config file’s directory.
Existing process environment variables are preserved. Values from .env only
fill missing environment variables.
Example:
APP_SERVER_PORT=9000
APP_DATABASE_POOL_SIZE=64
These variables override config file values when the schema declares matching
#[config(env = "...")] attributes.
Parsing Values
The bridge provider lets Figment parse environment values. It does not call
confique’s parse_env hooks. Keep complex values in config files unless the
Figment environment value syntax is a good fit for the type.
Source Tracking
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
Use load_config_with_figment to keep the Figment graph used by runtime
loading:
#![allow(unused)]
fn main() {
use rust_config_tree::load_config_with_figment;
let (config, figment) = load_config_with_figment::<AppConfig>("config.yaml")?;
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
The returned Figment value can answer source questions for runtime values:
#![allow(unused)]
fn main() {
if let Some(metadata) = figment.find_metadata("database.pool_size") {
let source = metadata.interpolate(
&figment::Profile::Default,
&["database", "pool_size"],
);
println!("database.pool_size came from {source}");
}
}
For values supplied by ConfiqueEnvProvider, interpolation returns the native
environment variable name declared in the schema:
database.pool_size came from APP_DATABASE_POOL_SIZE
TRACE Events
The loader emits source tracking events with tracing::trace!. It does this
only when TRACE is enabled:
#![allow(unused)]
fn main() {
use rust_config_tree::{load_config_with_figment, trace_config_sources};
let (config, figment) = load_config_with_figment::<AppConfig>("config.yaml")?;
// If the tracing subscriber is initialized after config loading, emit the
// same source events again after installing the subscriber.
trace_config_sources::<AppConfig>(&figment);
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
Each event uses the rust_config_tree::config target and includes:
config_key: the dotted config key.source: the rendered source metadata.
Values that came only from confique defaults do not have Figment runtime
metadata. They are reported as confique default or unset optional field.
Template Generation
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
Templates are generated from the same confique schema used at runtime.
confique renders the actual template content, including doc comments,
defaults, required fields, and declared environment variable names.
Use write_config_templates:
#![allow(unused)]
fn main() {
use rust_config_tree::write_config_templates;
write_config_templates::<AppConfig>("config.yaml", "config.example.yaml")?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
Generate Draft 7 JSON Schemas for the root config and split nested sections:
#![allow(unused)]
fn main() {
use rust_config_tree::write_config_schemas;
write_config_schemas::<AppConfig>("schemas/myapp.schema.json")?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
Mark a nested field with #[schemars(extend("x-tree-split" = true))] when it
should be generated as its own *.yaml template and
<section>.schema.json schema. Unmarked nested fields stay in the parent
template and parent schema.
Mark a leaf field with #[schemars(extend("x-env-only" = true))] when the value must come only from environment variables. Generated templates and JSON Schemas omit env-only fields, and empty parent objects left behind by those omissions are pruned.
Generated schemas omit required constraints. IDEs can still offer completion,
but partial files such as log.yaml do not report missing root fields.
The root schema only completes fields that belong in the root file; split
section fields are omitted there and completed by their own section schemas.
Present fields can still receive basic editor checks, such as type, enum, and
unknown property checks supported by the generated schema. Generated
*.schema.json files do not decide whether a concrete field value is legal for
the application. Implement field value validation in code with
#[config(validate = Self::validate)]; load_config and config-validate
execute that runtime validation.
Bind those schemas from generated TOML, YAML, JSON, and JSON5 templates:
#![allow(unused)]
fn main() {
use rust_config_tree::write_config_templates_with_schema;
write_config_templates_with_schema::<AppConfig>(
"config.toml",
"config.example.toml",
"schemas/myapp.schema.json",
)?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
Root templates bind the root schema and do not complete split child section
fields. Split section YAML templates bind their section schema. JSON and JSON5
templates receive a top-level $schema field. Editor settings such as VS Code
json.schemas can still be used as an alternative binding path.
The output format is inferred from the output path:
.yamland.ymlgenerate YAML..tomlgenerates TOML..jsonand.json5generate JSON5-compatible templates.- unknown or missing extensions generate YAML.
The template APIs write exactly under the output_path you pass. The built-in
config-template CLI command normalizes generated templates under
config/<root_config_name>/; without --output, AppConfig writes
config/app_config/app_config.example.yaml and the matching default schema
config/app_config/app_config.schema.json.
Schema Bindings
With a schema path of schemas/myapp.schema.json, generated root templates use:
#:schema ./schemas/myapp.schema.json
# yaml-language-server: $schema=./schemas/myapp.schema.json
Generated section templates bind section schemas:
# log.yaml
# yaml-language-server: $schema=./schemas/log.schema.json
Generated JSON and JSON5 templates bind the schema with a top-level $schema
field:
{
"$schema": "./schemas/myapp.schema.json"
}
Editor settings are still useful when a project does not want an in-file binding:
{
"json.schemas": [
{
"fileMatch": [
"/config.json",
"/config.*.json"
],
"url": "./schemas/myapp.schema.json"
}
]
}
Template Source Selection
Template generation chooses its source tree in this order:
- Existing config path.
- Existing output template path.
- Output path treated as a new empty template tree.
This lets a project update templates from current config files, update an existing template set, or create a new template set from only the schema.
Mirrored Include Trees
If the source file declares includes, generated templates mirror those include paths under the output directory.
# config.yaml
include:
- server.yaml
Generating config.example.yaml writes:
config.example.yaml
server.yaml
Relative include targets are mirrored under the output file’s parent directory. Absolute include targets remain absolute.
Opt-in Section Splitting
When a source file has no includes, the crate can derive include targets from
nested schema sections marked with x-tree-split. For a schema with a marked
server section, an empty root template source can produce:
config.example.yaml
server.yaml
The root template receives an include block, and server.yaml contains
only the server section. Unmarked nested sections stay inline in their parent
template. Nested sections are split recursively only when those fields also
carry x-tree-split.
IDE Completions
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
Generated JSON Schemas can be used by TOML, YAML, JSON, and JSON5 config files.
They are generated from the same Rust type used by confique:
#![allow(unused)]
fn main() {
use confique::Config;
use schemars::JsonSchema;
#[derive(Debug, Config, JsonSchema)]
struct AppConfig {
#[config(nested)]
#[schemars(extend("x-tree-split" = true))]
server: ServerConfig,
}
}
Generate them with:
#![allow(unused)]
fn main() {
use rust_config_tree::write_config_schemas;
write_config_schemas::<AppConfig>("schemas/myapp.schema.json")?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
This writes the root schema and section schemas such as
schemas/server.schema.json. Generated schemas omit required constraints so
completion works for partial config files without missing-field diagnostics.
The root schema omits split nested section properties, so split child section
completion is available only in files that bind the matching section schema.
Unmarked nested sections remain in the root schema.
Fields marked with x-env-only are omitted from generated schemas, so IDEs do not suggest secrets or other values that must come only from environment variables.
IDE schemas are for completion and basic editor checks, such as type, enum, and
unknown property checks supported by the generated schema. They do not decide
whether a concrete field value is legal for the application. Implement field
value validation in code with #[config(validate = Self::validate)], then run
it through load_config or config-validate. Required fields and final merged
config validation also use those runtime paths.
TOML
TOML files should bind the schema with a top-of-file #:schema directive:
#:schema ./schemas/myapp.schema.json
[server]
bind = "0.0.0.0"
port = 3000
Do not use a root $schema = "..." field in TOML. It becomes real config data
and can affect runtime deserialization. write_config_templates_with_schema
adds the #:schema directive automatically for TOML templates.
YAML
YAML files should use the YAML Language Server modeline:
# yaml-language-server: $schema=./schemas/myapp.schema.json
server:
bind: 0.0.0.0
port: 3000
write_config_templates_with_schema adds this modeline automatically for YAML
templates. Split YAML templates bind their section schema, for example
log.yaml binds ./schemas/log.schema.json.
JSON
JSON and JSON5 files can bind a schema with a top-level $schema property.
write_config_templates_with_schema adds it automatically for generated JSON
and JSON5 templates:
{
"$schema": "./schemas/myapp.schema.json"
}
Editor settings are still useful when a project does not want an in-file binding:
{
"json.schemas": [
{
"fileMatch": [
"/config.json",
"/config.*.json",
"/deploy/*.json"
],
"url": "./schemas/myapp.schema.json"
}
]
}
YAML can also be bound through VS Code settings:
{
"yaml.schemas": {
"./schemas/myapp.schema.json": [
"config.yaml",
"config.*.yaml",
"deploy/*.yaml"
]
}
}
The final layout is:
schemas/myapp.schema.json:
Root file fields only
schemas/server.schema.json:
Server section schema
config.toml:
#:schema ./schemas/myapp.schema.json
config.yaml:
# yaml-language-server: $schema=./schemas/myapp.schema.json
server.yaml:
# yaml-language-server: $schema=./schemas/server.schema.json
config.json:
"$schema": "./schemas/myapp.schema.json"
References:
CLI Integration
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
ConfigCommand provides reusable clap subcommands:
config-templateconfig-schemaconfig-validatecompletionsinstall-completionsuninstall-completions
These built-in subcommands are separate from application-specific config override flags. Merge config override flags as Figment providers in the runtime loading path.
Config override flags remain part of the consuming application’s CLI. Their
names do not need to match dotted config paths. For example, the application can
parse --server-port and map it to the nested server.port config key.
Only flags that the application maps into CliOverrides affect config values.
Flatten it into an application command enum:
- Keep the application’s own
Parsertype. - Keep the application’s own
Subcommandenum. - Add
#[command(flatten)] Config(ConfigCommand)to that enum. - Clap expands the flattened
ConfigCommandvariants into the same command level as the application’s own variants. - Match the
Config(command)variant and pass it tohandle_config_command.
use std::path::PathBuf;
use clap::{Parser, Subcommand};
use confique::Config;
use schemars::JsonSchema;
use rust_config_tree::{ConfigCommand, ConfigSchema, handle_config_command, load_config};
#[derive(Debug, Config, JsonSchema)]
struct AppConfig {
#[config(default = [])]
include: Vec<PathBuf>,
}
impl ConfigSchema for AppConfig {
fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
layer.include.clone().unwrap_or_default()
}
}
#[derive(Debug, Parser)]
#[command(name = "demo")]
struct Cli {
#[arg(long, default_value = "config.yaml")]
config: PathBuf,
#[command(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
enum Command {
Run,
#[command(flatten)]
Config(ConfigCommand),
}
fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let cli = Cli::parse();
match cli.command {
Command::Run => {
let config = load_config::<AppConfig>(&cli.config)?;
println!("{config:#?}");
}
Command::Config(command) => {
handle_config_command::<Cli, AppConfig>(command, &cli.config)?;
}
}
Ok(())
}
Config Templates
demo config-template
The command writes templates under config/<root_config_name>/. If --output
receives a path, only the file name is used. If no output file name is provided,
the command writes
config/<root_config_name>/<root_config_name>.example.yaml. Add
--schema schemas/myapp.schema.json to bind generated TOML, YAML, JSON, and JSON5 templates
to generated JSON Schemas. Split YAML templates bind the matching section
schema. The command also writes the root and section schemas to the selected
schema path.
demo config-template --output app_config.example.toml --schema schemas/myapp.schema.json
Generate root and section JSON Schemas:
demo config-schema
Without --output, config-schema writes the root schema to
config/<root_config_name>/<root_config_name>.schema.json.
Validate the complete runtime config tree:
demo config-validate
Generated editor schemas intentionally avoid required-field diagnostics for
split files. config-validate loads includes, applies defaults, and runs final
confique validation, including validators declared with
#[config(validate = Self::validate)]. Generated *.schema.json files remain
for IDE completion and basic editor checks, not field value legality. It prints
Configuration is ok when validation succeeds.
Shell Completions
Print completions to stdout:
demo completions zsh
Install completions:
demo install-completions zsh
Uninstall completions:
demo uninstall-completions zsh
The installer supports Bash, Elvish, Fish, PowerShell, and Zsh. It writes the completion file under the user’s home directory and updates the shell startup file for shells that require it.
Before changing an existing shell startup file such as ~/.zshrc, ~/.bashrc,
an Elvish rc file, or a PowerShell profile, the command writes a backup next to
the original file:
<rc-file>.backup.by.<program-name>.<timestamp>
Examples
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
The repository includes runnable examples for loading config trees, CLI overrides, built-in config commands, template generation, and the lower-level tree API.
Read the repository examples index:
Run examples from the repository root:
cargo run --example basic_loading
cargo run --example cli_overrides -- --server-port 9000
cargo run --example config_commands -- config-template
cargo run --example config_commands -- config-schema
cargo run --example config_commands -- config-validate
cargo run --example generate_templates
cargo run --example tree_api
The config_commands template and schema commands use the CLI defaults, so
AppConfig writes generated files under config/app_config/.
Tree API
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
Use the lower-level tree API when the application does not use confique, or
when it needs direct access to traversal results.
#![allow(unused)]
fn main() {
use std::{
fs,
io,
path::{Path, PathBuf},
};
use rust_config_tree::{ConfigSource, load_config_tree};
fn load_source(path: &Path) -> io::Result<ConfigSource<String>> {
let content = fs::read_to_string(path)?;
let includes = content
.lines()
.filter_map(|line| line.strip_prefix("include: "))
.map(PathBuf::from)
.collect();
Ok(ConfigSource::new(content, includes))
}
let tree = load_config_tree("config.yaml", load_source)?;
for node in tree.nodes() {
println!("{}", node.path().display());
}
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
Traversal Rules
The tree loader:
- normalizes source paths lexically;
- rejects empty include paths;
- resolves relative includes from the file that declared them;
- preserves absolute include paths;
- detects recursive include cycles;
- skips files already loaded through another include branch.
ConfigTreeOptions can reverse sibling include traversal:
#![allow(unused)]
fn main() {
use rust_config_tree::{ConfigTreeOptions, IncludeOrder};
let options = ConfigTreeOptions::default().include_order(IncludeOrder::Reverse);
let _ = options;
}
Path Helpers
The path helpers are lexical only. They do not resolve symbolic links and do not require paths to exist:
absolutize_lexical(path)normalize_lexical(path)resolve_include_path(parent_path, include_path)
GitHub Pages
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
This repository publishes the manual with mdBook and GitHub Pages.
Each language manual is an independent mdBook project. Each language has its
own SUMMARY.md, so the left sidebar only contains pages for the current
language:
manual/
en/
book.toml
SUMMARY.md
introduction.md
quick-start.md
...
zh/
book.toml
SUMMARY.md
introduction.md
quick-start.md
...
ja/
book.toml
SUMMARY.md
introduction.md
quick-start.md
...
ko/
fr/
de/
es/
pt/
sv/
fi/
nl/
Build locally with:
scripts/publish-pages.sh
The generated site is written to:
target/mdbook
Publishing Workflow
The workflow in .github/workflows/pages.yml runs on pushes to main and on
manual dispatch. It:
- Checks out the repository.
- Installs mdBook.
- Runs
scripts/publish-pages.sh. - Uploads
target/mdbookas the Pages artifact. - Deploys the artifact to GitHub Pages.
The published URL is:
https://developerworks.github.io/rust-config-tree/
Crate Release
For the complete commit, push, Pages deploy, and crate publish flow:
scripts/release.sh --execute --message "Release 0.1.3"
Use the crate release helper from the repository root:
scripts/publish-crate.sh
The default mode runs checks and cargo publish --dry-run. To publish to
crates.io after the checks pass. If the current version already exists on
crates.io, the script bumps the patch version automatically:
scripts/publish-crate.sh --execute
Script usage is summarized in scripts/README.md.