Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

rust-config-tree 매뉴얼

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

이 문서는 rust-config-tree의 한국어 매뉴얼입니다.

소개, 빠른 시작, 또는 실행 가능한 예제부터 읽어보세요.

소개

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

rust-config-tree는 계층형 설정 파일을 사용하는 Rust 애플리케이션을 위한 재사용 가능한 설정 트리 로딩과 CLI 헬퍼를 제공합니다.

이 crate는 작은 책임 분리를 중심으로 설계되어 있습니다.

  • confique는 스키마 정의, 코드 기본값, 검증, 설정 템플릿 생성을 담당합니다.
  • figment는 런타임 로딩과 런타임 소스 메타데이터를 담당합니다.
  • rust-config-tree는 재귀 include 순회, include 경로 해석, .env 로딩, 템플릿 대상 발견, 재사용 가능한 clap 명령을 담당합니다.

이 crate는 애플리케이션이 다음과 같은 자연스러운 설정 파일 레이아웃을 원할 때 유용합니다.

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

log:
  level: info

포함된 각 파일은 같은 스키마 형태를 사용할 수 있고, 상대 include 경로는 그것을 선언한 파일을 기준으로 해석됩니다. 최종 설정은 여전히 일반 confique 스키마 값입니다.

주요 기능

  • 순환 감지가 있는 재귀 include 순회.
  • 선언 파일 기준의 상대 include 경로 해석.
  • 환경 프로바이더 평가 전 .env 로딩.
  • delimiter 분할 없는 스키마 선언 환경 변수.
  • 런타임 소스 추적을 위한 Figment 메타데이터.
  • tracing을 통한 TRACE 레벨 소스 추적 이벤트.
  • 에디터 완성과 기본 schema 검사를 위한 Draft 7 JSON Schema 생성.
  • 애플리케이션 코드에서 #[config(validate = Self::validate)]로 구현하고 load_config 또는 config-validate로 실행하는 필드 값 유효성 검사.
  • YAML, TOML, JSON, JSON5 템플릿 생성.
  • 생성된 템플릿을 위한 TOML #:schema, YAML Language Server 스키마 modeline, JSON/JSON5 $schema 필드.
  • x-tree-split로 표시한 중첩 섹션의 YAML 템플릿 분할.
  • 설정 템플릿, JSON Schema, 셸 완성을 위한 내장 clap 하위 명령.
  • confique를 사용하지 않는 호출자를 위한 낮은 수준의 트리 API.

공개 진입점

대부분의 애플리케이션에서는 다음 API를 사용하세요.

  • load_config::<S>(path)는 최종 스키마를 로드합니다.
  • load_config_with_figment::<S>(path)는 스키마를 로드하고 소스 추적에 사용한 Figment 그래프를 반환합니다.
  • write_config_templates::<S>(config_path, output_path)는 루트 템플릿과 재귀적으로 발견한 자식 템플릿을 씁니다.
  • write_config_schemas::<S>(output_path)는 루트 및 섹션 Draft 7 JSON Schema를 씁니다.
  • handle_config_command::<Cli, S>(command, config_path)는 내장 clap 설정 명령을 처리합니다.

confique 없이 순회 primitive가 필요하면 load_config_tree를 사용하세요.

빠른 시작

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

애플리케이션에서 사용할 crate와 스키마/런타임 라이브러리를 추가합니다.

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

confique 스키마를 정의하고 루트 타입에 ConfigSchema를 구현합니다.

#![allow(unused)]
fn main() {
use std::path::PathBuf;

use confique::Config;
use rust_config_tree::ConfigSchema;

#[derive(Debug, Config)]
struct AppConfig {
    #[config(default = [])]
    include: Vec<PathBuf>,

    #[config(nested)]
    server: ServerConfig,
}

#[derive(Debug, Config)]
struct ServerConfig {
    #[config(default = "127.0.0.1")]
    #[config(env = "APP_SERVER_BIND")]
    bind: String,

    #[config(default = 8080)]
    #[config(env = "APP_SERVER_PORT")]
    port: u16,
}

impl ConfigSchema for AppConfig {
    fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
        layer.include.clone().unwrap_or_default()
    }
}
}

설정을 로드합니다.

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

