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

Manual de rust-config-tree

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

Este es el manual en español de rust-config-tree.

Empieza con Introducción, Inicio rápido o los Ejemplos ejecutables.

Introducción

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

rust-config-tree proporciona carga reutilizable de árboles de configuración y ayudantes de CLI para aplicaciones Rust que usan archivos de configuración por capas.

El crate está diseñado alrededor de una pequeña división de responsabilidades:

  • confique posee las definiciones de esquema, valores por defecto en código, validación y generación de plantillas de configuración.
  • figment posee la carga en tiempo de ejecución y los metadatos de origen en tiempo de ejecución.
  • rust-config-tree posee el recorrido recursivo de includes, la resolución de rutas de include, la carga de .env, el descubrimiento de destinos de plantilla y comandos clap reutilizables.

El crate es útil cuando una aplicación quiere una distribución natural de archivos de configuración como esta:

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

log:
  level: info

Cada archivo incluido puede usar la misma forma de esquema, y las rutas de include relativas se resuelven desde el archivo que las declaró. La configuración final sigue siendo un valor normal de esquema confique.

Funciones principales

  • Recorrido recursivo de includes con detección de ciclos.
  • Rutas de include relativas resueltas desde el archivo que las declara.
  • Carga de .env antes de evaluar proveedores de entorno.
  • Variables de entorno declaradas por esquema sin división por delimitadores.
  • Metadatos de Figment para seguimiento de origen en tiempo de ejecución.
  • Eventos de seguimiento de origen en nivel TRACE mediante tracing.
  • Generación de JSON Schema Draft 7 para completado y comprobaciones básicas de esquema en editores.
  • Validación de valores de campo en código de aplicación con #[config(validate = Self::validate)], ejecutada por load_config o config-validate.
  • Generación de plantillas YAML, TOML, JSON y JSON5.
  • Directivas TOML #:schema, modelines de YAML Language Server y campos JSON/JSON5 $schema para plantillas generadas.
  • División opt-in de plantillas YAML para secciones marcadas con x-tree-split.
  • Subcomandos clap incorporados para plantillas de configuración, JSON Schema y completions de shell.
  • Una API de árbol de menor nivel para llamadores que no usan confique.

Puntos de entrada públicos

Usa estas APIs para la mayoría de aplicaciones:

  • load_config::<S>(path) carga el esquema final.
  • load_config_with_figment::<S>(path) carga el esquema y devuelve el grafo Figment usado para seguimiento de origen.
  • write_config_templates::<S>(config_path, output_path) escribe la plantilla raíz y las plantillas hijas descubiertas recursivamente.
  • write_config_schemas::<S>(output_path) escribe JSON Schemas Draft 7 raíz y de sección.
  • handle_config_command::<Cli, S>(command, config_path) maneja comandos clap de configuración incorporados.

Usa load_config_tree cuando necesites la primitiva de recorrido sin confique.

Inicio rápido

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

Añade el crate y las bibliotecas de esquema/tiempo de ejecución que usa tu aplicación:

[dependencies]
rust-config-tree = "0.1"
confique = { version = "0.4", features = ["yaml", "toml", "json5"] }
figment = { version = "0.10", features = ["yaml", "toml", "json", "env"] }
schemars = { version = "1", features = ["derive"] }
serde = { version = "1", features = ["derive"] }
clap = { version = "4", features = ["derive"] }

Define un esquema confique e implementa ConfigSchema para el tipo raíz:

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

Carga la configuración:

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

Usa un archivo raíz con includes recursivos:

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

La precedencia por defecto de load_config es:

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

Cuando los includes se cargan mediante la API de alto nivel, el archivo raíz tiene la prioridad de archivo más alta. Los archivos incluidos aportan valores de menor prioridad y pueden usarse para valores por defecto o archivos específicos de sección.

Los argumentos de línea de comandos son específicos de la aplicación, así que load_config no los lee automáticamente. Fusiona overrides de CLI después de build_config_figment cuando la aplicación tenga flags de override de configuración:

