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 Handbuch

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

Dies ist das deutsche Handbuch fuer rust-config-tree.

Beginne mit Einfuehrung, Schnellstart oder den ausfuehrbaren Beispielen.

Einfuehrung

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

rust-config-tree stellt wiederverwendbares Laden von Konfigurationsbaeumen und CLI-Helfer fuer Rust-Anwendungen bereit, die geschichtete Konfigurationsdateien verwenden.

Die Crate ist um eine kleine Verantwortungsaufteilung herum gebaut:

  • confique besitzt Schemadefinitionen, Code-Defaults, Validierung und Konfigurationsvorlagenerzeugung.
  • figment besitzt Laufzeitladen und Laufzeit-Quellenmetadaten.
  • rust-config-tree besitzt rekursive Include-Traversierung, Include-Pfadaufloesung, .env-Laden, Vorlagenziel-Erkennung und wiederverwendbare clap-Befehle.

Die Crate ist nuetzlich, wenn eine Anwendung ein natuerliches Dateilayout wie dieses verwenden soll:

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

log:
  level: info

Jede inkludierte Datei kann dieselbe Schemaform verwenden, und relative Include-Pfade werden von der Datei aufgeloest, die sie deklariert hat. Die finale Konfiguration bleibt ein normales confique-Schema.

Hauptfunktionen

  • Rekursive Include-Traversierung mit Zyklenerkennung.
  • Relative Include-Pfade werden von der deklarierenden Datei aufgeloest.
  • .env wird geladen, bevor Umgebungsprovider ausgewertet werden.
  • Schema-deklarierte Umgebungsvariablen ohne Trennzeichen-Splitting.
  • Figment-Metadaten fuer Quellenverfolgung zur Laufzeit.
  • Quellenverfolgungsereignisse auf TRACE-Ebene ueber tracing.
  • Erzeugung von Draft-7-JSON-Schemas fuer Editor-Vervollstaendigung und grundlegende Schema-Pruefungen.
  • Feldwertvalidierung im Anwendungscode mit #[config(validate = Self::validate)], ausgefuehrt durch load_config oder validate-config.
  • Vorlagenerzeugung fuer YAML, TOML, JSON und JSON5.
  • TOML-#:schema, YAML-Language-Server-Modelines und JSON/JSON5-$schema- Felder fuer erzeugte Vorlagen.
  • Opt-in-YAML-Vorlagenaufteilung fuer mit x-tree-split markierte Abschnitte.
  • Eingebaute clap-Unterbefehle fuer Konfigurationsvorlagen, JSON-Schema und Shell-Vervollstaendigungen.
  • Eine untergeordnete Tree-API fuer Aufrufer, die confique nicht verwenden.

Oeffentliche Einstiegspunkte

Diese APIs eignen sich fuer die meisten Anwendungen:

  • load_config::<S>(path) laedt das finale Schema.
  • load_config_with_figment::<S>(path) laedt das Schema und gibt den Figment-Graphen fuer Quellenverfolgung zurueck.
  • write_config_templates::<S>(config_path, output_path) schreibt die Root-Vorlage und rekursiv entdeckte Kind-Vorlagen.
  • write_config_schemas::<S>(output_path) schreibt Draft-7-JSON-Schemas fuer Root und Abschnitte.
  • handle_config_command::<Cli, S>(command, config_path) behandelt eingebaute clap-Konfigurationsbefehle.

Verwende load_config_tree, wenn du das Traversierungsprimitiv ohne confique brauchst.

Schnellstart

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

Fuege die Crate und die Schema-/Laufzeitbibliotheken hinzu, die deine Anwendung verwendet:

[dependencies]
rust-config-tree = "0.2"
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"] }

Definiere ein confique-Schema und implementiere ConfigSchema fuer den Root-Typ:

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

Lade die Konfiguration:

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

Verwende eine Root-Datei mit rekursiven Includes:

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

Die Standardprioritaet von load_config ist:

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

Wenn Includes ueber die High-Level-API geladen werden, hat die Root-Datei die hoechste Dateiprioritaet. Inkludierte Dateien liefern Werte mit niedrigerer Prioritaet und koennen fuer Defaults oder abschnittsspezifische Dateien genutzt werden.

