← 中文资源

简体中文版本

Figment 分层环境配置组合

用 figment 组合配置层:默认值打底、TOML 文件覆盖、APP 前缀环境变量压顶,双下划线映射嵌套键,抽取失败带层级定位。

阅读英文原文

示例说明

figment 的模型是层叠融合:from 设定基线,merge 逐层覆盖,后合并者优先。Serialized::defaults 把结构体实例直接作为默认层——默认值与类型定义同处维护;Env 的 split 双下划线模式让扁平环境变量映射到嵌套结构:APP_DATABASE__URL 对应 database.url,运维侧无需理解配置内部形状。

技术要点

  • 层叠顺序即优先级后压先
  • 双下划线映射嵌套配置键
  • extract 严格校验并报告层级

代码解读

layered_config 用结构体实例做默认层,依次合并 TOML 文件与 APP 前缀环境变量;extract 把合并结果反序列化为 AppConfig——类型不符或缺失会带位置信息报错。main 设置两个环境变量演示覆盖:environment 变 production、连接池变二十四,其余字段保持下层值。

使用建议

配置文件缺失不报错是 figment 默认行为,关键文件要显式校验存在。密钥走独立 provider 而非环境变量明文。测试里用 Provider trait 注入内存层。

代码示例

use figment::{
    providers::{Env, Format, Serialized, Toml},
    Figment,
};
use serde::{Deserialize, Serialize};
use std::time::Duration;

#[derive(Debug, Serialize, Deserialize, Clone)]
struct DatabaseConfig {
    url: String,
    pool_size: u32,
    connect_timeout_ms: u64,
}

#[derive(Debug, Serialize, Deserialize)]
struct AppConfig {
    environment: String,
    port: u16,
    database: DatabaseConfig,
}

fn layered_config() -> Result<AppConfig, figment::Error> {
    let defaults = AppConfig {
        environment: "development".into(),
        port: 3000,
        database: DatabaseConfig {
            url: "postgres://localhost/app".into(),
            pool_size: 8,
            connect_timeout_ms: 3000,
        },
    };

    Figment::from(Serialized::defaults(defaults)) // 内置默认值打底
        .merge(Toml::file("config/app.toml")) // 配置文件覆盖
        .merge(Env::prefixed("APP_").split("__")) // 环境变量压顶
        .extract()
}

fn main() {
    // APP_DATABASE__URL 等:双下划线映射嵌套键
    std::env::set_var("APP_ENVIRONMENT", "production");
    std::env::set_var("APP_DATABASE__POOL_SIZE", "24");

    match layered_config() {
        Ok(config) => {
            println!(
                "{} on port {} -> {} (pool {})",
                config.environment,
                config.port,
                config.database.url,
                config.database.pool_size
            );
            let timeout = Duration::from_millis(config.database.connect_timeout_ms);
            println!("connect timeout: {timeout:?}");
        }
        Err(err) => {
            // extract 失败会报告具体哪个字段在哪个层出了问题
            eprintln!("config error: {err}");
        }
    }
}