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 do rust-config-tree

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

Este e o manual em portugues do rust-config-tree.

Comece por Introducao, Inicio rapido ou pelos Exemplos executaveis.

Introducao

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

rust-config-tree fornece carregamento reutilizavel de arvores de configuracao e auxiliares de CLI para aplicacoes Rust que usam arquivos de configuracao em camadas.

O crate e projetado em torno de uma pequena divisao de responsabilidades:

  • confique e dono das definicoes de esquema, padroes de codigo, validacao e geracao de modelos de configuracao.
  • figment e dono do carregamento em tempo de execucao e dos metadados de origem em tempo de execucao.
  • rust-config-tree e dono da travessia recursiva de includes, resolucao de caminhos de include, carregamento de .env, descoberta de destinos de modelo e comandos clap reutilizaveis.

O crate e util quando uma aplicacao quer um layout natural de arquivos de configuracao como este:

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

log:
  level: info

Cada arquivo incluido pode usar o mesmo formato de esquema, e caminhos de include relativos sao resolvidos a partir do arquivo que os declarou. A configuracao final continua sendo um valor normal de esquema confique.

Principais recursos

  • Travessia recursiva de includes com deteccao de ciclos.
  • Caminhos de include relativos resolvidos a partir do arquivo declarante.
  • Carregamento de .env antes que provedores de ambiente sejam avaliados.
  • Variaveis de ambiente declaradas no esquema sem divisao por delimitador.
  • Metadados Figment para rastreamento de origem em tempo de execucao.
  • Eventos de rastreamento de origem em nivel TRACE por tracing.
  • Geracao de JSON Schema Draft 7 para completamento e verificacoes basicas de esquema no editor.
  • Validacao de valores de campo no codigo da aplicacao com #[config(validate = Self::validate)], executada por load_config ou config-validate.
  • Geracao de modelos YAML, TOML, JSON e JSON5.
  • Diretivas TOML #:schema, modelines YAML Language Server e campos JSON/JSON5 $schema para modelos gerados.
  • Divisao opt-in de modelos YAML para secoes marcadas com x-tree-split.
  • Subcomandos clap embutidos para modelos de configuracao, JSON Schema e shell completions.
  • Uma API de arvore de nivel mais baixo para chamadores que nao usam confique.

Pontos de entrada publicos

Use estas APIs para a maioria das aplicacoes:

  • load_config::<S>(path) carrega o esquema final.
  • load_config_with_figment::<S>(path) carrega o esquema e retorna o grafo Figment usado para rastreamento de origem.
  • write_config_templates::<S>(config_path, output_path) grava o modelo raiz e modelos filhos descobertos recursivamente.
  • write_config_schemas::<S>(output_path) grava JSON Schemas Draft 7 raiz e de secao.
  • handle_config_command::<Cli, S>(command, config_path) manipula comandos clap de configuracao embutidos.

Use load_config_tree quando precisar do primitivo de travessia sem confique.

Inicio rapido

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

Adicione o crate e as bibliotecas de esquema/tempo de execucao usadas pela sua aplicacao:

[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"] }

Defina um esquema confique e implemente ConfigSchema para o tipo raiz:

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

Carregue a configuracao:

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

Use um arquivo raiz com includes recursivos:

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

A precedencia padrao de load_config e:

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

Quando includes sao carregados pela API de alto nivel, o arquivo raiz tem a maior prioridade entre arquivos. Arquivos incluidos fornecem valores de menor prioridade e podem ser usados para padroes ou arquivos especificos de secao.

Argumentos de linha de comando sao especificos da aplicacao, entao load_config nao os le automaticamente. Mescle sobrescritas de CLI depois de build_config_figment quando a aplicacao tiver flags de sobrescrita de configuracao:

Nomes de flags de CLI sao escolhidos pela aplicacao. Eles nao sao automaticamente caminhos de configuracao a.b.c. Prefira flags clap normais, como --server-port, e depois mapeie-as para uma estrutura aninhada de sobrescrita. O formato serializado aninhado controla a chave de configuracao que sera sobrescrita.

Somente valores representados no provedor CliOverrides da aplicacao sobrescrevem a configuracao. Isso e util para parametros alterados com frequencia em uma unica execucao sem editar o arquivo de configuracao. Valores estaveis devem permanecer em arquivos de configuracao.

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

Com sobrescritas de CLI mescladas dessa forma, a precedencia completa e:

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

Esquema de configuracao

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

Esquemas de aplicacao sao tipos normais de configuracao confique. O esquema raiz deve implementar ConfigSchema para que rust-config-tree possa descobrir includes recursivos a partir da camada intermediaria do 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 de include

O campo de include pode ter qualquer nome. rust-config-tree so o conhece por meio de ConfigSchema::include_paths.

Normalmente, o campo deve ter um padrao vazio:

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

O carregador recebe uma camada parcialmente carregada para cada arquivo. Isso permite descobrir arquivos de configuracao filhos antes que o esquema final seja mesclado e validado.

Secoes aninhadas

Use #[config(nested)] para secoes estruturadas. Secoes aninhadas sempre sao usadas para carregamento em tempo de execucao. Adicione #[schemars(extend("x-tree-split" = true))] quando um campo aninhado tambem deve ser gerado como seu proprio modelo *.yaml e schema <section>.schema.json:

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

O formato YAML natural e:

server:
  bind: 127.0.0.1
  port: 8080

Campos somente de ambiente

Marque um campo folha com #[schemars(extend("x-env-only" = true))] quando seu valor deve vir somente de uma variavel de ambiente e nao deve aparecer em arquivos de configuracao gerados. Modelos YAML e JSON Schemas gerados omitem campos env-only, e objetos pai que ficarem vazios tambem sao removidos.

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

Validacao de valores de campo

Os arquivos *.schema.json gerados servem apenas para completamento de IDE e verificacoes basicas do editor. Eles nao decidem se um valor concreto de campo e valido para a aplicacao.

A validacao de valores deve ser implementada no codigo com #[config(validate = Self::validate)]. O validador e executado quando a configuracao final e carregada por load_config ou verificada por config-validate.

Sobrescritas de secao de modelo

Quando uma origem de modelo nao tem includes, o crate pode derivar arquivos de modelo filhos a partir de secoes de esquema aninhadas marcadas com x-tree-split. O caminho padrao de primeiro nivel e <section>.yaml.

Sobrescreva esse caminho com 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,
        }
    }
}
}

Carregamento em tempo de execucao

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

O carregamento em tempo de execucao e intencionalmente dividido entre Figment e confique:

figment:
  runtime file loading
  runtime environment loading
  runtime source metadata

confique:
  schema metadata
  defaults
  validation
  config templates

A API principal e:

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

Use load_config_with_figment quando a aplicacao precisar de metadados de origem:

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

Etapas de carregamento

O carregador de alto nivel executa estas etapas:

  1. Resolve lexicalmente o caminho da configuracao raiz.
  2. Carrega o primeiro arquivo .env encontrado ao subir a partir do diretorio da configuracao raiz.
  3. Carrega cada arquivo de configuracao como uma camada parcial para descobrir includes.
  4. Constroi um grafo Figment a partir dos arquivos de configuracao descobertos.
  5. Mescla o ConfiqueEnvProvider com prioridade maior que arquivos.
  6. Opcionalmente mescla sobrescritas de CLI especificas da aplicacao.
  7. Extrai uma camada confique do Figment.
  8. Aplica padroes de codigo do confique.
  9. Valida e constroi o esquema final.

load_config e load_config_with_figment executam as etapas 1-5 e 7-9. A etapa 6 e especifica da aplicacao porque este crate nao consegue inferir como uma flag de CLI mapeia para um campo do esquema.

Formatos de arquivo