Kommandozeilenargumente sind anwendungsspezifisch, daher liest load_config sie nicht automatisch. Verwende die ConfigOverrides-Ableitungsmakro, um aus geparsten CLI-Flags einen Ueberschreibungsanbieter zu erstellen:

#![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 {
    #[arg(long)]
    config: Option<std::path::PathBuf>,

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

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

Das #[config_override(path = "...")]-Attribut bildet jedes CLI-Flag auf einen gepunkteten Konfigurationspfad ab. Nur angegebene Flags erzeugen Ueberschreibungswerte; weggelassene Flags verschwinden. Der Ueberschreibungsanbieter wird zuletzt gemerged, sodass angegebene Flags Datei- und Umgebungsvariablen ueberschreiben:

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

Konfigurationsschema

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

Anwendungsschemas sind normale confique-Konfigurationstypen. Das Root-Schema muss ConfigSchema implementieren, damit rust-config-tree rekursive Includes aus der zwischengeschalteten confique-Schicht entdecken kann.

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

Das Include-Feld kann beliebig heissen. rust-config-tree kennt es nur ueber ConfigSchema::include_paths.

Das Feld sollte normalerweise einen leeren Default haben:

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

Der Loader erhaelt fuer jede Datei eine teilweise geladene Schicht. Dadurch kann er Kind-Konfigurationsdateien entdecken, bevor das finale Schema zusammengefuehrt und validiert wird.

Verschachtelte Abschnitte

Verwende #[config(nested)] fuer strukturierte Abschnitte. Verschachtelte Abschnitte werden immer fuer das Laden zur Laufzeit genutzt. Fuege #[schemars(extend("x-tree-split" = true))] hinzu, wenn ein nested Feld zusaetzlich als eigenes *.yaml-Template und <section>.schema.json-Schema erzeugt werden soll:

#![allow(unused)]
fn main() {
#[derive(Debug, Config, JsonSchema)]
struct AppConfig {
    #[config(nested)]
    #[schemars(extend("x-tree-split" = true))]
    server: ServerConfig,
}
}

Die natuerliche YAML-Form ist:

server:
  bind: 127.0.0.1
  port: 8080

Nur-Umgebung-Felder

Markiere ein Blattfeld mit #[schemars(extend("x-env-only" = true))], wenn sein Wert nur aus einer Umgebungsvariable kommen soll und nicht in generierten Konfigurationsdateien erscheinen darf. Generierte YAML-Vorlagen und JSON-Schemas lassen env-only-Felder weg, und dadurch leere Elternobjekte werden entfernt.

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

Feldwertvalidierung

Erzeugte *.schema.json-Dateien sind nur fuer IDE-Vervollstaendigung und grundlegende Editor-Pruefungen gedacht. Sie entscheiden nicht, ob ein konkreter Feldwert fuer die Anwendung gueltig ist.

Feldwertvalidierung muss im Code mit #[config(validate = Self::validate)] implementiert werden. Der Validator laeuft, wenn die finale Konfiguration ueber load_config geladen oder ueber validate-config geprueft wird.

Abschnittspfade fuer Vorlagen ueberschreiben

Wenn eine Vorlagenquelle keine Includes hat, kann die Crate Kind-Vorlagendateien aus mit x-tree-split markierten verschachtelten Schemaabschnitten ableiten. Der Standardpfad auf oberster Ebene ist <section>.yaml.

Ueberschreibe diesen Pfad mit 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,
        }
    }
}
}

Laden zur Laufzeit

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

Das Laden zur Laufzeit ist bewusst zwischen Figment und confique aufgeteilt:

figment:
  runtime file loading
  runtime environment loading
  runtime source metadata

confique:
  schema metadata
  defaults
  validation
  config templates

Die Haupt-API ist:

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

Verwende load_config_with_figment, wenn die Anwendung Quellenmetadaten braucht:

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

Ladeschritte