Los nombres de flags de CLI los elige la aplicación. No son automáticamente rutas de configuración a.b.c. Prefiere flags clap normales como --server-port, luego mapéalas a una estructura de override anidada. La forma serializada anidada controla la clave de configuración que se sobrescribe.

Solo los valores representados en el proveedor CliOverrides de la aplicación sobrescriben la configuración. Esto es útil para parámetros que se cambian con frecuencia para una ejecución sin editar el archivo de configuración. Los valores estables deberían permanecer en archivos de configuración.

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

Con overrides de CLI fusionados de esta forma, la precedencia completa es:

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

Esquema de configuración

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

Los esquemas de aplicación son tipos normales de configuración confique. El esquema raíz debe implementar ConfigSchema para que rust-config-tree pueda descubrir includes recursivos desde la capa intermedia de confique.

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

Campo include

El campo include puede tener cualquier nombre. rust-config-tree solo lo conoce mediante ConfigSchema::include_paths.

Normalmente el campo debería tener un valor por defecto vacío:

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

El cargador recibe una capa parcialmente cargada para cada archivo. Eso le permite descubrir archivos de configuración hijos antes de fusionar y validar el esquema final.

Secciones anidadas

Usa #[config(nested)] para secciones estructuradas. Las secciones anidadas siempre se usan para la carga en tiempo de ejecución. Agrega #[schemars(extend("x-tree-split" = true))] cuando un campo anidado tambien deba generarse como su propio template *.yaml y schema <section>.schema.json:

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

La forma YAML natural es:

server:
  bind: 127.0.0.1
  port: 8080

Campos solo de entorno

Marca un campo hoja con #[schemars(extend("x-env-only" = true))] cuando su valor debe venir solo de una variable de entorno y no debe aparecer en archivos de configuración generados. Las plantillas YAML y los JSON Schemas generados omiten los campos env-only, y también se eliminan los objetos padre que queden vacíos.

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

Validación de valores de campo

Los archivos *.schema.json generados sirven solo para completado de IDE y comprobaciones básicas del editor. No deciden si un valor concreto de campo es válido para la aplicación.

La validación de valores debe implementarse en código con #[config(validate = Self::validate)]. El validador se ejecuta cuando la configuración final se carga con load_config o se comprueba con config-validate.

Overrides de sección de plantilla

Cuando una fuente de plantilla no tiene includes, el crate puede derivar archivos de plantilla hijos desde secciones anidadas del esquema marcadas con x-tree-split. La ruta de primer nivel por defecto es <section>.yaml.

Sobrescribe esa ruta con 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,
        }
    }
}
}

Carga en tiempo de ejecución

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

La carga en tiempo de ejecución se divide deliberadamente entre Figment y confique:

figment:
  runtime file loading
  runtime environment loading
  runtime source metadata

confique:
  schema metadata
  defaults
  validation
  config templates

La API principal es:

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

Usa load_config_with_figment cuando la aplicación necesita metadatos de origen:

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

Pasos de carga

El cargador de alto nivel realiza estos pasos:

  1. Resolver léxicamente la ruta de configuración raíz.
  2. Cargar el primer archivo .env encontrado al caminar hacia arriba desde el directorio de configuración raíz.
  3. Cargar cada archivo de configuración como una capa parcial para descubrir includes.
  4. Construir un grafo Figment desde los archivos de configuración descubiertos.
  5. Fusionar ConfiqueEnvProvider con mayor prioridad que los archivos.
  6. Fusionar opcionalmente overrides de CLI específicos de la aplicación.
  7. Extraer una capa confique desde Figment.
  8. Aplicar valores por defecto de código de confique.
  9. Validar y construir el esquema final.

load_config y load_config_with_figment realizan los pasos 1-5 y 7-9. El paso 6 es específico de la aplicación porque este crate no puede inferir cómo una flag de CLI se mapea a un campo del esquema.

Formatos de archivo