O provedor de arquivo em tempo de execucao e selecionado pela extensao do caminho de configuracao:

  • .yaml e .yml usam YAML.
  • .toml usa TOML.
  • .json e .json5 usam JSON.
  • extensoes desconhecidas ou ausentes usam YAML.

A geracao de modelos ainda usa os renderizadores de modelo do confique para saida YAML, TOML e compativel com JSON5.

Prioridade de includes

O carregador de alto nivel mescla provedores de arquivo para que arquivos incluidos tenham prioridade menor que o arquivo que os incluiu. O arquivo de configuracao raiz tem a maior prioridade entre arquivos.

Variaveis de ambiente tem prioridade maior que todos os arquivos de configuracao. Padroes do confique sao usados apenas para valores que nao foram fornecidos por provedores de tempo de execucao.

Quando sobrescritas de CLI sao mescladas depois de build_config_figment, a precedencia completa e:

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

A sintaxe de linha de comando nao e definida por rust-config-tree. Uma flag como --server-port pode sobrescrever server.port se a aplicacao mapear esse valor analisado para um provedor serializado aninhado. Uma sintaxe pontuada --server.port ou a.b.c so existe se a aplicacao a implementar.

Isso significa que a precedencia de CLI se aplica apenas a chaves presentes no provedor de sobrescrita da aplicacao. Use-a para valores operacionais alterados com frequencia em uma unica execucao. Deixe configuracao duravel em arquivos.

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

Variaveis de ambiente

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

Nomes de variaveis de ambiente sao declarados no esquema com 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 le esses nomes de confique::Config::META e constroi um provedor Figment que mapeia cada variavel de ambiente para seu caminho exato de campo.

Nao use mapeamento de ambiente Figment baseado em delimitador 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 underscores como separadores de chaves aninhadas. Isso faz APP_DATABASE_POOL_SIZE virar um caminho como database.pool.size, que entra em conflito com nomes de campos Rust como pool_size.

Com ConfiqueEnvProvider, esse mapeamento e explicito:

APP_DATABASE_POOL_SIZE -> database.pool_size

Underscores simples continuam fazendo parte do nome da variavel de ambiente. Figment nao tenta adivinhar a regra de aninhamento.

Carregamento dotenv

Antes que provedores de tempo de execucao sejam avaliados, o carregador procura um arquivo .env subindo a partir do diretorio do arquivo de configuracao raiz.

Variaveis de ambiente existentes no processo sao preservadas. Valores de .env apenas preenchem variaveis de ambiente ausentes.

Exemplo:

APP_SERVER_PORT=9000
APP_DATABASE_POOL_SIZE=64

Essas variaveis sobrescrevem valores de arquivos de configuracao quando o esquema declara atributos #[config(env = "...")] correspondentes.

Analise de valores

O provedor de ponte deixa o Figment analisar valores de ambiente. Ele nao chama os hooks parse_env do confique. Mantenha valores complexos em arquivos de configuracao a menos que a sintaxe de valores de ambiente do Figment seja uma boa escolha para o tipo.

Rastreamento de origem

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

Use load_config_with_figment para manter o grafo Figment usado pelo carregamento em tempo de execucao:

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

O valor Figment retornado pode responder perguntas de origem sobre valores em tempo de execucao:

#![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 fornecidos por ConfiqueEnvProvider, a interpolacao retorna o nome nativo da variavel de ambiente declarado no esquema:

database.pool_size came from APP_DATABASE_POOL_SIZE

Eventos TRACE

O carregador emite eventos de rastreamento de origem com tracing::trace!. Ele faz isso apenas quando TRACE esta 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 o target rust_config_tree::config e inclui:

  • config_key: a chave de configuracao pontuada.
  • source: os metadados de origem renderizados.

Valores que vieram apenas de padroes do confique nao tem metadados Figment em tempo de execucao. Eles sao relatados como confique default or unset optional field.

Geracao de modelos

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

