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 config-validate.
  • 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.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"] }

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. Fuehre CLI-Ueberschreibungen nach build_config_figment zusammen, wenn die Anwendung Flags fuer Konfigurationsueberschreibungen hat.

CLI-Flag-Namen werden von der Anwendung gewaehlt. Sie sind nicht automatisch a.b.c-Konfigurationspfade. Bevorzuge normale clap-Flags wie --server-port und bilde sie dann auf eine verschachtelte Ueberschreibungsstruktur ab. Die verschachtelte serialisierte Form steuert den ueberschriebenen Konfigurationsschluessel.

Nur Werte, die im CliOverrides-Provider der Anwendung dargestellt sind, ueberschreiben Konfiguration. Das ist nuetzlich fuer Parameter, die haeufig fuer einen einzelnen Lauf geaendert werden, ohne die Konfigurationsdatei zu bearbeiten. Stabile Werte sollten in Konfigurationsdateien bleiben.

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

Mit so zusammengefuehrten CLI-Ueberschreibungen ist die vollstaendige Prioritaet:

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 config-validate 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 config-validate 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 config-validate 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:

  • config-template
  • config-schema
  • config-validate
  • 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 config-template

Der Befehl schreibt Vorlagen unter config/<root_config_name>/. Wenn --output einen Pfad erhaelt, wird nur der Dateiname verwendet. Wenn kein Ausgabe-Dateiname 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 config-template --output app_config.example.toml --schema schemas/myapp.schema.json

Root- und Abschnitts-JSON-Schemas erzeugen:

demo config-schema

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

Den vollstaendigen Laufzeit-Konfigurationsbaum validieren:

demo config-validate

Erzeugte Editor-Schemas vermeiden bewusst Pflichtfeld-Diagnosen fuer aufgeteilte Dateien. config-validate 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 -- 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

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:

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.