재귀 include가 있는 루트 파일을 사용합니다.

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

기본 load_config 우선순위는 다음과 같습니다.

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

상위 수준 API가 include를 로드할 때 루트 파일이 가장 높은 파일 우선순위를 가집니다. 포함된 파일은 더 낮은 우선순위의 값을 제공하며 기본값 또는 섹션별 파일로 사용할 수 있습니다.

명령줄 인자는 애플리케이션별이므로 load_config가 자동으로 읽지 않습니다. 애플리케이션에 설정 override 플래그가 있다면 build_config_figment 뒤에 CLI override를 병합하세요.

CLI 플래그 이름은 애플리케이션이 선택합니다. 자동으로 a.b.c 설정 경로가 되지 않습니다. --server-port 같은 일반 clap 플래그를 선호하고, 이를 중첩 override 구조에 매핑하세요. 중첩 직렬화 형태가 override되는 설정 키를 제어합니다.

애플리케이션의 CliOverrides 프로바이더에 표현된 값만 설정을 override합니다. 이는 설정 파일을 편집하지 않고 한 번의 실행에서 자주 바꾸는 파라미터에 유용합니다. 안정적인 값은 설정 파일에 두어야 합니다.

#![allow(unused)]
fn main() {
use figment::providers::Serialized;
use serde::Serialize;
use rust_config_tree::{build_config_figment, load_config_from_figment};

#[derive(Debug, Serialize)]
struct CliOverrides {
    #[serde(skip_serializing_if = "Option::is_none")]
    server: Option<CliServerOverrides>,
}

#[derive(Debug, Serialize)]
struct CliServerOverrides {
    #[serde(skip_serializing_if = "Option::is_none")]
    port: Option<u16>,
}

let cli_overrides = CliOverrides {
    server: Some(CliServerOverrides { port: Some(9000) }),
};

let figment = build_config_figment::<AppConfig>("config.yaml")?
    .merge(Serialized::defaults(cli_overrides));

let config = load_config_from_figment::<AppConfig>(&figment)?;
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

이 방식으로 CLI override를 병합하면 전체 우선순위는 다음과 같습니다.

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

설정 스키마

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

애플리케이션 스키마는 일반 confique 설정 타입입니다. 루트 스키마는 ConfigSchema를 구현해야 하며, 그래야 rust-config-tree가 중간 confique 레이어에서 재귀 include를 발견할 수 있습니다.

#![allow(unused)]
fn main() {
use std::path::PathBuf;

use confique::Config;
use schemars::JsonSchema;
use rust_config_tree::ConfigSchema;

#[derive(Debug, Config, JsonSchema)]
struct AppConfig {
    #[config(default = [])]
    include: Vec<PathBuf>,

    #[config(nested)]
    #[schemars(extend("x-tree-split" = true))]
    database: DatabaseConfig,
}

#[derive(Debug, Config, JsonSchema)]
struct DatabaseConfig {
    #[config(env = "APP_DATABASE_URL")]
    url: String,

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

impl ConfigSchema for AppConfig {
    fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
        layer.include.clone().unwrap_or_default()
    }
}
}

Include 필드

include 필드는 어떤 이름이어도 됩니다. rust-config-treeConfigSchema::include_paths를 통해서만 이 필드를 압니다.

이 필드에는 일반적으로 빈 기본값을 두어야 합니다.

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

로더는 각 파일에 대해 부분적으로 로드된 레이어를 받습니다. 이를 통해 최종 스키마가 병합 및 검증되기 전에 자식 설정 파일을 발견할 수 있습니다.

중첩 섹션

구조화된 섹션에는 #[config(nested)]를 사용하세요. 중첩 섹션은 런타임 로딩에는 항상 사용됩니다. 중첩 필드를 독립적인 *.yaml 템플릿과 <section>.schema.json 스키마로도 생성해야 할 때 #[schemars(extend("x-tree-split" = true))]를 추가하세요.

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

자연스러운 YAML 형태는 다음과 같습니다.

server:
  bind: 127.0.0.1
  port: 8080

환경 변수 전용 필드

값을 환경 변수로만 제공해야 하고 생성된 설정 파일에는 나타나면 안 되는 leaf 필드는 #[schemars(extend("x-env-only" = true))]로 표시합니다. 생성된 YAML 템플릿과 JSON Schema는 env-only 필드를 생략하며, 이 생략으로 비게 된 부모 객체도 함께 제거합니다.

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

필드 값 유효성 검사

생성된 *.schema.json 파일은 IDE 완성과 기본 에디터 검사용입니다. 구체적인 필드 값이 애플리케이션에서 유효한지는 판단하지 않습니다.

필드 값 유효성 검사는 코드에서 #[config(validate = Self::validate)]로 구현합니다. 최종 설정을 load_config로 로드하거나 config-validate로 검사할 때 이 런타임 검증이 실행됩니다.

템플릿 섹션 override

템플릿 소스에 include가 없으면 crate는 x-tree-split로 표시한 중첩 스키마 섹션에서 자식 템플릿 파일을 derive할 수 있습니다. 기본 최상위 경로는 <section>.yaml입니다.

그 경로를 template_path_for_section으로 override합니다.

#![allow(unused)]
fn main() {
impl ConfigSchema for AppConfig {
    fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
        layer.include.clone().unwrap_or_default()
    }

    fn template_path_for_section(section_path: &[&str]) -> Option<PathBuf> {
        match section_path {
            ["database"] => Some(PathBuf::from("examples/database.yaml")),
            _ => None,
        }
    }
}
}