Modelos sao gerados a partir do mesmo esquema confique usado em tempo de execucao. confique renderiza o conteudo real do modelo, incluindo comentarios de documentacao, padroes, campos obrigatorios e nomes declarados de variaveis de ambiente.

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

Gere JSON Schemas Draft 7 para a configuracao raiz e secoes aninhadas marcadas para divisao:

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

Marque um campo aninhado com #[schemars(extend("x-tree-split" = true))] quando ele deve ser gerado como seu proprio modelo *.yaml e seu proprio schema <section>.schema.json. Campos aninhados nao marcados permanecem no modelo pai e no schema pai.

Marque um campo folha com #[schemars(extend("x-env-only" = true))] quando o valor deve vir somente de variaveis de ambiente. Os modelos gerados e os JSON Schemas omitem campos env-only, e objetos pai que ficarem vazios tambem sao removidos.

Os esquemas gerados omitem restricoes required. IDEs ainda podem oferecer completamento, mas arquivos parciais como log.yaml nao relatam campos raiz ausentes. O esquema raiz completa apenas campos que pertencem ao arquivo raiz; campos de secoes divididas sao omitidos ali e completados por seus proprios esquemas de secao. Campos presentes ainda podem receber verificacoes basicas do editor, como tipo, enum e propriedades desconhecidas suportadas pelo esquema gerado. Os *.schema.json gerados nao decidem se um valor concreto de campo e valido para a aplicacao. A validacao de valores deve ser implementada no codigo com #[config(validate = Self::validate)]; load_config e config-validate executam essa validacao em tempo de execucao.

Vincule esses esquemas a partir de modelos TOML, YAML, JSON e JSON5 gerados:

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

Modelos raiz TOML e YAML vinculam o esquema raiz e nao completam campos de secoes filhas. Modelos YAML de secao dividida vinculam seu esquema de secao. Modelos JSON e JSON5 recebem um campo raiz $schema que o VS Code pode reconhecer. VS Code json.schemas continua sendo um caminho alternativo de vinculo.

O formato de saida e inferido a partir do caminho de saida:

  • .yaml e .yml geram YAML.
  • .toml gera TOML.
  • .json e .json5 geram modelos compativeis com JSON5.
  • extensoes desconhecidas ou ausentes geram YAML.

Vinculos de esquema

Com um caminho de esquema schemas/myapp.schema.json, modelos raiz gerados usam:

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

Modelos de secao gerados vinculam esquemas de secao:

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

Modelos JSON e JSON5 gerados escrevem um campo raiz $schema reconhecido pelo VS Code. As configuracoes do editor continuam opcionais:

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

Selecao da origem de modelos

A geracao de modelos escolhe sua arvore de origem nesta ordem:

  1. Caminho de configuracao existente.
  2. Caminho de modelo de saida existente.
  3. Caminho de saida tratado como uma nova arvore de modelo vazia.

Isso permite que um projeto atualize modelos a partir dos arquivos de configuracao atuais, atualize um conjunto de modelos existente ou crie um novo conjunto de modelos apenas a partir do esquema.

Arvores de include espelhadas

Se o arquivo de origem declara includes, modelos gerados espelham esses caminhos de include sob o diretorio de saida.

# config.yaml
include:
  - server.yaml

Gerar config.example.yaml grava:

config.example.yaml
server.yaml

Destinos de include relativos sao espelhados sob o diretorio pai do arquivo de saida. Destinos de include absolutos permanecem absolutos.

Divisao opt-in de secoes

Quando um arquivo de origem nao tem includes, o crate pode derivar destinos de include a partir de secoes de esquema aninhadas marcadas com x-tree-split. Para um esquema com uma secao marcada server, uma origem de modelo raiz vazia pode produzir:

config.example.yaml
server.yaml

O modelo raiz recebe um bloco de include, e server.yaml contem apenas a secao server. Secoes aninhadas so sao divididas recursivamente quando esses campos tambem carregam x-tree-split.

Completions de IDE

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