El proveedor de archivos en tiempo de ejecución se selecciona desde la extensión de la ruta de configuración:

  • .yaml y .yml usan YAML.
  • .toml usa TOML.
  • .json y .json5 usan JSON.
  • extensiones desconocidas o ausentes usan YAML.

La generación de plantillas sigue usando los renderizadores de plantillas de confique para YAML, TOML y salida compatible con JSON5.

Prioridad de includes

El cargador de alto nivel fusiona proveedores de archivo para que los archivos incluidos tengan menor prioridad que el archivo que los incluyó. El archivo de configuración raíz tiene la prioridad de archivo más alta.

Las variables de entorno tienen mayor prioridad que todos los archivos de configuración. Los valores por defecto de confique solo se usan para valores que no son suministrados por proveedores de tiempo de ejecución.

Cuando se fusionan overrides de CLI después de build_config_figment, la precedencia completa es:

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

La sintaxis de línea de comandos no la define rust-config-tree. Una flag como --server-port puede sobrescribir server.port si la aplicación mapea ese valor parseado a un proveedor serializado anidado. Una sintaxis con puntos como --server.port o a.b.c solo existe si la aplicación la implementa.

Esto significa que la precedencia de CLI se aplica solo a las claves presentes en el proveedor de overrides de la aplicación. Úsala para valores operativos que se cambian con frecuencia en una sola ejecución. Deja la configuración duradera en archivos.

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

Variables de entorno

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

Los nombres de variables de entorno se declaran en el esquema con confique:

#![allow(unused)]
fn main() {
#[derive(Debug, Config)]
struct DatabaseConfig {
    #[config(env = "APP_DATABASE_URL")]
    url: String,

    #[config(default = 16)]
    #[config(env = "APP_DATABASE_POOL_SIZE")]
    pool_size: u32,
}
}

rust-config-tree lee esos nombres desde confique::Config::META y construye un proveedor Figment que mapea cada variable de entorno a su ruta exacta de campo.

No uses mapeo de entorno de Figment basado en delimitadores para este crate:

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

split("_") trata los guiones bajos como separadores de claves anidadas. Eso hace que APP_DATABASE_POOL_SIZE se convierta en una ruta como database.pool.size, que entra en conflicto con nombres de campos Rust como pool_size.

Con ConfiqueEnvProvider, este mapeo es explícito:

APP_DATABASE_POOL_SIZE -> database.pool_size

Los guiones bajos simples siguen siendo parte del nombre de variable de entorno. Figment no adivina la regla de anidamiento.

Carga dotenv

Antes de evaluar proveedores en tiempo de ejecución, el cargador busca un archivo .env caminando hacia arriba desde el directorio del archivo de configuración raíz.

Las variables de entorno existentes del proceso se conservan. Los valores de .env solo rellenan variables de entorno ausentes.

Ejemplo:

APP_SERVER_PORT=9000
APP_DATABASE_POOL_SIZE=64

Estas variables sobrescriben valores de archivos de configuración cuando el esquema declara atributos #[config(env = "...")] coincidentes.

Parseo de valores

El proveedor puente deja que Figment parsee los valores de entorno. No llama a los hooks parse_env de confique. Mantén valores complejos en archivos de configuración salvo que la sintaxis de valores de entorno de Figment encaje bien con el tipo.

Seguimiento de origen

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

Usa load_config_with_figment para conservar el grafo Figment usado por la carga en tiempo de ejecución:

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

El valor Figment devuelto puede responder preguntas de origen para valores en tiempo de ejecución:

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

Para valores suministrados por ConfiqueEnvProvider, la interpolación devuelve el nombre nativo de la variable de entorno declarada en el esquema:

database.pool_size came from APP_DATABASE_POOL_SIZE

Eventos TRACE

El cargador emite eventos de seguimiento de origen con tracing::trace!. Lo hace solo cuando TRACE está habilitado:

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

Cada evento usa el target rust_config_tree::config e incluye:

  • config_key: la clave de configuración con puntos.
  • source: los metadatos de origen renderizados.

Los valores que provienen solo de valores por defecto de confique no tienen metadatos de tiempo de ejecución de Figment. Se informan como confique default or unset optional field.