Der High-Level-Loader fuehrt diese Schritte aus:

  1. Root-Konfigurationspfad lexikalisch aufloesen.
  2. Die erste .env-Datei laden, die beim Aufwaertslaufen ab dem Root-Konfigurationsverzeichnis gefunden wird.
  3. Jede Konfigurationsdatei als Teilschicht laden, um Includes zu entdecken.
  4. Einen Figment-Graphen aus den entdeckten Konfigurationsdateien bauen.
  5. Den ConfiqueEnvProvider mit hoeherer Prioritaet als Dateien zusammenfuehren.
  6. Optional anwendungsspezifische CLI-Ueberschreibungen zusammenfuehren.
  7. Eine confique-Schicht aus Figment extrahieren.
  8. confique-Code-Defaults anwenden.
  9. Das finale Schema validieren und konstruieren.

load_config und load_config_with_figment fuehren die Schritte 1-5 und 7-9 aus. Schritt 6 ist anwendungsspezifisch, weil diese Crate nicht ableiten kann, wie ein CLI-Flag auf ein Schemafeld abgebildet wird.

Dateiformate

Der Laufzeit-Dateiprovider wird aus der Erweiterung des Konfigurationspfads gewaehlt:

  • .yaml und .yml verwenden YAML.
  • .toml verwendet TOML.
  • .json und .json5 verwenden JSON.
  • unbekannte oder fehlende Erweiterungen verwenden YAML.

Die Vorlagenerzeugung verwendet weiterhin die Template-Renderer von confique fuer YAML, TOML und JSON5-kompatible Ausgabe.

Include-Prioritaet

Der High-Level-Loader fuehrt Dateiprovider so zusammen, dass inkludierte Dateien niedrigere Prioritaet haben als die Datei, die sie inkludiert. Die Root- Konfigurationsdatei hat die hoechste Dateiprioritaet.

Umgebungsvariablen haben hoehere Prioritaet als alle Konfigurationsdateien. confique-Defaults werden nur fuer Werte verwendet, die nicht von Laufzeitprovidern geliefert werden.

Wenn CLI-Ueberschreibungen nach build_config_figment zusammengefuehrt werden, gilt diese vollstaendige Prioritaet:

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

Die Kommandozeilensyntax wird nicht von rust-config-tree definiert. Ein Flag wie --server-port kann server.port ueberschreiben, wenn die Anwendung den geparsten Wert in einen verschachtelten serialisierten Provider abbildet. Eine Syntax mit Punkten wie --server.port oder a.b.c existiert nur, wenn die Anwendung sie implementiert.

Das bedeutet: CLI-Prioritaet gilt nur fuer Schluessel, die im Ueberschreibungsprovider der Anwendung vorhanden sind. Nutze sie fuer operative Werte, die haeufig fuer einen einzelnen Lauf geaendert werden. Dauerhafte Konfiguration bleibt in Dateien.

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

Umgebungsvariablen

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

Umgebungsvariablennamen werden im Schema mit confique deklariert:

#![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 liest diese Namen aus confique::Config::META und baut einen Figment-Provider, der jede Umgebungsvariable ihrem exakten Feldpfad zuordnet.

Verwende fuer diese Crate kein trennzeichenbasiertes 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("_") behandelt Unterstriche als Trenner fuer verschachtelte Schluessel. Dadurch wird aus APP_DATABASE_POOL_SIZE ein Pfad wie database.pool.size, was mit Rust-Feldnamen wie pool_size kollidiert.

Mit ConfiqueEnvProvider ist die Zuordnung explizit:

APP_DATABASE_POOL_SIZE -> database.pool_size

Einzelne Unterstriche bleiben Teil des Umgebungsvariablennamens. Figment raet die Verschachtelungsregel nicht.

Dotenv-Laden

Bevor Laufzeitprovider ausgewertet werden, sucht der Loader nach einer .env-Datei, indem er vom Verzeichnis der Root-Konfigurationsdatei nach oben laeuft.

Bereits vorhandene Prozess-Umgebungsvariablen bleiben erhalten. Werte aus .env fuellen nur fehlende Umgebungsvariablen.

Beispiel:

APP_SERVER_PORT=9000
APP_DATABASE_POOL_SIZE=64

Diese Variablen ueberschreiben Konfigurationsdateiwerte, wenn das Schema passende #[config(env = "...")]-Attribute deklariert.