런타임 로딩

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

런타임 로딩은 의도적으로 Figment와 confique 사이에 나뉩니다.

figment:
  runtime file loading
  runtime environment loading
  runtime source metadata

confique:
  schema metadata
  defaults
  validation
  config templates

주요 API는 다음과 같습니다.

#![allow(unused)]
fn main() {
use rust_config_tree::load_config;

let config = load_config::<AppConfig>("config.yaml")?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

애플리케이션이 소스 메타데이터를 필요로 하면 load_config_with_figment를 사용하세요.

#![allow(unused)]
fn main() {
use rust_config_tree::load_config_with_figment;

let (config, figment) = load_config_with_figment::<AppConfig>("config.yaml")?;
let _ = (config, figment);
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

로딩 단계

상위 수준 로더는 다음 단계를 수행합니다.

  1. 루트 설정 경로를 사전식으로 해석합니다.
  2. 루트 설정 디렉터리에서 위로 올라가며 처음 발견한 .env 파일을 로드합니다.
  3. 각 설정 파일을 부분 레이어로 로드해 include를 발견합니다.
  4. 발견한 설정 파일에서 Figment 그래프를 빌드합니다.
  5. ConfiqueEnvProvider를 파일보다 높은 우선순위로 병합합니다.
  6. 선택적으로 애플리케이션별 CLI override를 병합합니다.
  7. Figment에서 confique 레이어를 추출합니다.
  8. confique 코드 기본값을 적용합니다.
  9. 최종 스키마를 검증하고 구성합니다.

load_configload_config_with_figment는 1-5단계와 7-9단계를 수행합니다. 6단계는 CLI 플래그가 스키마 필드에 어떻게 매핑되는지 이 crate가 추론할 수 없기 때문에 애플리케이션별입니다.

파일 형식

런타임 파일 프로바이더는 설정 경로 확장자에서 선택됩니다.

  • .yaml.yml은 YAML을 사용합니다.
  • .toml은 TOML을 사용합니다.
  • .json.json5는 JSON을 사용합니다.
  • 알 수 없거나 없는 확장자는 YAML을 사용합니다.

템플릿 생성은 YAML, TOML, JSON5 호환 출력에 대해 여전히 confique의 템플릿 렌더러를 사용합니다.

Include 우선순위

상위 수준 로더는 포함된 파일이 그 파일을 include한 파일보다 낮은 우선순위가 되도록 파일 프로바이더를 병합합니다. 루트 설정 파일이 가장 높은 파일 우선순위를 가집니다.

환경 변수는 모든 설정 파일보다 높은 우선순위를 가집니다. confique 기본값은 런타임 프로바이더가 제공하지 않은 값에만 사용됩니다.

build_config_figment 뒤에 CLI override를 병합하면 전체 우선순위는 다음과 같습니다.

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

명령줄 문법은 rust-config-tree가 정의하지 않습니다. 애플리케이션이 파싱한 값을 중첩 직렬화 프로바이더에 매핑하면 --server-port 같은 플래그가 server.port를 override할 수 있습니다. 점이 있는 --server.port 또는 a.b.c 문법은 애플리케이션이 구현한 경우에만 존재합니다.

즉 CLI 우선순위는 애플리케이션의 override 프로바이더에 있는 키에만 적용됩니다. 한 번의 실행에서 자주 바뀌는 운영 값을 위해 사용하고, 지속적인 설정은 파일에 남겨두세요.

#![allow(unused)]
fn main() {
use figment::providers::Serialized;
use serde::Serialize;
use rust_config_tree::{build_config_figment, load_config_from_figment};

#[derive(Debug, Serialize)]
struct CliOverrides {
    #[serde(skip_serializing_if = "Option::is_none")]
    server: Option<CliServerOverrides>,
}

#[derive(Debug, Serialize)]
struct CliServerOverrides {
    #[serde(skip_serializing_if = "Option::is_none")]
    port: Option<u16>,
}

let cli_overrides = CliOverrides {
    server: Some(CliServerOverrides { port: Some(9000) }),
};

let figment = build_config_figment::<AppConfig>("config.yaml")?
    .merge(Serialized::defaults(cli_overrides));

let config = load_config_from_figment::<AppConfig>(&figment)?;
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

환경 변수

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

환경 변수 이름은 confique로 스키마에 선언합니다.

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

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

rust-config-treeconfique::Config::META에서 이 이름을 읽고 각 환경 변수를 정확한 필드 경로에 매핑하는 Figment 프로바이더를 만듭니다.

이 crate에는 delimiter 기반 Figment 환경 매핑을 사용하지 마세요.

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

split("_")는 underscore를 중첩 키 구분자로 처리합니다. 그러면 APP_DATABASE_POOL_SIZEdatabase.pool.size 같은 경로가 되어 pool_size 같은 Rust 필드 이름과 충돌합니다.

ConfiqueEnvProvider를 사용하면 이 매핑은 명시적입니다.

APP_DATABASE_POOL_SIZE -> database.pool_size

단일 underscore는 환경 변수 이름의 일부로 남습니다. Figment는 중첩 규칙을 추측하지 않습니다.

Dotenv 로딩

런타임 프로바이더가 평가되기 전에 로더는 루트 설정 파일의 디렉터리에서 위로 올라가며 .env 파일을 찾습니다.

기존 프로세스 환경 변수는 보존됩니다. .env의 값은 빠진 환경 변수만 채웁니다.

예:

APP_SERVER_PORT=9000
APP_DATABASE_POOL_SIZE=64

스키마가 대응하는 #[config(env = "...")] 속성을 선언하면 이 변수들은 설정 파일 값을 override합니다.

값 파싱

브리지 프로바이더는 Figment가 환경 값을 파싱하게 합니다. confiqueparse_env hook은 호출하지 않습니다. Figment 환경 값 문법이 타입에 잘 맞지 않는 복잡한 값은 설정 파일에 두세요.

소스 추적

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

런타임 로딩에 사용한 Figment 그래프를 보존하려면 load_config_with_figment를 사용하세요.

#![allow(unused)]
fn main() {
use rust_config_tree::load_config_with_figment;

let (config, figment) = load_config_with_figment::<AppConfig>("config.yaml")?;
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

반환된 Figment 값은 런타임 값의 소스 질문에 답할 수 있습니다.

#![allow(unused)]
fn main() {
if let Some(metadata) = figment.find_metadata("database.pool_size") {
    let source = metadata.interpolate(
        &figment::Profile::Default,
        &["database", "pool_size"],
    );

    println!("database.pool_size came from {source}");
}
}

ConfiqueEnvProvider가 제공한 값의 경우 interpolation은 스키마에 선언된 원래 환경 변수 이름을 반환합니다.

database.pool_size came from APP_DATABASE_POOL_SIZE

TRACE 이벤트

로더는 tracing::trace!로 소스 추적 이벤트를 내보냅니다. TRACE가 활성화된 경우에만 수행됩니다.

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

let (config, figment) = load_config_with_figment::<AppConfig>("config.yaml")?;

// If the tracing subscriber is initialized after config loading, emit the
// same source events again after installing the subscriber.
trace_config_sources::<AppConfig>(&figment);
let _ = config;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

각 이벤트는 rust_config_tree::config target을 사용하고 다음을 포함합니다.

  • config_key: 점으로 구분된 설정 키.
  • source: 렌더링된 소스 메타데이터.

confique 기본값에서만 온 값에는 Figment 런타임 메타데이터가 없습니다. 이런 값은 confique default or unset optional field로 보고됩니다.

템플릿 생성

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

템플릿은 런타임에서 사용하는 것과 같은 confique 스키마에서 생성됩니다. confique가 doc comment, 기본값, 필수 필드, 선언된 환경 변수 이름을 포함한 실제 템플릿 내용을 렌더링합니다.

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

루트 설정과 분할된 중첩 섹션의 Draft 7 JSON Schema를 생성합니다.

#![allow(unused)]
fn main() {
use rust_config_tree::write_config_schemas;

write_config_schemas::<AppConfig>("schemas/myapp.schema.json")?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

중첩 필드를 자체 *.yaml 템플릿과 자체 <section>.schema.json 스키마로 생성하려면 #[schemars(extend("x-tree-split" = true))]를 붙입니다. 표시하지 않은 중첩 필드는 부모 템플릿과 부모 스키마에 남습니다.

값을 환경 변수로만 제공해야 하는 leaf 필드에는 #[schemars(extend("x-env-only" = true))]를 붙입니다. 생성된 템플릿과 JSON Schema는 env-only 필드를 생략하며, 이 생략으로 비게 된 부모 객체도 함께 제거합니다.

생성된 스키마는 required 제약을 생략합니다. IDE는 여전히 완성을 제공하지만 log.yaml 같은 부분 파일에 대해 빠진 루트 필드를 보고하지 않습니다. 루트 스키마는 루트 파일에 속하는 필드만 완성합니다. 분할된 섹션 필드는 여기서 생략되고 자체 섹션 스키마에서 완성됩니다. 존재하는 필드는 타입, enum, 알 수 없는 프로퍼티 같은 기본 에디터 검사를 받을 수 있습니다. 생성된 *.schema.json은 구체적인 필드 값이 애플리케이션에서 유효한지는 판단하지 않습니다. 필드 값 유효성 검사는 코드에서 #[config(validate = Self::validate)]로 구현하고, load_config 또는 config-validate로 실행합니다.

생성된 TOML, YAML, JSON 및 JSON5 템플릿에서 이 스키마를 바인딩합니다.

#![allow(unused)]
fn main() {
use rust_config_tree::write_config_templates_with_schema;

write_config_templates_with_schema::<AppConfig>(
    "config.toml",
    "config.example.toml",
    "schemas/myapp.schema.json",
)?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

TOML 및 YAML 루트 템플릿은 루트 스키마를 바인딩하며 자식 섹션 필드를 완성하지 않습니다. 분할 섹션 YAML 템플릿은 해당 섹션 스키마를 바인딩합니다. JSON 및 JSON5 템플릿은 VS Code가 인식할 수 있는 루트 $schema 필드를 받습니다. VS Code json.schemas는 대체 바인딩 경로로 유지됩니다.

출력 형식은 출력 경로에서 추론됩니다.

  • .yaml.yml은 YAML을 생성합니다.
  • .toml은 TOML을 생성합니다.
  • .json.json5는 JSON5 호환 템플릿을 생성합니다.
  • 알 수 없거나 없는 확장자는 YAML을 생성합니다.

스키마 바인딩

스키마 경로가 schemas/myapp.schema.json이면 생성된 루트 템플릿은 다음을 사용합니다.

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

생성된 섹션 템플릿은 섹션 스키마를 바인딩합니다.

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

생성된 JSON 및 JSON5 템플릿은 VS Code가 인식하는 루트 $schema 필드를 씁니다. 에디터 설정은 여전히 선택 사항입니다:

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

템플릿 소스 선택

템플릿 생성은 다음 순서로 소스 트리를 선택합니다.

  1. 기존 설정 경로.
  2. 기존 출력 템플릿 경로.
  3. 새 빈 템플릿 트리로 취급한 출력 경로.

이를 통해 프로젝트는 현재 설정 파일에서 템플릿을 업데이트하거나, 기존 템플릿 집합을 업데이트하거나, 스키마만으로 새 템플릿 집합을 만들 수 있습니다.

미러링된 include 트리

소스 파일이 include를 선언하면 생성된 템플릿은 출력 디렉터리 아래에 해당 include 경로를 미러링합니다.

# config.yaml
include:
  - server.yaml

config.example.yaml을 생성하면 다음을 씁니다.

config.example.yaml
server.yaml

상대 include 대상은 출력 파일의 부모 디렉터리 아래에 미러링됩니다. 절대 include 대상은 절대 경로 그대로 유지됩니다.

opt-in 섹션 분할

소스 파일에 include가 없으면 crate는 x-tree-split로 표시한 중첩 스키마 섹션에서 include 대상을 derive할 수 있습니다. 표시한 server 섹션이 있는 스키마의 경우 빈 루트 템플릿 소스는 다음을 생성할 수 있습니다.

config.example.yaml
server.yaml

루트 템플릿은 include 블록을 받고, server.yaml에는 server 섹션만 포함됩니다. 중첩 섹션은 해당 필드도 x-tree-split를 가질 때만 재귀적으로 분할됩니다.

IDE 완성

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

생성된 JSON Schema는 TOML, YAML, JSON, JSON5 설정 파일에서 사용할 수 있습니다. 스키마는 confique가 사용하는 것과 같은 Rust 타입에서 생성됩니다.

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

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

다음으로 생성합니다.

#![allow(unused)]
fn main() {
use rust_config_tree::write_config_schemas;

write_config_schemas::<AppConfig>("schemas/myapp.schema.json")?;
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

이는 루트 스키마와 schemas/server.schema.json 같은 섹션 스키마를 씁니다. 생성된 스키마는 required 제약을 생략하므로 누락 필드 진단 없이 부분 설정 파일에서 완성이 동작합니다. 루트 스키마는 분할된 섹션 프로퍼티를 생략하므로, 자식 섹션 완성은 대응하는 섹션 스키마를 바인딩한 파일에서만 사용할 수 있습니다. 표시하지 않은 중첩 섹션은 루트 스키마에 남습니다.

x-env-only로 표시한 필드는 생성된 스키마에서 생략되므로, 환경 변수로만 제공해야 하는 secret이나 기타 값은 IDE가 완성하지 않습니다.

IDE 스키마는 완성과 기본 에디터 검사용입니다. 타입, enum, 알 수 없는 프로퍼티 검사처럼 생성된 스키마가 표현할 수 있는 범위만 다룹니다. 구체적인 필드 값이 애플리케이션에서 유효한지는 판단하지 않습니다. 필드 값 유효성 검사는 코드에서 #[config(validate = Self::validate)]로 구현하고, load_config 또는 config-validate로 실행합니다. 필수 필드와 최종 병합 설정 검증도 이 런타임 경로에서 처리합니다.

TOML

TOML 파일은 파일 맨 위의 #:schema 지시문으로 스키마를 바인딩해야 합니다.

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

[server]
bind = "0.0.0.0"
port = 3000

TOML에서 루트 $schema = "..." 필드를 사용하지 마세요. 이는 실제 설정 데이터가 되며 런타임 deserialization에 영향을 줄 수 있습니다. write_config_templates_with_schema는 TOML 템플릿에 #:schema 지시문을 자동으로 추가합니다.

YAML

YAML 파일은 YAML Language Server modeline을 사용해야 합니다.

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

server:
  bind: 0.0.0.0
  port: 3000

write_config_templates_with_schema는 YAML 템플릿에 이 modeline을 자동으로 추가합니다. 분할 YAML 템플릿은 섹션 스키마를 바인딩합니다. 예를 들어 log.yaml./schemas/log.schema.json를 바인딩합니다.

JSON

JSON 및 JSON5 파일은 루트 $schema 필드로 스키마를 바인딩할 수 있습니다. write_config_templates_with_schema는 생성된 JSON 및 JSON5 템플릿에 이 필드를 자동으로 추가합니다.

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

파일 안 바인딩을 원하지 않는 프로젝트에서는 에디터 설정도 계속 사용할 수 있습니다.

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

YAML도 VS Code 설정으로 바인딩할 수 있습니다.

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

최종 레이아웃은 다음과 같습니다.

schemas/myapp.schema.json:
  루트 파일 필드만

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

참고:

CLI 통합

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

ConfigCommand는 재사용 가능한 clap 하위 명령을 제공합니다.

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

이 내장 하위 명령은 애플리케이션별 설정 override 플래그와 분리되어 있습니다. 설정 override 플래그는 런타임 로딩 경로에서 Figment 프로바이더로 병합하세요.

설정 override 플래그는 사용 애플리케이션 CLI의 일부로 남습니다. 이름이 점으로 구분된 설정 경로와 일치할 필요는 없습니다. 예를 들어 애플리케이션은 --server-port를 파싱하고 중첩 server.port 설정 키에 매핑할 수 있습니다. 애플리케이션이 CliOverrides에 매핑한 플래그만 설정 값에 영향을 줍니다.

애플리케이션 명령 enum에 flatten합니다.

  1. 애플리케이션 자체 Parser 타입을 유지합니다.
  2. 애플리케이션 자체 Subcommand enum을 유지합니다.
  3. 해당 enum에 #[command(flatten)] Config(ConfigCommand)를 추가합니다.
  4. Clap은 flatten된 ConfigCommand variant를 애플리케이션 자체 variant와 같은 명령 레벨로 확장합니다.
  5. Config(command) variant를 match하고 handle_config_command에 전달합니다.
use std::path::PathBuf;

use clap::{Parser, Subcommand};
use confique::Config;
use schemars::JsonSchema;
use rust_config_tree::{ConfigCommand, ConfigSchema, handle_config_command, load_config};

#[derive(Debug, Config, JsonSchema)]
struct AppConfig {
    #[config(default = [])]
    include: Vec<PathBuf>,
}

impl ConfigSchema for AppConfig {
    fn include_paths(layer: &<Self as Config>::Layer) -> Vec<PathBuf> {
        layer.include.clone().unwrap_or_default()
    }
}

#[derive(Debug, Parser)]
#[command(name = "demo")]
struct Cli {
    #[arg(long, default_value = "config.yaml")]
    config: PathBuf,

    #[command(subcommand)]
    command: Command,
}

#[derive(Debug, Subcommand)]
enum Command {
    Run,

    #[command(flatten)]
    Config(ConfigCommand),
}

fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let cli = Cli::parse();

    match cli.command {
        Command::Run => {
            let config = load_config::<AppConfig>(&cli.config)?;
            println!("{config:#?}");
        }
        Command::Config(command) => {
            handle_config_command::<Cli, AppConfig>(command, &cli.config)?;
        }
    }

    Ok(())
}

설정 템플릿

demo config-template

명령은 config/<root_config_name>/ 아래에 템플릿을 씁니다. --output이 경로를 받으면 파일 이름만 사용합니다. 출력 파일 이름을 제공하지 않으면 config/<root_config_name>/<root_config_name>.example.yaml을 씁니다. 생성된 TOML, YAML, JSON 및 JSON5 템플릿을 생성된 JSON Schema에 바인딩하려면 --schema schemas/myapp.schema.json를 추가하세요. 분할 YAML 템플릿은 대응하는 섹션 스키마를 바인딩합니다. JSON 및 JSON5 템플릿은 VS Code가 인식하는 $schema 필드를 받습니다. 이 명령은 루트 및 섹션 스키마도 선택한 스키마 경로에 씁니다.

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

루트 및 섹션 JSON Schema를 생성합니다.

demo config-schema

--output이 없으면 config-schema는 루트 스키마를 config/<root_config_name>/<root_config_name>.schema.json에 씁니다.

전체 런타임 설정 트리를 검증합니다.

demo config-validate

생성된 에디터 스키마는 분할 파일에 대해 의도적으로 필수 필드 진단을 피합니다. config-validate는 include를 로드하고 기본값을 적용한 뒤 최종 confique 검증을 실행합니다. 여기에는 #[config(validate = Self::validate)]로 선언한 validator도 포함됩니다. 생성된 *.schema.json은 IDE 완성과 기본 에디터 검사용이며, 필드 값 유효성 판단에는 사용하지 않습니다. 검증이 성공하면 Configuration is ok를 출력합니다.

셸 완성

완성을 stdout으로 출력합니다.

demo completions zsh

완성을 설치합니다.

demo install-completions zsh

완성을 제거합니다.

demo uninstall-completions zsh

설치기는 Bash, Elvish, Fish, PowerShell, Zsh를 지원합니다. 사용자 홈 디렉터리 아래에 완성 파일을 쓰고, 필요한 셸에 대해서는 셸 시작 파일을 업데이트합니다.

기존 셸 시작 파일을 변경하기 전에, 예를 들어 ~/.zshrc, ~/.bashrc, Elvish rc 파일 또는 PowerShell profile을 변경하기 전에, 명령은 원본 파일 옆에 백업을 씁니다.

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

예제

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

저장소에는 설정 트리 로딩, CLI override, 내장 설정 명령, 템플릿 생성, 낮은 수준의 트리 API를 위한 실행 가능한 예제가 포함되어 있습니다.

저장소 예제 색인을 읽어보세요.

저장소 루트에서 예제를 실행합니다.

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

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

애플리케이션이 confique를 사용하지 않거나 순회 결과에 직접 접근해야 한다면 낮은 수준의 트리 API를 사용하세요.

#![allow(unused)]
fn main() {
use std::{
    fs,
    io,
    path::{Path, PathBuf},
};

use rust_config_tree::{ConfigSource, load_config_tree};

fn load_source(path: &Path) -> io::Result<ConfigSource<String>> {
    let content = fs::read_to_string(path)?;
    let includes = content
        .lines()
        .filter_map(|line| line.strip_prefix("include: "))
        .map(PathBuf::from)
        .collect();

    Ok(ConfigSource::new(content, includes))
}

let tree = load_config_tree("config.yaml", load_source)?;

for node in tree.nodes() {
    println!("{}", node.path().display());
}
Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
}

순회 규칙

트리 로더는 다음을 수행합니다.

  • 소스 경로를 사전식으로 정규화합니다.
  • 빈 include 경로를 거부합니다.
  • 상대 include를 선언한 파일 기준으로 해석합니다.
  • 절대 include 경로를 보존합니다.
  • 재귀 include 순환을 감지합니다.
  • 다른 include branch를 통해 이미 로드된 파일을 건너뜁니다.

ConfigTreeOptions는 같은 레벨 include 순서를 뒤집을 수 있습니다.

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

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

경로 헬퍼

경로 헬퍼는 사전식 처리만 합니다. symbolic link를 해석하지 않고 경로가 존재할 필요도 없습니다.

  • 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

이 저장소는 mdBook과 GitHub Pages로 매뉴얼을 게시합니다.

언어별 매뉴얼은 독립 mdBook 프로젝트입니다. 각 언어에는 자체 SUMMARY.md가 있으므로 왼쪽 사이드바에는 현재 언어의 페이지만 포함됩니다.

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

로컬에서 빌드합니다.

scripts/publish-pages.sh

생성된 사이트는 다음 위치에 쓰입니다.

target/mdbook

게시 workflow

.github/workflows/pages.yml의 workflow는 main push와 수동 dispatch에서 실행됩니다. 이 workflow는 다음을 수행합니다.

  1. 저장소를 checkout합니다.
  2. mdBook을 설치합니다.
  3. scripts/publish-pages.sh를 실행합니다.
  4. target/mdbook을 Pages artifact로 업로드합니다.
  5. artifact를 GitHub Pages에 배포합니다.

게시 URL은 다음과 같습니다.

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

Crate 릴리스

commit, push, Pages 배포, crate 게시를 포함한 전체 흐름:

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

저장소 루트에서 crate 릴리스 헬퍼를 사용하세요.

scripts/publish-crate.sh

기본 모드는 검사와 cargo publish --dry-run을 실행합니다. 검사가 통과한 뒤 crates.io에 게시하려면 다음을 사용합니다. 현재 버전이 이미 crates.io에 있으면 스크립트가 patch 버전을 자동으로 올립니다.

scripts/publish-crate.sh --execute

스크립트 사용법은 scripts/README.md에 요약되어 있습니다.