Generación de plantillas

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

Las plantillas se generan desde el mismo esquema confique usado en tiempo de ejecución. confique renderiza el contenido real de la plantilla, incluidos comentarios de documentación, valores por defecto, campos obligatorios y nombres de variables de entorno declaradas.

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

Genera JSON Schemas Draft 7 para la configuración raíz y las secciones anidadas:

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

Marca un campo anidado con #[schemars(extend("x-tree-split" = true))] cuando deba generarse como su propia plantilla *.yaml y su propio esquema <section>.schema.json. Los campos anidados sin marcar permanecen en la plantilla padre y el esquema padre.

Marca un campo hoja con #[schemars(extend("x-env-only" = true))] cuando el valor debe venir solo de variables de entorno. Las plantillas generadas y los JSON Schemas omiten los campos env-only, y tambien se eliminan los objetos padre que queden vacios.

Los esquemas generados omiten restricciones required. Los IDE todavía pueden ofrecer completado, pero archivos parciales como log.yaml no informan campos raíz faltantes. El esquema raíz solo completa campos que pertenecen al archivo raíz; los campos de secciones divididas se omiten allí y se completan mediante sus propios esquemas de sección. Los campos presentes siguen siendo comprobados de forma básica por el editor, por ejemplo tipo, enum y propiedades desconocidas admitidas por el esquema generado. Los *.schema.json generados no deciden si un valor concreto de campo es válido para la aplicación. La validación de valores debe implementarse en código con #[config(validate = Self::validate)]; load_config y config-validate ejecutan esa validación en tiempo de ejecución.

Enlaza esos esquemas desde plantillas TOML, YAML, JSON y JSON5 generadas:

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

Las plantillas raíz TOML y YAML enlazan el esquema raíz y no completan campos de secciones hijas. Las plantillas YAML de sección dividida enlazan su esquema de sección. Las plantillas JSON y JSON5 reciben un campo raíz $schema que VS Code puede reconocer. VS Code json.schemas sigue siendo una ruta de enlace alternativa.

El formato de salida se infiere de la ruta de salida:

  • .yaml y .yml generan YAML.
  • .toml genera TOML.
  • .json y .json5 generan plantillas compatibles con JSON5.
  • extensiones desconocidas o ausentes generan YAML.

Enlaces de esquema

Con una ruta de esquema schemas/myapp.schema.json, las plantillas raíz generadas usan:

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

Las plantillas de sección generadas enlazan esquemas de sección:

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

Las plantillas JSON y JSON5 generadas escriben un campo raíz $schema que VS Code reconoce. Los ajustes del editor siguen siendo opcionales:

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

Selección de fuente de plantilla

La generación de plantillas elige su árbol fuente en este orden:

  1. Ruta de configuración existente.
  2. Ruta de plantilla de salida existente.
  3. Ruta de salida tratada como un nuevo árbol de plantillas vacío.

Esto permite a un proyecto actualizar plantillas desde archivos de configuración actuales, actualizar un conjunto de plantillas existente o crear un conjunto nuevo solo desde el esquema.

Árboles de includes reflejados

Si el archivo fuente declara includes, las plantillas generadas reflejan esas rutas de include bajo el directorio de salida.

# config.yaml
include:
  - server.yaml

Generar config.example.yaml escribe:

config.example.yaml
server.yaml

Los destinos de include relativos se reflejan bajo el directorio padre del archivo de salida. Los destinos de include absolutos siguen siendo absolutos.

División opt-in de secciones

Cuando un archivo fuente no tiene includes, el crate puede derivar destinos de include desde secciones anidadas del esquema marcadas con x-tree-split. Para un esquema con una sección marcada server, una fuente de plantilla raíz vacía puede producir:

config.example.yaml
server.yaml

La plantilla raíz recibe un bloque include, y server.yaml contiene solo la sección server. Las secciones anidadas solo se dividen recursivamente cuando esos campos tambien llevan x-tree-split.

Completado en IDE

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