Werte parsen

Der Bridge-Provider laesst Figment Umgebungswerte parsen. Er ruft nicht die parse_env-Hooks von confique auf. Lege komplexe Werte in Konfigurationsdateien ab, ausser die Figment-Syntax fuer Umgebungswerte passt gut zum Typ.

Quellenverfolgung

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

Verwende load_config_with_figment, um den Figment-Graphen zu behalten, der beim Laufzeitladen verwendet wurde:

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

Der zurueckgegebene Figment-Wert kann Quellenfragen fuer Laufzeitwerte beantworten:

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

Fuer Werte, die von ConfiqueEnvProvider geliefert werden, gibt die Interpolation den nativen Umgebungsvariablennamen zurueck, der im Schema deklariert ist:

database.pool_size came from APP_DATABASE_POOL_SIZE

TRACE-Ereignisse

Der Loader gibt Quellenverfolgungsereignisse mit tracing::trace! aus. Das geschieht nur, wenn TRACE aktiviert ist:

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

Jedes Ereignis verwendet das Ziel rust_config_tree::config und enthaelt:

  • config_key: den gepunkteten Konfigurationsschluessel.
  • source: die gerenderte Quellenmetadatenangabe.

Werte, die nur aus confique-Defaults stammen, haben keine Figment-Laufzeitmetadaten. Sie werden als confique default or unset optional field gemeldet.

Vorlagenerzeugung

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

Vorlagen werden aus demselben confique-Schema erzeugt, das zur Laufzeit verwendet wird. confique rendert den eigentlichen Vorlageninhalt, einschliesslich Doc-Kommentaren, Defaults, Pflichtfeldern und deklarierten Umgebungsvariablennamen.

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

Erzeuge Draft-7-JSON-Schemas fuer die Root-Konfiguration und explizit aufgeteilte verschachtelte Abschnitte:

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

Markiere ein verschachteltes Feld mit #[schemars(extend("x-tree-split" = true))], wenn es als eigene *.yaml-Vorlage und als eigenes <section>.schema.json-Schema erzeugt werden soll. Nicht markierte verschachtelte Felder bleiben in der Elternvorlage und im Elternschema.

Markiere ein Blattfeld mit #[schemars(extend("x-env-only" = true))], wenn der Wert nur aus Umgebungsvariablen kommen darf. Generierte Vorlagen und JSON-Schemas lassen env-only-Felder weg, und dadurch leere Elternobjekte werden entfernt.

Erzeugte Schemas lassen required-Einschraenkungen weg. IDEs koennen weiterhin Vervollstaendigung anbieten, aber partielle Dateien wie log.yaml melden keine fehlenden Root-Felder. Das Root-Schema vervollstaendigt nur Felder, die in die Root-Datei gehoeren; aufgeteilte Abschnittsfelder werden dort weggelassen und durch ihre eigenen Abschnittsschemas vervollstaendigt. Vorhandene Felder koennen weiterhin grundlegende Editor-Pruefungen erhalten, etwa Typ-, Enum- und Unbekannte-Eigenschaft-Pruefungen, soweit sie vom erzeugten Schema unterstuetzt werden. Erzeugte *.schema.json-Dateien entscheiden nicht, ob ein konkreter Feldwert fuer die Anwendung gueltig ist. Feldwertvalidierung muss im Code mit #[config(validate = Self::validate)] implementiert werden; load_config und validate-config fuehren diese Laufzeitvalidierung aus.

Binde diese Schemas aus erzeugten TOML-, YAML-, JSON- und JSON5-Vorlagen:

#![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- und YAML-Root-Vorlagen binden das Root-Schema und vervollstaendigen keine aufgeteilten untergeordneten Abschnittsfelder. Aufgeteilte YAML-Abschnittsvorlagen binden ihr Abschnittsschema. JSON- und JSON5-Vorlagen erhalten ein oberstes $schema-Feld, das VS Code erkennen kann. VS Code json.schemas bleibt als alternative Bindung moeglich.

