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

rust-config-tree マニュアル

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

これは rust-config-tree の日本語マニュアルです。

概要クイックスタート、または実行可能な サンプルから読み始めてください。

概要

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

rust-config-tree は、階層化された設定ファイルを使う Rust アプリケーション 向けに、再利用可能な設定ツリー読み込み機能と CLI 補助機能を提供します。

この crate は責務を小さく分けています。

  • confique は schema 定義、code default、validation、config template generation を担当します。
  • figment は runtime loading と runtime source metadata を担当します。
  • rust-config-tree は recursive include traversal、include path resolution、 .env loading、template target discovery、reusable clap command を担当します。

たとえば、次のような自然な設定ファイル layout を扱うときに便利です。

include:
  - config/server.yaml
  - config/database.yaml

log:
  level: info

include された各 file は同じ schema shape を使えます。相対 include path は、 それを宣言した file から解決されます。最終的な config は通常の confique schema value です。

主な機能

  • recursive include traversal と cycle detection。
  • 宣言元 file からの相対 include path resolution。
  • environment provider 評価前の .env loading。
  • delimiter splitting を使わない schema-declared environment variables。
  • runtime source tracking 向け Figment metadata。
  • tracing による TRACE-level source tracking event。
  • editor completion と基本的な schema check 向け Draft 7 JSON Schema generation。
  • application code で #[config(validate = Self::validate)] として実装し、 load_config または config-validate で実行する field value validation。
  • YAML、TOML、JSON、JSON5 template generation。
  • generated TOML template の #:schema、YAML Language Server modeline、 JSON / JSON5 template の $schema field。
  • x-tree-split で mark した nested section の YAML template splitting。
  • config template、JSON Schema、shell completion 向け built-in clap subcommands。
  • confique を使わない caller 向けの lower-level tree API。

主な入口

多くの application では次の API を使います。

  • load_config::<S>(path) は最終 schema を読み込みます。
  • load_config_with_figment::<S>(path) は schema を読み込み、source tracking に使う Figment graph も返します。
  • write_config_templates::<S>(config_path, output_path) は root template と recursively discovered child template を書き出します。
  • write_config_schemas::<S>(output_path) は root / section Draft 7 JSON Schema を書き出します。
  • handle_config_command::<Cli, S>(command, config_path) は built-in clap config command を処理します。

confique なしで traversal primitive だけが必要な場合は load_config_tree を使います。

クイックスタート

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

crate と、application が使う schema/runtime library を追加します。

[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 type に 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()
    }
}
}

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>>(())
}

root file は recursive include を持てます。

# config.yaml
include:
  - config/server.yaml
# config/server.yaml
server:
  bind: 0.0.0.0
  port: 3000

default の load_config precedence は次の通りです。

environment variables
  > config files, with later merged files overriding earlier files
    > confique code defaults

high-level API で includes を読み込む場合、root file が最も高い file priority を持ちます。included files は lower-priority value を提供し、default や section-specific file として使えます。

command-line arguments は application-specific なので、load_config は自動で 読みません。application が config override flag を持つ場合は、 build_config_figment の後に CLI override を merge します。

CLI flag 名は application が選びます。自動的な a.b.c config path では ありません。--server-port のような通常の clap flag を使い、nested override structure へ map してください。どの config key を上書きするかは serialized nested shape が決めます。

#![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 を merge すると、完全な precedence は次の通りです。

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

設定スキーマ

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

application schema は通常の confique config type です。root schema は ConfigSchema を実装する必要があります。これにより rust-config-tree は 中間 confique layer から recursive 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 Field

include field の名前は任意です。rust-config-treeConfigSchema::include_paths を通してのみ include field を知ります。

通常、この field には empty default を付けます。

#![allow(unused)]
fn main() {
#[config(default = [])]
include: Vec<PathBuf>,
}

loader は各 file を partially loaded layer として受け取ります。これにより、 最終 schema の merge / validation より前に child config files を発見できます。

Nested Sections

structured section には #[config(nested)] を使います。nested section は runtime loading では常に使われます。独立した *.yaml template と <section>.schema.json schema も生成したい nested field には #[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 shape は次の通りです。

server:
  bind: 127.0.0.1
  port: 8080

環境変数専用フィールド

値を環境変数からだけ渡し、生成される config file には出したくない leaf field には #[schemars(extend("x-env-only" = true))] を付けます。生成される YAML template と JSON Schema は env-only field を省略し、その結果空になった parent object も削除します.

#![allow(unused)]
fn main() {
#[config(env = "APP_SECRET")]
#[schemars(extend("x-env-only" = true))]
secret: String,
}

Field Value Validation

生成された *.schema.json file は IDE 補完と基本的な editor check のための ものです。具体的な field value が application として合法かどうかは判断しません。