JSON Schemas gerados podem ser usados por arquivos de configuracao TOML, YAML, JSON e JSON5. Eles sao gerados a partir do mesmo tipo Rust usado pelo 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,
}
}

Gere-os com:

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

Isso grava o esquema raiz e esquemas de secao, como schemas/server.schema.json. Esquemas gerados omitem restricoes required para que o completamento funcione em arquivos de configuracao parciais sem diagnosticos de campos ausentes. O esquema raiz omite propriedades de secoes aninhadas, entao o completamento de secoes filhas fica disponivel apenas em arquivos que vinculam o esquema de secao correspondente.

Campos marcados com x-env-only sao omitidos dos esquemas gerados, entao IDEs nao sugerem secrets ou outros valores que devem vir somente de variaveis de ambiente.

Esquemas de IDE servem para completamento e verificacoes basicas do editor, como tipo, enum e propriedades desconhecidas suportadas pelo esquema gerado. Eles nao decidem se um valor concreto de campo e valido para a aplicacao. A validacao de valores deve ser implementada no codigo com #[config(validate = Self::validate)] e executada por load_config ou config-validate. Campos obrigatorios e a validacao final da configuracao mesclada tambem usam esses caminhos de execucao.

TOML

Arquivos TOML devem vincular o esquema com uma diretiva #:schema no topo do arquivo:

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

[server]
bind = "0.0.0.0"
port = 3000

Nao use um campo raiz $schema = "..." em TOML. Ele vira dado real de configuracao e pode afetar a desserializacao em tempo de execucao. write_config_templates_with_schema adiciona a diretiva #:schema automaticamente para modelos TOML.

YAML

Arquivos YAML devem usar a modeline do YAML Language Server:

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

server:
  bind: 0.0.0.0
  port: 3000

write_config_templates_with_schema adiciona essa modeline automaticamente para modelos YAML. Modelos YAML divididos vinculam seu esquema de secao; por exemplo, log.yaml vincula ./schemas/log.schema.json.

JSON

Arquivos JSON e JSON5 podem vincular um schema com um campo raiz $schema. write_config_templates_with_schema o adiciona automaticamente aos modelos JSON e JSON5 gerados:

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

As configuracoes do editor continuam uteis quando o projeto nao quer vinculo dentro do arquivo:

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

YAML tambem pode ser vinculado por configuracoes do VS Code:

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

O layout final e:

schemas/myapp.schema.json:
  Somente campos do arquivo raiz

schemas/server.schema.json:
  Schema da secao 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:

Integracao de CLI

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

ConfigCommand fornece subcomandos clap reutilizaveis:

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

Esses subcomandos embutidos sao separados das flags de sobrescrita de configuracao especificas da aplicacao. Mescle flags de sobrescrita de configuracao como provedores Figment no caminho de carregamento em tempo de execucao.

Flags de sobrescrita de configuracao continuam fazendo parte da CLI da aplicacao consumidora. Seus nomes nao precisam corresponder a caminhos de configuracao pontuados. Por exemplo, a aplicacao pode analisar --server-port e mapeia-lo para a chave aninhada server.port. Somente flags que a aplicacao mapeia para CliOverrides afetam valores de configuracao.

Achate-o em um enum de comandos da aplicacao:

  1. Mantenha o tipo Parser proprio da aplicacao.
  2. Mantenha o enum Subcommand proprio da aplicacao.
  3. Adicione #[command(flatten)] Config(ConfigCommand) a esse enum.
  4. Clap expande as variantes achatadas de ConfigCommand para o mesmo nivel de comando das variantes proprias da aplicacao.
  5. Faca match da variante Config(command) e passe-a para 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(())
}

Modelos de configuracao

demo config-template