Das Ausgabeformat wird aus dem Ausgabepfad abgeleitet:

  • .yaml und .yml erzeugen YAML.
  • .toml erzeugt TOML.
  • .json und .json5 erzeugen JSON5-kompatible Vorlagen.
  • unbekannte oder fehlende Erweiterungen erzeugen YAML.

Schema-Bindungen

Mit einem Schemapfad schemas/myapp.schema.json verwenden erzeugte Root-Vorlagen:

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

Erzeugte Abschnittsvorlagen binden Abschnittsschemas:

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

Erzeugte JSON- und JSON5-Vorlagen schreiben ein oberstes $schema-Feld, das VS Code erkennt. Editor-Einstellungen bleiben optional:

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

Auswahl der Vorlagenquelle

Die Vorlagenerzeugung waehlt ihren Quellbaum in dieser Reihenfolge:

  1. Vorhandener Konfigurationspfad.
  2. Vorhandener Ausgabe-Vorlagenpfad.
  3. Ausgabepfad, behandelt als neuer leerer Vorlagenbaum.

So kann ein Projekt Vorlagen aus aktuellen Konfigurationsdateien aktualisieren, ein vorhandenes Vorlagenset aktualisieren oder ein neues Vorlagenset nur aus dem Schema erzeugen.

Gespiegelte Include-Baeume

Wenn die Quelldatei Includes deklariert, spiegeln erzeugte Vorlagen diese Include-Pfade unter dem Ausgabeverzeichnis.

# config.yaml
include:
  - server.yaml

Das Erzeugen von config.example.yaml schreibt:

config.example.yaml
server.yaml

Relative Include-Ziele werden unter dem Elternverzeichnis der Ausgabedatei gespiegelt. Absolute Include-Ziele bleiben absolut.

Opt-in-Abschnittsaufteilung

Wenn eine Quelldatei keine Includes hat, kann die Crate Include-Ziele aus mit x-tree-split markierten verschachtelten Schemaabschnitten ableiten. Fuer ein Schema mit einem markierten Abschnitt server kann eine leere Root-Vorlagenquelle Folgendes erzeugen:

config.example.yaml
server.yaml

Die Root-Vorlage erhaelt einen Include-Block, und server.yaml enthaelt nur den Abschnitt server. Verschachtelte Abschnitte werden nur rekursiv aufgeteilt, wenn diese Felder ebenfalls x-tree-split tragen.

IDE-Vervollstaendigung

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

Erzeugte JSON-Schemas koennen von TOML-, YAML-, JSON- und JSON5- Konfigurationsdateien verwendet werden. Sie werden aus demselben Rust-Typ erzeugt, den confique verwendet:

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

Erzeuge sie mit:

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

Dies schreibt das Root-Schema und Abschnittsschemas wie schemas/server.schema.json. Erzeugte Schemas lassen required- Einschraenkungen weg, damit Vervollstaendigung fuer partielle Konfigurationsdateien ohne Fehlende-Felder-Diagnosen funktioniert. Das Root-Schema laesst aufgeteilte Abschnittseigenschaften weg, sodass Kindabschnitts-Vervollstaendigung nur in Dateien verfuegbar ist, die das passende Abschnittsschema binden. Nicht markierte verschachtelte Abschnitte bleiben im Root-Schema.

Mit x-env-only markierte Felder werden aus erzeugten Schemas weggelassen, sodass IDEs keine Secrets oder andere Werte vorschlagen, die nur aus Umgebungsvariablen kommen duerfen.

IDE-Schemas dienen der Vervollstaendigung und grundlegenden Editor-Pruefungen, etwa Typ-, Enum- und Unbekannte-Eigenschaft-Pruefungen, soweit sie vom erzeugten Schema unterstuetzt werden. Sie entscheiden nicht, ob ein konkreter Feldwert fuer die Anwendung gueltig ist. Feldwertvalidierung muss im Code mit #[config(validate = Self::validate)] implementiert und dann ueber load_config oder validate-config ausgefuehrt werden. Pflichtfelder und die finale Validierung der zusammengefuehrten Konfiguration verwenden ebenfalls diese Laufzeitpfade.

TOML

TOML-Dateien sollten das Schema mit einer #:schema-Direktive am Dateianfang binden:

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