field value validation は code 側で #[config(validate = Self::validate)] として 実装します。final config を load_config で読み込むとき、または config-validate で確認するときに、この runtime validation が実行されます。

Template Section Overrides

template source に include がない場合、crate は x-tree-split で mark した nested schema section から child template file を導出できます。default の top-level path は <section>.yaml です。

template_path_for_section で path を上書きできます。

#![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

runtime loading は Figment と confique に明確に分割されています。

figment:
  runtime file loading
  runtime environment loading
  runtime source metadata

confique:
  schema metadata
  defaults
  validation
  config templates

main 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>>(())
}

application が source 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>>(())
}

Loading Steps

high-level loader は次の step を実行します。

  1. root config path を字句的に解決する。
  2. root config directory から上方向に最初の .env file を探して読み込む。
  3. 各 config file を partial layer として読み込み include を発見する。
  4. 発見した config files から Figment graph を構築する。
  5. file より高い priority で ConfiqueEnvProvider を merge する。
  6. 必要に応じて application-specific CLI override を merge する。
  7. Figment から confique layer を extract する。
  8. confique code default を適用する。
  9. 最終 schema を validate して構築する。

load_configload_config_with_figment は step 1-5 と 7-9 を実行します。 step 6 は application-specific です。この crate は CLI flag と schema field の 対応を推測しません。

File Formats

runtime file provider は config path extension から選択されます。

  • .yaml.yml は YAML。
  • .toml は TOML。
  • .json.json5 は JSON。
  • unknown extension または extension なしは YAML。

template generation は引き続き confique の YAML、TOML、JSON5-compatible template renderer を使います。

Include Priority

high-level loader は、included file が include した file より lower priority に なるように file provider を merge します。root config file は最も高い file priority を持ちます。

environment variables はすべての config file より高い priority を持ちます。 confique default は runtime provider が値を提供しない場合だけ使われます。

build_config_figment の後に CLI override を merge した場合、完全な precedence は次の通りです。

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

command-line syntax は rust-config-tree が定義しません。application が --server-port を parse し、その値を nested serialized provider に map すれば server.port を上書きできます。--server.porta.b.c のような syntax は、 application が実装した場合だけ存在します。

#![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

environment variable name は schema 内で 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-treeconfique::Config::META からこれらの名前を読み取り、 各 environment variable を正確な field path へ mapping する Figment provider を構築します。

この crate の schema では delimiter-based Figment environment mapping を使いません。

#![allow(unused)]
fn main() {
// Do not use this pattern for rust-config-tree schemas.
Env::prefixed("APP_").split("_")
Env::prefixed("APP_").split("__")
}

split("_") は underscore を nested key separator として扱います。そのため APP_DATABASE_POOL_SIZEdatabase.pool.size のような path になり、 pool_size のような Rust field name と衝突します。

ConfiqueEnvProvider では mapping は明示的です。

APP_DATABASE_POOL_SIZE -> database.pool_size

single underscore は environment variable name の一部として残ります。Figment は nesting rule を推測しません。

Dotenv Loading

runtime provider を評価する前に、loader は root config file の directory から 上方向に .env file を探します。

既存の process environment variables は保持されます。.env の値は missing environment variable だけを埋めます。

例:

APP_SERVER_PORT=9000
APP_DATABASE_POOL_SIZE=64

schema が matching #[config(env = "...")] attribute を宣言している場合、 これらの variable は config file value を上書きします。

Parsing Values

bridge provider は Figment に environment value を parse させます。 confiqueparse_env hook は呼びません。complex value は、Figment の environment value syntax が型に十分合う場合を除き、config file に置いてください。

ソース追跡

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

runtime loading に使われた Figment graph を保持するには 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;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

返された Figment value は runtime value の source を問い合わせられます。

#![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 で宣言 された native environment variable name を返します。

database.pool_size came from APP_DATABASE_POOL_SIZE

TRACE Events

loader は tracing::trace! で source tracking event を出力します。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")?;

// 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>>(())
}

各 event は rust_config_tree::config target を使い、次を含みます。

  • config_key: dotted config key。
  • source: rendered source metadata。

confique default だけから来た値には Figment runtime metadata がありません。 その場合は confique default or unset optional field として報告されます。

テンプレート生成

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

template は runtime で使う同じ confique schema から生成されます。実際の template content は confique が render し、doc comment、default、required field、declared environment variable name を含みます。

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 と split nested section の Draft 7 JSON Schema を生成します。

#![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 field を独立した *.yaml template と <section>.schema.json schema として 生成したい場合は #[schemars(extend("x-tree-split" = true))] を付けます。 mark していない nested field は parent template と parent schema に残ります。

#[schemars(extend("x-env-only" = true))] を leaf field に付けると、その値は環境変数からだけ渡すものとして扱われます。生成される template と JSON Schema は env-only field を省略し、その結果空になった parent object も削除します.

