rust-config-tree 手册
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
这是 rust-config-tree 的中文手册。手册介绍配置树加载、模板生成、
JSON Schema(JSON 结构定义)、CLI(命令行接口) 集成和来源追踪。
简介
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
rust-config-tree 为使用分层配置文件的 Rust(系统编程语言) 应用提供可复用的
配置树加载能力和 CLI(命令行接口) 辅助能力。
这个 crate(软件包) 按照清晰的职责边界组织各项功能:
confique负责 schema(结构定义)、代码默认值、校验和配置模板生成。figment负责运行时加载和运行时来源元数据。rust-config-tree负责 include(包含文件) 的递归遍历、include(包含文件) 路径解析、.env文件加载、模板目标发现,以及可复用的 clap(命令行解析库) 命令。
它适合这种自然的配置文件布局:
include:
- config/server.yaml
- config/database.yaml
log:
level: info
每个被 include(包含) 的文件都可以使用相同的 schema(结构定义) 形状。加载器会从
声明 include(包含) 的文件所在目录解析相对路径。最终得到的配置仍然是一个普通的
confique schema(结构定义) 值。
主要能力
- 它会递归遍历 include(包含文件),并检测循环包含。
- 它会从声明 include(包含) 的文件解析相对路径。
- 它会在环境变量 provider(值提供器) 求值之前加载
.env文件。 - 它会使用 schema(结构定义) 中声明的环境变量名,并且不会按分隔符拆分变量名。
- 它会通过 Figment(配置合并库) metadata(元数据) 追踪运行时来源。
- 它会通过
tracing输出 TRACE(追踪级别) 的来源追踪事件。 - 它会生成 Draft 7 JSON Schema(JSON 结构定义),供编辑器补全和基础 schema(结构定义) 检查使用。
- 应用代码通过
#[config(validate = Self::validate)]实现字段值合法性校验,load_config或config-validate会执行这个校验。 - 它会生成 YAML、TOML、JSON 和 JSON5 配置模板。
- 它会为生成的 TOML 模板写入
#:schema,为 YAML 模板写入 YAML Language Server(YAML 语言服务器) modeline(模式声明行),并为 JSON 和 JSON5 模板写入$schema字段。 - 它会按
x-tree-split标记拆分嵌套 section(配置段) 的 YAML 模板。 - 它内置了用于 config template(配置模板)、JSON Schema(JSON 结构定义) 和 shell completion(命令补全) 的 clap(命令行解析库) 子命令。
- 它为不使用
confique的调用方提供低层 tree API(树形接口)。
主要入口
多数应用会使用这些 API(应用程序接口):
load_config::<S>(path)会加载最终 schema(结构定义)。load_config_with_figment::<S>(path)会加载 schema(结构定义),并返回用于 来源追踪的 Figment(配置合并库) graph(配置图)。write_config_templates::<S>(config_path, output_path)会写入 root(根配置) 模板和递归发现的子模板。write_config_schemas::<S>(output_path)会写入 root(根配置) 和 section(配置段) 的 Draft 7 JSON Schema(JSON 结构定义)。handle_config_command::<Cli, S>(command, config_path)会处理内置的 clap(命令行解析库) 配置命令。
不使用 confique 时,可以直接使用 load_config_tree。
快速开始
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
先添加 crate(软件包),再添加应用所需的 schema(结构定义) 库和运行时库:
[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"] }
然后定义 confique schema(结构定义),并为 root(根配置) 类型实现
ConfigSchema:
#![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()
}
}
}
加载配置:
#![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>>(())
}
root(根配置) 文件可以通过 include(包含) 递归加载子配置:
# config.yaml
include:
- config/server.yaml
# config/server.yaml
server:
bind: 0.0.0.0
port: 3000
默认情况下,load_config 使用以下优先级:
环境变量
> 配置文件,后合并的文件会覆盖先合并的文件
> confique 代码默认值
通过高层 API(应用程序接口) 加载 include(包含文件) 时,root(根配置) 文件拥有 最高的文件优先级。被 include(包含) 的文件优先级更低,适合承载默认值,也适合 承载按 section(配置段) 拆分的配置。
命令行参数属于应用自己的 CLI(命令行接口) 语义,所以 load_config 不会自动读取。
当应用需要用命令行参数覆盖配置时,应用应在 build_config_figment 之后合并
CLI override(命令行覆盖值)。合并方式如下:
CLI flag(命令行参数) 名称由应用自己决定,加载器不会自动使用 a.b.c 这种
配置路径。推荐使用正常的 clap(命令行解析库) 参数名,比如 --server-port,
再把参数值映射成嵌套 override(覆盖值) 结构。序列化后的嵌套结构真正决定
哪个配置 key(键) 会被覆盖。
只有应用放进 CliOverrides provider(值提供器) 的值才会覆盖配置。这个机制
适合单次运行时频繁调整参数、但不想修改配置文件的场景。稳定值应继续保存在
配置文件中。
#![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>>(())
}
这样合并 CLI override(命令行覆盖值) 后,完整优先级如下:
命令行覆盖值
> 环境变量
> 配置文件
> confique 代码默认值
配置结构
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
应用的 schema(结构定义) 是普通的 confique config(配置) 类型。
root schema(根结构定义) 必须实现 ConfigSchema,这样 rust-config-tree
才能从中间 confique layer(层) 中发现递归 include(包含文件)。
#![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(包含) 字段
include(包含) 字段可以使用任意名称。rust-config-tree 只通过
ConfigSchema::include_paths 读取这个字段。
这个字段通常应有空默认值:
#![allow(unused)]
fn main() {
#[config(default = [])]
include: Vec<PathBuf>,
}
加载器会接收每个文件的部分 layer(层)。这样加载器可以在最终 schema(结构定义) 合并和校验之前发现子配置文件。
嵌套 Section(配置段)
使用 #[config(nested)] 表示结构化 section(配置段)。嵌套 section(配置段)
一定会影响运行时加载。如果某个 nested(嵌套) 字段还需要生成独立的
*.yaml 模板和 <section>.schema.json schema(结构定义),就给这个字段加上
#[schemars(extend("x-tree-split" = true))]:
#![allow(unused)]
fn main() {
#[derive(Debug, Config, JsonSchema)]
struct AppConfig {
#[config(nested)]
#[schemars(extend("x-tree-split" = true))]
server: ServerConfig,
}
}
对应的自然 YAML 形状为:
server:
bind: 127.0.0.1
port: 8080
环境变量专用字段
当 leaf(叶子) 字段只能由环境变量提供,并且不应该出现在生成的配置文件中时,
可以使用 #[schemars(extend("x-env-only" = true))]。生成的 YAML 模板和
JSON Schema(JSON 结构定义) 会省略 env-only(仅环境变量) 字段;如果父对象因此变空,
生成器也会删除这个父对象。
#![allow(unused)]
fn main() {
#[config(env = "APP_SECRET")]
#[schemars(extend("x-env-only" = true))]
secret: String,
}
字段值合法性校验
生成的 *.schema.json 文件只用于 IDE(集成开发环境) 补全和基础编辑期检查,
不负责判断具体字段值对应用是否合法。
字段值合法性应在代码中通过 #[config(validate = Self::validate)] 实现。
当 load_config 加载最终配置,或者 config-validate 检查最终配置时,
运行时会执行这个校验。
模板 Section(配置段) 路径覆盖
当模板 source(来源) 没有 include(包含文件) 时,crate(软件包) 可以从带
x-tree-split 标记的嵌套 schema section(结构定义配置段) 推导子模板文件。
默认顶层路径是相对 root template(根模板) 目录的 <section>.yaml。
使用 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,
}
}
}
}
运行时加载
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
运行时加载会把职责分别交给 Figment(配置合并库) 和 confique(配置结构定义库):
Figment(配置合并库):
运行时文件加载
运行时环境变量加载
运行时来源元数据
confique(配置结构定义库):
结构定义元数据
默认值
校验
配置模板
主要 API(应用程序接口) 如下:
#![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>>(())
}
当应用需要来源 metadata(元数据) 时,可以使用 load_config_with_figment:
#![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>>(())
}
加载步骤
高层加载器会按以下步骤执行:
- 加载器会对 root config(根配置) 路径做词法解析。
- 加载器会从 root config(根配置) 所在目录开始向上查找,并加载第一个
.env文件。 - 加载器会将每个配置文件加载成部分 layer(层),用于发现 include(包含文件)。
- 加载器会从发现的配置文件构建 Figment(配置合并库) graph(配置图)。
- 加载器会以高于文件的优先级合并
ConfiqueEnvProvider。 - 应用可以选择合并自己的 CLI override(命令行覆盖值)。
- 加载器会从 Figment(配置合并库) 提取
confiquelayer(层)。 - 应用
confique代码默认值。 - 加载器会校验并构造最终 schema(结构定义)。
load_config 和 load_config_with_figment 执行第 1-5 步和第 7-9 步。
第 6 步属于应用语义,因为这个 crate(软件包) 无法推断某个
CLI flag(命令行参数) 应该映射到哪个 schema(结构定义) 字段。
文件格式
运行时文件 provider(值提供器) 会根据配置路径的扩展名选择文件格式:
.yaml和.yml使用 YAML。.toml使用 TOML。.json和.json5使用 JSON。- 未知或缺失扩展名使用 YAML。
模板生成仍使用 confique(配置结构定义库) 的 YAML、TOML 和 JSON5-compatible(JSON5 兼容) 模板渲染器。
Include(包含) 优先级
高层加载器合并文件 provider(值提供器) 时,被 include(包含) 的文件优先级低于 include(包含) 它的文件。root config(根配置) 文件拥有最高的文件优先级。
环境变量优先级高于所有配置文件。confique 默认值只用于运行时
provider(值提供器) 没有提供的值。
当 CLI override(命令行覆盖值) 在 build_config_figment 之后合并时,完整优先级如下:
命令行覆盖值
> 环境变量
> 配置文件
> confique 代码默认值
命令行语法不是由 rust-config-tree 定义的。只要应用把 --server-port
解析出的值映射进嵌套 serialized provider(序列化值提供器),这个值就可以覆盖
server.port。
--server.port 或 a.b.c 这种点分路径语法只有在应用自己实现时才存在。
因此 CLI(命令行接口) 优先级只作用于应用 override provider(覆盖值提供器) 中存在的 key(键)。它适合临时、频繁调整的运行参数。长期稳定配置应留在 配置文件里。
#![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>>(())
}
环境变量
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
环境变量名通过 confique 的 schema(结构定义) 声明:
#![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 会从 confique::Config::META 读取这些名称,并构建
Figment(配置合并库) provider(值提供器),再把每个环境变量映射到精确字段路径。
不要在这个 crate(软件包) 的 schema(结构定义) 中使用基于分隔符的 Figment(配置合并库) 环境变量映射:
#![allow(unused)]
fn main() {
// 不要在 rust-config-tree 的结构定义中使用这种模式。
Env::prefixed("APP_").split("_")
Env::prefixed("APP_").split("__")
}
split("_") 会把下划线当成嵌套 key(键) 分隔符。这样
APP_DATABASE_POOL_SIZE 会变成类似 database.pool.size 的路径,与
pool_size 这种 Rust 字段名冲突。
使用 ConfiqueEnvProvider 时,映射是显式的:
APP_DATABASE_POOL_SIZE -> database.pool_size
单个下划线仍然只是环境变量名的一部分。Figment(配置合并库) 不会猜测嵌套规则。
Dotenv 加载
在运行时 provider(值提供器) 求值之前,加载器会从 root config(根配置)
文件所在目录开始向上查找 .env 文件。
已有的进程环境变量会被保留。.env 中的值只填充缺失的环境变量。
示例:
APP_SERVER_PORT=9000
APP_DATABASE_POOL_SIZE=64
当 schema(结构定义) 声明了匹配的 #[config(env = "...")] 属性时,这些变量会覆盖
配置文件中的值。
值解析
桥接 provider(值提供器) 会让 Figment(配置合并库) 解析环境变量值。它不会调用
confique 的 parse_env hook(钩子函数)。复杂值应优先放在配置文件中;
只有当 Figment(配置合并库) 的环境变量值语法很适合目标类型时,才适合把复杂值
放进环境变量。
来源追踪
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
使用 load_config_with_figment 可以保留运行时加载使用的 Figment(配置合并库)
graph(配置图):
#![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>>(())
}
返回的 Figment(配置合并库) 值可以查询运行时值来源:
#![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}");
}
}
对于 ConfiqueEnvProvider 提供的值,interpolation(插值结果) 会返回
schema(结构定义) 中声明的原始环境变量名:
database.pool_size came from APP_DATABASE_POOL_SIZE
TRACE 事件
加载器使用 tracing::trace! 输出来源追踪事件。只有 TRACE(追踪级别) 启用时,
加载器才会输出这些事件:
#![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")?;
// 如果配置加载完成后才初始化 tracing subscriber(追踪订阅器),
// 可以在安装订阅器后再次输出相同的来源事件。
trace_config_sources::<AppConfig>(&figment);
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}
每个事件都会使用 rust_config_tree::config target(目标),并包含以下字段:
config_key表示用点分隔的配置 key(键)。source表示渲染后的来源 metadata(元数据)。
如果某个字段只来自 confique 默认值,它就没有 Figment(配置合并库) 运行时
metadata(元数据),并会显示为 confique default or unset optional field。
模板生成
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
模板由运行时使用的同一个 confique schema(结构定义) 生成。confique 负责
渲染实际模板内容,包括文档注释、默认值、必填字段和声明的环境变量名。
使用 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>>(())
}
下面的调用会为 root config(根配置) 和显式拆分的嵌套 section(配置段) 生成 Draft 7 JSON Schema(JSON 结构定义):
#![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>>(())
}
如果 nested(嵌套) 字段需要独立生成 *.yaml 模板和
<section>.schema.json schema(结构定义),就使用
#[schemars(extend("x-tree-split" = true))] 标记这个字段。没有这个标记的
nested(嵌套) 字段会留在父模板和父 schema(结构定义) 中。
当某个 leaf(叶子) 字段只能从环境变量提供时,可以添加
#[schemars(extend("x-env-only" = true))]。生成的模板和
JSON Schema(JSON 结构定义) 会省略 env-only(仅环境变量) 字段;如果父对象因此变空,
生成器也会删除这个父对象。
生成的 schema(结构定义) 会移除 required 约束。IDE(集成开发环境) 仍然可以补全,
但是 log.yaml 这类局部文件不会因为缺少 root(根配置) 字段而报错。
root schema(根结构定义) 只补全 root(根配置) 文件里应该写的字段;被拆分的
section(配置段) 字段会从 root schema(根结构定义) 中省略,并只由各自的
section schema(配置段结构定义) 补全。
已经出现的字段仍可由 IDE(集成开发环境) 做基础编辑期检查,例如生成的
schema(结构定义) 支持的类型、枚举和未知属性检查。生成的 *.schema.json
不负责判断具体字段值对应用是否合法。字段值合法性应在代码中通过
#[config(validate = Self::validate)] 实现;
load_config 和 config-validate 会执行这类运行时校验。
生成 TOML、YAML、JSON 和 JSON5 模板时,可以绑定这些 schema(结构定义):
#![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(根配置) 模板会绑定 root schema(根结构定义),并且不会补全被拆分的
child section(子配置段) 字段。拆分出的 section(配置段) YAML 模板会绑定对应的
section schema(配置段结构定义)。JSON 和 JSON5 模板会写入顶层 $schema 字段。
VS Code(代码编辑器) json.schemas 等编辑器设置仍可作为替代绑定方式。
输出格式由输出路径推断:
.yaml和.yml生成 YAML。.toml生成 TOML。.json和.json5会生成 JSON5-compatible(JSON5 兼容) 模板。- 未知或缺失扩展名生成 YAML。
模板 API 会严格写入调用方传入的 output_path。内置的 config-template
CLI(命令行接口) 命令会把生成的模板归档到 config/<root_config_name>/;
未传 --output 时,AppConfig 会写入
config/app_config/app_config.example.yaml,对应的默认 schema(结构定义)
写入 config/app_config/app_config.schema.json。
Schema(结构定义) 绑定
当 schema path(结构定义路径) 是 schemas/myapp.schema.json 时,生成的
root(根配置) 模板会使用以下内容:
#:schema ./schemas/myapp.schema.json
# yaml-language-server: $schema=./schemas/myapp.schema.json
生成的 section(配置段) 模板会绑定 section schema(配置段结构定义):
# log.yaml
# yaml-language-server: $schema=./schemas/log.schema.json
生成的 JSON 和 JSON5 模板会用顶层 $schema 字段绑定 schema(结构定义):
{
"$schema": "./schemas/myapp.schema.json"
}
如果项目不想在文件内写绑定,也可以通过编辑器设置绑定:
{
"json.schemas": [
{
"fileMatch": [
"/config.json",
"/config.*.json"
],
"url": "./schemas/myapp.schema.json"
}
]
}
模板 Source(来源) 选择
模板生成会按以下顺序选择 source tree(来源树):
- 它会先使用已存在的 config path(配置路径)。
- 它会再使用已存在的 output template path(输出模板路径)。
- 它最后会把 output path(输出路径) 当作新的空 template tree(模板树)。
这样项目可以从当前配置更新模板,也可以更新已有模板集,还可以只从 schema(结构定义) 创建新的模板集。
镜像 Include Tree(包含树)
如果 source(来源) 文件声明了 include(包含文件),生成的模板会在 output(输出) 目录下镜像这些 include path(包含路径)。
# config.yaml
include:
- server.yaml
生成 config.example.yaml 会写入:
config.example.yaml
server.yaml
相对 include(包含) 目标会镜像到 output(输出) 文件父目录下。绝对 include(包含) 目标会保留绝对路径。
显式 Section(配置段) 拆分
当 source(来源) 文件没有 include(包含文件) 时,crate(软件包) 可以从带
x-tree-split 标记的嵌套 schema section(结构定义配置段) 推导 include(包含)
目标。对于包含已标记 server section(配置段) 的 schema(结构定义),空
root template source(根模板来源) 可以生成以下文件:
config.example.yaml
server.yaml
root template(根模板) 会得到 include block(包含块),server.yaml
只包含 server section(配置段)。没有标记的 nested section(嵌套配置段) 会内联
保留在父模板中;更深层 section(配置段) 只有同样带 x-tree-split 时才会继续拆分。
IDE(集成开发环境) 补全
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
生成的 JSON Schema(JSON 结构定义) 可以给 TOML、YAML、JSON 和 JSON5 配置文件使用。
这个 schema(结构定义) 从 confique 使用的同一个 Rust(系统编程语言) 类型生成:
#![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,
}
}
生成方式:
#![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>>(())
}
这会写入 root schema(根结构定义) 和 schemas/server.schema.json 这类
section schema(配置段结构定义)。生成的 schema(结构定义) 会移除 required 约束,
所以局部配置文件仍有补全,并且不会出现缺字段诊断。
root schema(根结构定义) 会省略被拆分的 nested section(嵌套配置段) 属性,所以
child section(子配置段) 的补全只会出现在绑定对应 section schema(配置段结构定义)
的文件里。没有标记的 nested section(嵌套配置段) 会保留在
root schema(根结构定义) 中。
带 x-env-only 标记的字段会从生成的 schema(结构定义) 中省略,因此
IDE(集成开发环境) 不会补全必须只来自环境变量的 secret(秘密值) 或其他值。
IDE schema(集成开发环境结构定义) 只用于补全和基础编辑期检查,例如生成的
schema(结构定义) 支持的类型、枚举和未知属性检查。它不负责判断具体字段值对应用
是否合法。字段值合法性应在代码中通过
#[config(validate = Self::validate)] 实现,并由 load_config 或
config-validate 触发。必填字段和最终合并配置的校验也使用这些运行时路径。
TOML
TOML 文件应在顶部使用 #:schema directive(指令) 绑定 schema(结构定义):
#:schema ./schemas/myapp.schema.json
[server]
bind = "0.0.0.0"
port = 3000
不要使用根字段 $schema = "..."。它会成为真实配置数据,可能影响运行时
反序列化。write_config_templates_with_schema 会为 TOML 模板自动添加
#:schema directive(指令)。
YAML
YAML 文件使用 YAML Language Server(YAML 语言服务器) modeline(模式声明行):
# yaml-language-server: $schema=./schemas/myapp.schema.json
server:
bind: 0.0.0.0
port: 3000
write_config_templates_with_schema 会为 YAML 模板自动添加这个 modeline(模式声明行)。
拆分出的 YAML 模板会绑定对应 section schema(配置段结构定义),例如 log.yaml
绑定 ./schemas/log.schema.json。
JSON
JSON 和 JSON5 文件可以用顶层 $schema 字段绑定 schema(结构定义)。
write_config_templates_with_schema 会为生成的 JSON 和 JSON5 模板自动加入这个字段:
{
"$schema": "./schemas/myapp.schema.json"
}
如果项目不想在文件内写绑定,也可以通过编辑器设置绑定:
{
"json.schemas": [
{
"fileMatch": [
"/config.json",
"/config.*.json",
"/deploy/*.json"
],
"url": "./schemas/myapp.schema.json"
}
]
}
YAML 也可以通过 VS Code settings(代码编辑器设置) 绑定:
{
"yaml.schemas": {
"./schemas/myapp.schema.json": [
"config.yaml",
"config.*.yaml",
"deploy/*.yaml"
]
}
}
最终布局如下:
schemas/myapp.schema.json:
只包含 root(根配置) 文件字段
schemas/server.schema.json:
server(服务器) 配置段结构定义
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"
参考:
CLI(命令行接口) 集成
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
ConfigCommand 提供以下可复用的 clap(命令行解析库) 子命令:
config-template会生成配置模板。config-schema会生成 JSON Schema(JSON 结构定义)。config-validate会校验最终配置。completions会输出 completion(补全脚本)。install-completions会安装 completion(补全脚本)。uninstall-completions会卸载 completion(补全脚本)。
这些内置子命令不同于应用自己的配置覆盖参数。配置覆盖参数应在运行时加载 路径里作为 Figment(配置合并库) provider(值提供器) 合并。
配置覆盖参数仍属于依赖方应用自己的 CLI(命令行接口)。参数名不需要匹配点分配置路径。
例如,应用可以解析 --server-port,再把它映射到嵌套配置 key(键) server.port。
只有应用映射进 CliOverrides 的 flag(命令行参数) 才会影响配置值。
应用可以把 ConfigCommand flatten(展开) 到自己的命令枚举中:
- 保留应用自己的
Parser类型。 - 保留应用自己的
Subcommandenum。 - 在这个 enum 里添加
#[command(flatten)] Config(ConfigCommand)。 - Clap(命令行解析库) 会把 flattened(已展开的)
ConfigCommandvariants(变体) 展开到应用自己的同一层命令。 - 应用在
match里处理Config(command)variant(变体),并交给handle_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(())
}
配置模板
demo config-template
命令会在 config/<root_config_name>/ 下写入模板。如果 --output 接收到
路径,命令只使用其中的文件名。未提供 output file name(输出文件名) 时,命令写入
config/<root_config_name>/<root_config_name>.example.yaml。添加
--schema schemas/myapp.schema.json 后,生成的 TOML、YAML、JSON 和 JSON5 模板会绑定生成的
JSON Schema(JSON 结构定义)。拆分出的 YAML 模板会绑定对应的
section schema(配置段结构定义)。该命令也会把 root(根配置) 和
section schema(配置段结构定义) 写入指定的 schema path(结构定义路径)。
demo config-template --output app_config.example.toml --schema schemas/myapp.schema.json
下面的命令会生成 root(根配置) 和 section(配置段) 的 JSON Schema(JSON 结构定义):
demo config-schema
未提供 --output 时,config-schema 会把 root schema(根结构定义) 写入
config/<root_config_name>/<root_config_name>.schema.json。
下面的命令会校验完整的 runtime config tree(运行时配置树):
demo config-validate
生成的 editor schema(编辑器结构定义) 会刻意避免在拆分文件里触发必填字段诊断。
config-validate 会加载 includes(包含文件)、应用默认值,并执行最终
confique 校验,
包括通过 #[config(validate = Self::validate)] 声明的校验。生成的
*.schema.json 仍然只用于 IDE(集成开发环境) 补全和基础编辑期检查,不负责字段值合法性判断。
校验成功时会输出 Configuration is ok。
Shell Completions(命令行补全)
下面的命令会把 completions(补全脚本) 输出到 stdout(标准输出):
demo completions zsh
下面的命令会安装 completions(补全脚本):
demo install-completions zsh
下面的命令会卸载 completions(补全脚本):
demo uninstall-completions zsh
安装器支持 Bash、Elvish、Fish、PowerShell 和 Zsh。它会将 completion(补全脚本) 文件写入用户 home(主目录) 目录,并为需要显式配置的 shell(命令行外壳) 更新启动文件。
在修改已有 shell(命令行外壳) 启动文件之前,例如 ~/.zshrc、~/.bashrc、
Elvish rc(运行控制) 文件或 PowerShell profile(配置文件),命令会先在原文件旁边写入备份:
<rc-file>.backup.by.<program-name>.<timestamp>
示例
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
仓库包含可运行示例。这些示例覆盖 config tree(配置树) 加载、 CLI(命令行接口) 覆盖参数、内置配置命令、模板生成和低层 tree API(树形接口)。
可以阅读仓库 examples(示例目录) 索引:
在仓库根目录运行示例:
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
config_commands 的 template(模板) 和 schema(结构定义) 命令使用 CLI 默认路径,
因此 AppConfig 会把生成文件写到 config/app_config/ 下。
Tree API(树形接口)
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
当应用不使用 confique,或者需要直接访问遍历结果时,应用可以使用低层
tree API(树形接口)。
#![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>>(())
}
遍历规则
tree loader(树形加载器) 会执行以下操作:
- 它会对 source path(来源路径) 做词法归一化。
- 它会拒绝空 include path(包含路径)。
- 它会从声明文件解析相对 include(包含)。
- 它会保留绝对 include path(包含路径)。
- 它会检测递归 include(包含) 循环。
- 它会跳过已经从其他 include(包含) 分支加载过的文件。
ConfigTreeOptions 可以反转同级 include(包含) 的遍历顺序:
#![allow(unused)]
fn main() {
use rust_config_tree::{ConfigTreeOptions, IncludeOrder};
let options = ConfigTreeOptions::default().include_order(IncludeOrder::Reverse);
let _ = options;
}
路径辅助函数
路径辅助函数只做词法处理。它们不解析符号链接,也不要求路径存在:
absolutize_lexical(path)会把路径转换成词法绝对路径。normalize_lexical(path)会对路径做词法归一化。resolve_include_path(parent_path, include_path)会根据父路径解析 include(包含) 路径。
GitHub Pages(静态站点托管)
English | 中文 | 日本語 | 한국어 | Français | Deutsch | Español | Português | Svenska | Suomi | Nederlands
本仓库使用 mdBook(文档构建工具) 和 GitHub Pages(静态站点托管) 发布手册。
每种语言的手册都是独立的 mdBook(文档构建工具) 项目。每种语言都有自己的
SUMMARY.md,因此左侧目录只显示当前语言的页面:
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/
本地构建:
scripts/publish-pages.sh
生成站点写入:
target/mdbook
发布 Workflow(工作流)
.github/workflows/pages.yml 中的 workflow(工作流) 会在 push(推送) 到
main 时运行,也支持手动触发。它会执行以下步骤:
- 它会 checkout(检出) 仓库。
- 它会安装 mdBook(文档构建工具)。
- 运行
scripts/publish-pages.sh。 - 它会将
target/mdbook上传为 Pages artifact(页面产物)。 - 它会将 artifact(产物) 部署到 GitHub Pages(静态站点托管)。
发布 URL:
https://developerworks.github.io/rust-config-tree/
Crate 发布
下面的命令会执行完整的提交、推送、Pages(静态页面) 部署和 crate(软件包) 发布流程:
scripts/release.sh --execute --message "Release 0.1.3"
在仓库根目录可以使用 crate(软件包) 发布辅助脚本:
scripts/publish-crate.sh
默认模式会运行检查和 cargo publish --dry-run。如果当前版本已经存在于
crates.io,脚本会自动 bump patch(递增补丁版本号)。检查通过后,可以发布到
crates.io:
scripts/publish-crate.sh --execute
脚本用法汇总在 scripts/README.md。