[server]
bind = "0.0.0.0"
port = 3000

Verwende in TOML kein Root-Feld $schema = "...". Es wird zu echten Konfigurationsdaten und kann die Laufzeit-Deserialisierung beeinflussen. write_config_templates_with_schema fuegt die #:schema-Direktive fuer TOML-Vorlagen automatisch hinzu.

YAML

YAML-Dateien sollten die YAML-Language-Server-Modeline verwenden:

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

server:
  bind: 0.0.0.0
  port: 3000

write_config_templates_with_schema fuegt diese Modeline fuer YAML-Vorlagen automatisch hinzu. Aufgeteilte YAML-Vorlagen binden ihr Abschnittsschema, zum Beispiel bindet log.yaml ./schemas/log.schema.json.

JSON

JSON- und JSON5-Dateien koennen ein Schema mit einem obersten $schema-Feld binden. write_config_templates_with_schema fuegt es fuer erzeugte JSON- und JSON5-Vorlagen automatisch hinzu:

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

Editor-Einstellungen bleiben nuetzlich, wenn ein Projekt keine Bindung in der Datei will:

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

YAML kann ebenfalls ueber VS-Code-Einstellungen gebunden werden:

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

Das finale Layout ist:

schemas/myapp.schema.json:
  Nur Felder der Root-Datei

schemas/server.schema.json:
  Schema fuer den Abschnitt 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"

Referenzen:

CLI-Integration

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

ConfigCommand stellt wiederverwendbare clap-Unterbefehle bereit:

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

Diese eingebauten Unterbefehle sind von anwendungsspezifischen Flags fuer Konfigurationsueberschreibungen getrennt. Fuehre solche Flags im Laufzeit-Ladepfad als Figment-Provider zusammen.

Konfigurations-Ueberschreibungsflags bleiben Teil der CLI der konsumierenden Anwendung. Ihre Namen muessen nicht mit gepunkteten Konfigurationspfaden uebereinstimmen. Zum Beispiel kann die Anwendung --server-port parsen und auf den verschachtelten Konfigurationsschluessel server.port abbilden. Nur Flags, die die Anwendung in CliOverrides abbildet, beeinflussen Konfigurationswerte.

Fuege es flach in ein Befehls-Enum der Anwendung ein:

  1. Behalte den eigenen Parser-Typ der Anwendung.
  2. Behalte das eigene Subcommand-Enum der Anwendung.
  3. Fuege #[command(flatten)] Config(ConfigCommand) zu diesem Enum hinzu.
  4. Clap erweitert die flachen ConfigCommand-Varianten auf dieselbe Befehlsebene wie die eigenen Varianten der Anwendung.
  5. Verarbeite die Variante Config(command) und uebergib sie an 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(())
}

Konfigurationsvorlagen

demo generate-template

Der Befehl schreibt Vorlagen in den von --output angegebenen Ausgabepfad. Wenn kein --output angegeben wird, schreibt der Befehl config/<root_config_name>/<root_config_name>.example.yaml. Fuege --schema schemas/myapp.schema.json hinzu, um erzeugte TOML-, YAML-, JSON- und JSON5-Vorlagen an erzeugte JSON-Schemas zu binden. Aufgeteilte YAML-Vorlagen binden das passende Abschnittsschema. JSON- und JSON5-Vorlagen erhalten ein von VS Code erkennbares $schema-Feld. Der Befehl schreibt ausserdem Root- und Abschnittsschemas an den gewaehlten Schemapfad.

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

Root- und Abschnitts-JSON-Schemas erzeugen:

demo generate-schema

Ohne --output schreibt generate-schema das Root-Schema nach config/<root_config_name>/<root_config_name>.schema.json.

Den vollstaendigen Laufzeit-Konfigurationsbaum validieren:

demo validate-config

Erzeugte Editor-Schemas vermeiden bewusst Pflichtfeld-Diagnosen fuer aufgeteilte Dateien. validate-config laedt Includes, wendet Defaults an und fuehrt die finale confique-Validierung aus, einschliesslich Validatoren aus #[config(validate = Self::validate)]. Erzeugte *.schema.json-Dateien bleiben fuer IDE-Vervollstaendigung und grundlegende Editor-Pruefungen gedacht, nicht fuer Feldwertlegalitaet. Bei erfolgreicher Validierung gibt es Configuration is ok aus.