generated schemas は required constraint を省略します。IDE は補完を提供 できますが、log.yaml のような partial file で missing root field を 報告しません。root schema は root file に属する field だけを補完し、nested section field は各 section schema が補完します。present field は type、enum、 unknown property などの基本的な editor check を受けられます。生成された *.schema.json は具体的な field value が application として合法かどうかを 判断しません。field value validation は code 側で #[config(validate = Self::validate)] として実装し、load_config または config-validate で実行します。

generated TOML / YAML / JSON / JSON5 template から schema を bind する場合:

#![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>>(())
}

TOML / YAML の root template は root schema に bind され、split child section field を補完しません。split section YAML template は対応する section schema に bind されます。JSON / JSON5 template は VS Code が認識できる root $schema field を受け取ります。VS Code json.schemas は代替の bind 方法として残ります。

output format は output path から推定されます。

  • .yaml.yml は YAML。
  • .toml は TOML。
  • .json.json5 は JSON5-compatible template。
  • unknown extension または extension なしは YAML。

Schema Bindings

schema path が schemas/myapp.schema.json の場合、generated root template は 次を使います。

#:schema ./schemas/myapp.schema.json
# yaml-language-server: $schema=./schemas/myapp.schema.json

generated section template は section schema を bind します。

# log.yaml
# yaml-language-server: $schema=./schemas/log.schema.json

生成された JSON / JSON5 template は VS Code が認識する root $schema field を書きます。editor settings は任意です:

{
  "json.schemas": [
    {
      "fileMatch": [
        "/config.json",
        "/config.*.json"
      ],
      "url": "./schemas/myapp.schema.json"
    }
  ]
}

Template Source Selection

template generation は source tree を次の順序で選びます。

  1. Existing config path。
  2. Existing output template path。
  3. 新しい empty template tree として扱う output path。

Mirrored Include Trees

source file が include を宣言している場合、generated template は output directory 下に include path を mirror します。

# config.yaml
include:
  - server.yaml

config.example.yaml を生成すると次を書きます。

config.example.yaml
server.yaml

relative include target は output file の parent directory 下に mirror されます。 absolute include target は absolute のままです。

Opt-in Section Splitting

source file に include がない場合、crate は nested schema section から include target を導出できます。server section を持つ schema では、empty root template source から次を生成できます。

config.example.yaml
server.yaml

root template は include block を受け取り、server.yamlserver section だけを含みます。nested section は、その field も x-tree-split を持つ場合だけ recursive splitting されます。

IDE 補完

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

generated JSON Schema は TOML、YAML、JSON、JSON5 config file で使えます。 schema は confique が使う同じ Rust type から生成されます。

#![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 を 書きます。generated schemas は required constraint を省略するため、partial config file に completion を出しながら missing-field diagnostic を出しません。 root schema は nested section property を省略するため、child section completion は matching section schema を bind した file でだけ使えます。

x-env-only で mark した field は generated schema から省略されるため、環境変数だけで渡す secret などは IDE 補完に出ません。

IDE schema は補完と基本的な editor check のためのものです。type、enum、 unknown property check など、生成 schema が表せる範囲だけを扱います。 具体的な field value が application として合法かどうかは判断しません。 field value validation は code 側で #[config(validate = Self::validate)] として 実装し、load_config または config-validate で実行します。required field と final merged config validation もこの runtime path で扱います。

TOML

TOML file は top-of-file #:schema directive で schema を bind します。

#:schema ./schemas/myapp.schema.json

[server]
bind = "0.0.0.0"
port = 3000

TOML で root $schema = "..." field は使わないでください。real config data になり、runtime deserialization に影響する可能性があります。 write_config_templates_with_schema は TOML template に #:schema directive を 自動追加します。

YAML

YAML file は 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 は YAML template にこの modeline を自動追加 します。split YAML template は section schema を bind します。たとえば log.yaml./schemas/log.schema.json を bind します。

JSON

JSON / JSON5 file は root $schema field で schema を bind できます。write_config_templates_with_schema は generated JSON / JSON5 template に自動で追加します。

{
  "$schema": "./schemas/myapp.schema.json"
}

file 内 binding を使わない project では editor settings も引き続き使えます。

{
  "json.schemas": [
    {
      "fileMatch": [
        "/config.json",
        "/config.*.json",
        "/deploy/*.json"
      ],
      "url": "./schemas/myapp.schema.json"
    }
  ]
}

YAML も VS Code settings で bind できます。

{
  "yaml.schemas": {
    "./schemas/myapp.schema.json": [
      "config.yaml",
      "config.*.yaml",
      "deploy/*.yaml"
    ]
  }
}

最終 layout:

schemas/myapp.schema.json:
  root file の field のみ

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 統合

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