Los JSON Schemas generados pueden usarse con archivos de configuración TOML, YAML, JSON y JSON5. Se generan desde el mismo tipo Rust usado por confique:

#![allow(unused)]
fn main() {
use confique::Config;
use schemars::JsonSchema;

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

Genéralos con:

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

Esto escribe el esquema raíz y esquemas de sección como schemas/server.schema.json. Los esquemas generados omiten restricciones required para que el completado funcione en archivos de configuración parciales sin diagnósticos de campos faltantes. El esquema raíz omite propiedades de secciones divididas, por lo que el completado de secciones hijas solo está disponible en archivos que enlazan el esquema de sección correspondiente. Las secciones anidadas sin marca permanecen en el esquema raíz.

Los campos marcados con x-env-only se omiten de los esquemas generados, por lo que los IDE no sugieren secrets u otros valores que deben venir solo de variables de entorno.

Los esquemas del IDE sirven para completado y comprobaciones básicas del editor, como tipo, enum y propiedades desconocidas admitidas por el esquema generado. No deciden si un valor concreto de campo es válido para la aplicación. La validación de valores debe implementarse en código con #[config(validate = Self::validate)] y ejecutarse mediante load_config o config-validate. Los campos obligatorios y la validación final de la configuración fusionada también usan esas rutas de ejecución.

TOML

Los archivos TOML deberían enlazar el esquema con una directiva #:schema al inicio del archivo:

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

[server]
bind = "0.0.0.0"
port = 3000

No uses un campo raíz $schema = "..." en TOML. Se convierte en datos reales de configuración y puede afectar la deserialización en tiempo de ejecución. write_config_templates_with_schema añade automáticamente la directiva #:schema para plantillas TOML.

YAML

Los archivos YAML deberían usar la modeline de YAML Language Server:

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

server:
  bind: 0.0.0.0
  port: 3000

write_config_templates_with_schema añade automáticamente esta modeline para plantillas YAML. Las plantillas YAML divididas enlazan su esquema de sección, por ejemplo log.yaml enlaza ./schemas/log.schema.json.

JSON

Los archivos JSON y JSON5 pueden enlazar un esquema con un campo raíz $schema. write_config_templates_with_schema lo agrega automáticamente a las plantillas JSON y JSON5 generadas:

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

Los ajustes del editor siguen siendo útiles si un proyecto no quiere un enlace dentro del archivo:

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

YAML también puede enlazarse mediante ajustes de VS Code:

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

La disposición final es:

schemas/myapp.schema.json:
  Solo campos del archivo raíz

schemas/server.schema.json:
  Esquema de la sección 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"

Referencias:

Integración CLI

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

ConfigCommand proporciona subcomandos clap reutilizables:

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

Estos subcomandos incorporados están separados de las flags de override de configuración específicas de la aplicación. Fusiona flags de override de configuración como proveedores Figment en la ruta de carga en tiempo de ejecución.

Las flags de override de configuración siguen siendo parte de la CLI de la aplicación consumidora. Sus nombres no necesitan coincidir con rutas de configuración con puntos. Por ejemplo, la aplicación puede parsear --server-port y mapearla a la clave de configuración anidada server.port. Solo las flags que la aplicación mapea dentro de CliOverrides afectan los valores de configuración.

Aplánalo dentro de un enum de comandos de aplicación:

  1. Mantén el tipo Parser propio de la aplicación.
  2. Mantén el enum Subcommand propio de la aplicación.
  3. Añade #[command(flatten)] Config(ConfigCommand) a ese enum.
  4. Clap expande las variantes aplanadas de ConfigCommand en el mismo nivel de comandos que las variantes propias de la aplicación.
  5. Haz match de la variante Config(command) y pásala a 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(())
}

Plantillas de configuración

demo config-template