Shell-Vervollstaendigungen

Vervollstaendigungen nach stdout ausgeben:

demo completions zsh

Vervollstaendigungen installieren:

demo install-completions zsh

Vervollstaendigungen deinstallieren:

demo uninstall-completions zsh

Der Installer unterstuetzt Bash, Elvish, Fish, PowerShell und Zsh. Er schreibt die Vervollstaendigungsdatei unter das Home-Verzeichnis des Benutzers und aktualisiert die Shell-Startdatei fuer Shells, die dies benoetigen.

Bevor eine vorhandene Shell-Startdatei wie ~/.zshrc, ~/.bashrc, eine Elvish-rc-Datei oder ein PowerShell-Profil geaendert wird, schreibt der Befehl ein Backup neben die Originaldatei:

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

Beispiele

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

Das Repository enthaelt ausfuehrbare Beispiele fuer das Laden von Konfigurationsbaeumen, CLI-Ueberschreibungen, eingebaute Konfigurationsbefehle, Vorlagenerzeugung und die untergeordnete Tree-API.

Lies den Beispielindex des Repositorys:

Fuehre Beispiele aus dem Repository-Root aus:

cargo run --example basic_loading
cargo run --example cli_overrides -- --server-port 9000
cargo run --example config_commands -- generate-template
cargo run --example config_commands -- generate-schema
cargo run --example config_commands -- validate-config
cargo run --example generate_templates
cargo run --example tree_api

Tree-API

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

Verwende die untergeordnete Tree-API, wenn die Anwendung confique nicht verwendet oder direkten Zugriff auf Traversierungsergebnisse braucht.

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

Traversierungsregeln

Der Tree-Loader:

  • normalisiert Quellpfade lexikalisch;
  • weist leere Include-Pfade zurueck;
  • loest relative Includes von der Datei auf, die sie deklariert hat;
  • behaelt absolute Include-Pfade bei;
  • erkennt rekursive Include-Zyklen;
  • ueberspringt Dateien, die bereits ueber einen anderen Include-Zweig geladen wurden.

ConfigTreeOptions kann die Traversierung von Geschwister-Includes umkehren:

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

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

Pfadhelfer

Die Pfadhelfer arbeiten rein lexikalisch. Sie loesen keine symbolischen Links auf und verlangen nicht, dass Pfade existieren:

  • 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

Dieses Repository veroeffentlicht das Handbuch mit mdBook und GitHub Pages.

Die Handbuecher der einzelnen Sprachen sind eigenstaendige mdBook-Projekte. Jede Sprache hat ihr eigenes SUMMARY.md, sodass die linke Seitenleiste nur Seiten der aktuellen Sprache enthaelt:

rust-config-tree/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
    ...

Lokal bauen mit:

scripts/publish-pages.sh

Die erzeugte Site wird hier geschrieben:

target/mdbook

Veroeffentlichungsworkflow

Der Workflow in .github/workflows/pages.yml laeuft bei Pushes nach main und bei manueller Ausloesung. Er:

  1. Checkt das Repository aus.
  2. Installiert mdBook.
  3. Fuehrt scripts/publish-pages.sh aus.
  4. Laedt target/mdbook als Pages-Artefakt hoch.
  5. Stellt das Artefakt auf GitHub Pages bereit.

Die veroeffentlichte URL ist:

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

Crate-Release

Fuer den vollstaendigen Ablauf aus Commit, Push, Pages-Deploy und Crate-Veroeffentlichung:

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

Verwende den Crate-Release-Helfer aus dem Repository-Root:

scripts/publish-crate.sh

Der Standardmodus fuehrt Pruefungen und cargo publish --dry-run aus. Zum Veroeffentlichen auf crates.io nach erfolgreichen Pruefungen. Wenn die aktuelle Version bereits auf crates.io existiert, erhoeht das Skript automatisch die Patch-Version:

scripts/publish-crate.sh --execute

Die Skriptnutzung ist in scripts/README.md zusammengefasst.