ConfigCommand は reusable clap subcommands を提供します。

  • config-template
  • config-schema
  • config-validate
  • completions
  • install-completions
  • uninstall-completions

これらの built-in subcommands は application-specific config override flags とは 別のものです。config override flags は runtime loading path で Figment provider として merge します。

config override flags は consuming application の CLI に属します。名前は dotted config path に一致している必要はありません。たとえば application は --server-port を parse し、それを nested server.port config key に map できます。CliOverrides に map した flag だけが config value に影響します。

application command enum に flatten します。

  1. application 自身の Parser type を保つ。
  2. application 自身の Subcommand enum を保つ。
  3. その enum に #[command(flatten)] Config(ConfigCommand) を追加する。
  4. Clap は flattened ConfigCommand variants を application 自身の command と 同じ level に展開する。
  5. Config(command) variant を match し、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(())
}

Config Templates

demo config-template

この command は config/<root_config_name>/ の下に template を書きます。 --output に path を渡した場合は file name だけを使います。output file name がない場合は config/<root_config_name>/<root_config_name>.example.yaml を書きます。 --schema schemas/myapp.schema.json を追加すると、generated TOML / YAML / JSON / JSON5 template を generated JSON Schema に bind します。split YAML template は matching section schema を bind します。JSON / JSON5 template は VS Code が認識する $schema field を受け取ります。この command は root / section schema も selected schema path に書きます。

demo config-template --output app_config.example.toml --schema schemas/myapp.schema.json

root / section JSON Schema を生成します。

demo config-schema

--output がない場合、config-schema は root schema を config/<root_config_name>/<root_config_name>.schema.json に書きます。

完全な runtime config tree を validate します。

demo config-validate

generated editor schemas は split file で required-field diagnostic を避けるよう に作られます。config-validate は includes を読み込み、defaults を適用し、 final confique validation を実行します。これには #[config(validate = Self::validate)] で宣言した validator も含まれます。 生成された *.schema.json は IDE 補完と基本的な editor check のためのもので、 field value legality は判断しません。成功時は Configuration is ok を出力します。

Shell Completions

completion を stdout に出力します。

demo completions zsh

completion を install します。

demo install-completions zsh

completion を uninstall します。

demo uninstall-completions zsh

installer は Bash、Elvish、Fish、PowerShell、Zsh を support します。completion file を user home directory 以下に書き、必要な shell では startup file も更新 します。

既存の shell startup file、たとえば ~/.zshrc~/.bashrc、Elvish rc file、 PowerShell profile を変更する前に、command は元ファイルの横に backup を書きます。

<rc-file>.backup.by.<program-name>.<timestamp>

サンプル

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

repository には、config tree loading、CLI overrides、built-in config commands、 template generation、lower-level tree API を扱う実行可能なサンプルが含まれます。

repository examples index:

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

Tree API

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

application が confique を使わない場合、または traversal result へ直接 アクセスしたい場合は lower-level 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>>(())
}

Traversal Rules

tree loader は次を行います。

  • source path を字句的に正規化する。
  • empty include path を拒否する。
  • relative include を宣言元 file から解決する。
  • absolute include path を保持する。
  • recursive include cycle を検出する。
  • 別の include branch で既に読み込まれた file を skip する。

ConfigTreeOptions は sibling include traversal を reverse できます。

#![allow(unused)]
fn main() {
use rust_config_tree::{ConfigTreeOptions, IncludeOrder};

let options = ConfigTreeOptions::default().include_order(IncludeOrder::Reverse);
let _ = options;
}

Path Helpers

path helper は lexical only です。symbolic link を解決せず、path の存在も要求 しません。

  • 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

この repository は mdBook と GitHub Pages で manual を publish します。

各 language manual は独立した mdBook project です。各 language はそれぞれ SUMMARY.md を持つため、left sidebar には current language の page だけが 表示されます。

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/

local build:

scripts/publish-pages.sh

generated site は次に書かれます。

target/mdbook

Publishing Workflow

.github/workflows/pages.yml の workflow は main への push と manual dispatch で実行されます。

  1. repository を checkout する。
  2. mdBook を install する。
  3. scripts/publish-pages.sh を実行する。
  4. target/mdbook を Pages artifact として upload する。
  5. artifact を GitHub Pages に deploy する。

published URL:

https://developerworks.github.io/rust-config-tree/

Crate Release

commit、push、Pages deploy、crate publish の完全な flow:

scripts/release.sh --execute --message "Release 0.1.3"

repository root から crate release helper を使います。

scripts/publish-crate.sh

default mode は checks と cargo publish --dry-run を実行します。current version が crates.io に既に存在する場合、script は patch version を自動的に bump します。checks が通ったあと crates.io に publish します。

scripts/publish-crate.sh --execute

script usage は scripts/README.md にまとめています。