O comando grava modelos em config/<root_config_name>/. Se --output receber um caminho, somente o nome do arquivo e usado. Se nenhum nome de arquivo de saida for fornecido, o comando grava config/<root_config_name>/<root_config_name>.example.yaml. Adicione --schema schemas/myapp.schema.json para vincular modelos TOML, YAML, JSON e JSON5 gerados a JSON Schemas gerados. Modelos YAML divididos vinculam o esquema de secao correspondente. Modelos JSON e JSON5 recebem um campo $schema reconhecido pelo VS Code. O comando tambem grava o esquema raiz e esquemas de secao no caminho de esquema selecionado.

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

Gere JSON Schemas raiz e de secao:

demo config-schema

Sem --output, config-schema grava o esquema raiz em config/<root_config_name>/<root_config_name>.schema.json.

Valide a arvore completa de configuracao em tempo de execucao:

demo config-validate

Esquemas de editor gerados evitam intencionalmente diagnosticos de campos obrigatorios para arquivos divididos. config-validate carrega includes, aplica padroes e executa a validacao final do confique, incluindo validadores declarados com #[config(validate = Self::validate)]. Os *.schema.json gerados continuam sendo para completamento de IDE e verificacoes basicas do editor, nao para legalidade de valores de campo. Ele imprime Configuration is ok quando a validacao tem sucesso.

Shell completions

Imprima completions em stdout:

demo completions zsh

Instale completions:

demo install-completions zsh

Desinstale completions:

demo uninstall-completions zsh

O instalador suporta Bash, Elvish, Fish, PowerShell e Zsh. Ele grava o arquivo de completion sob o diretorio home do usuario e atualiza o arquivo de inicializacao do shell para shells que exigem isso.

Antes de alterar um arquivo de inicializacao de shell existente, como ~/.zshrc, ~/.bashrc, um arquivo rc do Elvish ou um perfil do PowerShell, o comando grava um backup ao lado do arquivo original:

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

Exemplos

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

O repositorio inclui exemplos executaveis para carregar arvores de configuracao, sobrescritas de CLI, comandos de configuracao embutidos, geracao de modelos e a API de arvore de nivel mais baixo.

Leia o indice de exemplos do repositorio:

Execute exemplos a partir da raiz do 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 arvore

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

Use a API de arvore de nivel mais baixo quando a aplicacao nao usa confique ou quando precisa de acesso direto aos resultados da travessia.

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

Regras de travessia

O carregador de arvore:

  • normaliza caminhos de origem lexicalmente;
  • rejeita caminhos de include vazios;
  • resolve includes relativos a partir do arquivo que os declarou;
  • preserva caminhos de include absolutos;
  • detecta ciclos de include recursivos;
  • ignora arquivos ja carregados por outro ramo de include.

ConfigTreeOptions pode inverter a travessia de includes irmaos:

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

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

Auxiliares de caminho

Os auxiliares de caminho sao apenas lexicais. Eles nao resolvem links simbolicos e nao exigem que caminhos existam:

  • 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 o manual com mdBook e GitHub Pages.

Os manuais de cada idioma sao projetos mdBook independentes. Cada idioma tem seu proprio SUMMARY.md, entao a barra lateral esquerda contem apenas paginas do idioma atual:

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

Construa localmente com:

scripts/publish-pages.sh

O site gerado e gravado em:

target/mdbook

Workflow de publicacao

O workflow em .github/workflows/pages.yml roda em pushes para main e por acionamento manual. Ele:

  1. Faz checkout do repositorio.
  2. Instala mdBook.
  3. Executa scripts/publish-pages.sh.
  4. Envia target/mdbook como artefato Pages.
  5. Implanta o artefato no GitHub Pages.

A URL publicada e:

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

Lancamento do crate

Para o fluxo completo de commit, push, deploy do Pages e publicacao do crate:

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

Use o auxiliar de lancamento do crate a partir da raiz do repositorio:

scripts/publish-crate.sh

O modo padrao executa verificacoes e cargo publish --dry-run. Para publicar no crates.io depois que as verificacoes passarem. Se a versao atual ja existir no crates.io, o script incrementa a versao patch automaticamente:

scripts/publish-crate.sh --execute

O uso dos scripts e resumido em scripts/README.md.