El comando escribe plantillas bajo config/<root_config_name>/. Si --output recibe una ruta, solo se usa el nombre de archivo. Si no se proporciona un nombre de archivo de salida, el comando escribe config/<root_config_name>/<root_config_name>.example.yaml. Añade --schema schemas/myapp.schema.json para enlazar plantillas TOML, YAML, JSON y JSON5 generadas a JSON Schemas generados. Las plantillas YAML divididas enlazan el esquema de sección correspondiente. Las plantillas JSON y JSON5 reciben un campo $schema que VS Code reconoce. El comando también escribe los esquemas raíz y de sección en la ruta de esquema seleccionada.

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

Genera JSON Schemas raíz y de sección:

demo config-schema

Sin --output, config-schema escribe el esquema raíz en config/<root_config_name>/<root_config_name>.schema.json.

Valida el árbol completo de configuración en tiempo de ejecución:

demo config-validate

Los esquemas de editor generados evitan deliberadamente diagnósticos de campos obligatorios para archivos divididos. config-validate carga includes, aplica valores por defecto y ejecuta la validación final de confique, incluidos los validadores declarados con #[config(validate = Self::validate)]. Los *.schema.json generados siguen siendo para completado de IDE y comprobaciones básicas del editor, no para legalidad de valores de campo. Imprime Configuration is ok cuando la validación tiene éxito.

Shell completions

Imprime completions a stdout:

demo completions zsh

Instala completions:

demo install-completions zsh

Desinstala completions:

demo uninstall-completions zsh

El instalador admite Bash, Elvish, Fish, PowerShell y Zsh. Escribe el archivo de completion bajo el directorio home del usuario y actualiza el archivo de inicio del shell para shells que lo requieren.

Antes de cambiar un archivo de inicio de shell existente como ~/.zshrc, ~/.bashrc, un archivo rc de Elvish o un perfil de PowerShell, el comando escribe una copia de seguridad junto al archivo original:

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

Ejemplos

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

El repositorio incluye ejemplos ejecutables para cargar árboles de configuración, overrides de CLI, comandos de configuración incorporados, generación de plantillas y la API de árbol de menor nivel.

Lee el índice de ejemplos del repositorio:

Ejecuta ejemplos desde la raíz del repositorio:

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

API de árbol

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

Usa la API de árbol de menor nivel cuando la aplicación no use confique o cuando necesite acceso directo a los resultados del recorrido.

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

Reglas de recorrido

El cargador de árbol:

  • normaliza rutas fuente léxicamente;
  • rechaza rutas de include vacías;
  • resuelve includes relativos desde el archivo que los declaró;
  • conserva rutas de include absolutas;
  • detecta ciclos de include recursivos;
  • omite archivos ya cargados mediante otra rama de include.

ConfigTreeOptions puede invertir el recorrido de includes hermanos:

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

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

Ayudantes de ruta

Los ayudantes de ruta son solo léxicos. No resuelven enlaces simbólicos y no requieren que las rutas existan:

  • 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

Este repositorio publica el manual con mdBook y GitHub Pages.

Los manuales por idioma son proyectos mdBook independientes. Cada idioma tiene su propio SUMMARY.md, por lo que la barra lateral izquierda solo contiene páginas del idioma actual:

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
    ...

Construye localmente con:

scripts/publish-pages.sh

El sitio generado se escribe en:

target/mdbook

Workflow de publicación

El workflow en .github/workflows/pages.yml se ejecuta en pushes a main y en manual dispatch. Hace lo siguiente:

  1. Hace checkout del repositorio.
  2. Instala mdBook.
  3. Ejecuta scripts/publish-pages.sh.
  4. Sube target/mdbook como artefacto de Pages.
  5. Despliega el artefacto en GitHub Pages.

La URL publicada es:

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

Release del crate

Para el flujo completo de commit, push, despliegue de Pages y publicación del crate:

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

Usa el ayudante de release del crate desde la raíz del repositorio:

scripts/publish-crate.sh

El modo por defecto ejecuta comprobaciones y cargo publish --dry-run. Para publicar en crates.io después de que las comprobaciones pasen. Si la versión actual ya existe en crates.io, el script incrementa automáticamente la versión patch:

scripts/publish-crate.sh --execute

El uso de scripts se resume en scripts/README.md.