secra-config

Crates.iosecra-config
lib.rssecra-config
version0.1.1
created_at2026-01-20 09:55:16.03877+00
updated_at2026-01-20 11:17:26.297193+00
descriptionAn extensible Rust config loader: load from TOML file path with modular get/get_as APIs, designed to extend to Consul/Nacos.
homepage
repositoryhttps://github.com/BadassFree/secra-config
max_upload_size
id2056253
size35,921
(BadassFree)

documentation

README

secra-config

一个可扩展的 Rust 配置获取库:先支持从指定 TOML 文件路径加载配置,并提供统一的 get(module_name) 接口获取模块配置;后续可以通过实现 ConfigSource 轻松接入 Consul / Nacos 等配置中心。

特性

  • TOML 文件加载:给出配置文件路径即可加载。
  • 模块化读取get(module_name) 返回对应模块配置(JSON 视图)。
  • 强类型读取get_as::<T>(module_name) 直接反序列化成你的结构体。
  • 可扩展来源:抽象 ConfigSource,未来新增 Consul/Nacos 只需实现一个 Source。
  • 性能友好
    • 读路径轻量(内部是 RwLock:多读共享、少写互斥)。
    • 支持 reload_if_changed():指纹未变则跳过加载/解析。
  • 安全考虑
    • TOML 文件默认限制最大体积(防止超大文件导致内存/解析 DoS)。

安装

在你的项目 Cargo.toml 中添加依赖(示例):

[dependencies]
secra-config = { path = "../secra-config" }

如果你发布到 crates.io 后,可以改为版本号依赖。

配置文件格式约定(TOML)

本库约定:TOML 顶层 key 即 module 名称。每个 module 是一个 TOML table。

示例 config.toml

[db]
url = "postgres://localhost"
pool = 10

[redis]
addr = "127.0.0.1:6379"
db = 0

[feature]
enabled = true

读取时:

  • get("db") 得到 db 的整体配置
  • get("redis") 得到 redis 的整体配置

快速上手

1) 从 TOML 文件路径加载

use secra_config::Config;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 1) 给出 toml 配置文件路径
    let cfg = Config::from_toml_file("config.toml")?;

    // 2) get(module_name) 获取模块配置(JSON 视图)
    let db = cfg.get("db").expect("missing [db] module");
    let url = db.get("url").and_then(|v| v.as_str()).unwrap_or_default();
    println!("db.url = {url}");

    Ok(())
}

1.1) 按环境(prod/dev/local)选择配置文件(新增)

默认会从环境变量判断当前环境,并在指定目录下选择不同的 TOML 文件:

  • prod => config.prod.toml
  • dev => config.dev.toml
  • local => config.local.toml

推荐的目录结构(示例):

your-app/
  config/
    config.dev.toml
    config.prod.toml
    config.local.toml

环境变量优先级(从高到低):

  • SECRA_CONFIG_PATH:直接指定配置文件路径(最高优先级)
  • SECRA_CONFIG_ENV / APP_ENV / RUST_ENV:设置环境(支持 prod|production, dev|development, local

常见用法(示例):

# 选择生产环境配置文件:./config/config.prod.toml
export SECRA_CONFIG_ENV=prod

# 或者直接指定绝对/相对路径(将忽略 SECRA_CONFIG_ENV 等)
export SECRA_CONFIG_PATH=./config/config.local.toml

示例:

use secra_config::Config;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 例如:在运行时设置 SECRA_CONFIG_ENV=prod
    // 然后从 ./config/ 目录下读取 config.prod.toml
    let cfg = Config::from_env_toml_dir("./config")?;
    let db = cfg.get("db").unwrap();
    println!("db = {db}");
    Ok(())
}

2) get(module_name):动态读取(JSON 视图)

get 返回 Option<serde_json::Value>,适合:

  • 你想“按需”读取某些字段
  • 配置结构经常变化/不想强绑定结构体
use secra_config::Config;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = Config::from_toml_file("config.toml")?;

    if let Some(feature) = cfg.get("feature") {
        let enabled = feature.get("enabled").and_then(|v| v.as_bool()).unwrap_or(false);
        println!("feature.enabled = {enabled}");
    }

    Ok(())
}

3) get_as::<T>(module_name):强类型读取(推荐)

强类型读取更适合:

  • 你希望编译期约束配置结构
  • IDE 补全更友好(DX 更好)
  • 错误更明确(缺字段/类型不符会报错)
use secra_config::Config;

#[derive(serde::Deserialize)]
struct DbCfg {
    url: String,
    pool: u32,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = Config::from_toml_file("config.toml")?;
    let db: DbCfg = cfg.get_as("db")?;
    println!("db.pool = {}", db.pool);
    Ok(())
}

重新加载与热更新

1) 手动刷新:reload()

当你希望在某个事件发生时(例如收到信号、管理接口触发)强制刷新配置:

use secra_config::Config;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = Config::from_toml_file("config.toml")?;

    // ... 运行中 ...
    cfg.reload()?; // 强制重新加载

    Ok(())
}

2) 按需刷新:reload_if_changed()

当你希望避免频繁“无意义解析”:

  • 指纹没变化 => 不加载、不解析,返回 Ok(false)
  • 指纹变化 => 重新加载,返回 Ok(true)
use secra_config::Config;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = Config::from_toml_file("config.toml")?;

    if cfg.reload_if_changed()? {
        println!("config changed and reloaded");
    }

    Ok(())
}

当前 TomlFileSource 使用 path + 文件长度 + mtime 作为 fingerprint(兼顾成本与效果)。

3) 轮询热加载(不自动起线程)

库不强行创建后台线程(更贴合服务端/异步运行时的控制权),提供一个轮询闭包,方便你放进自己的线程/任务里:

use secra_config::Config;
use std::time::Duration;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = Config::from_toml_file("config.toml")?;

    let mut tick = cfg.poll_reload_loop(Duration::from_secs(2));
    loop {
        tick()?; // 内部会 reload_if_changed + sleep
        // ... 你的业务逻辑 ...
    }
}

限制与安全:文件大小上限

默认最大 TOML 文件大小为 2MiB(可调整)。如果文件超过限制,会返回错误,避免超大文件导致内存/解析压力。

use secra_config::{Config, TomlFileSource};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let source = TomlFileSource::new("config.toml").with_max_bytes(4 * 1024 * 1024);
    let cfg = Config::from_source(source)?;
    let _ = cfg.get("db");
    Ok(())
}

扩展:接入 Consul / Nacos(实现 ConfigSource)

本库把“配置从哪里来”抽象为 ConfigSource

  • load():拉取并解析配置,返回 ConfigSnapshot
  • fingerprint():可选的变更检测(例如使用版本号、ETag、ModifyIndex、Revision 等)

你只需要实现它,就能复用 Config 的缓存、并发读、reload 逻辑。

示例:实现一个自定义 Source(伪代码)

use secra_config::{Config, ConfigError, ConfigSnapshot, ConfigSource};
use serde_json::json;
use std::collections::BTreeMap;
use std::time::SystemTime;

struct MySource;

impl ConfigSource for MySource {
    fn load(&self) -> Result<ConfigSnapshot, ConfigError> {
        // 1) 从外部系统/文件/数据库拿到原始配置(这里用示例数据代替)
        let mut modules = BTreeMap::new();
        modules.insert("db".to_string(), json!({ "url": "postgres://x", "pool": 10 }));

        Ok(ConfigSnapshot {
            modules,
            fingerprint: Some("v1".to_string()),
            loaded_at: SystemTime::now(),
        })
    }

    fn fingerprint(&self) -> Result<Option<String>, ConfigError> {
        // 例如返回 Consul 的 ModifyIndex / Nacos 的 revision / HTTP 的 ETag
        Ok(Some("v1".to_string()))
    }
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let cfg = Config::from_source(MySource)?;
    let db = cfg.get("db").unwrap();
    println!("{db}");
    Ok(())
}

建议的 Consul/Nacos 设计(落地建议)

  • fingerprint 优先使用后端的版本字段(成本低、准确度高)
    • Consul:ModifyIndex
    • Nacos:配置 md5 / revision(以官方返回为准)
  • load 内部做必要的校验与映射
    • 统一映射为 BTreeMap<String, serde_json::Value>,保持 Config 的通用性
  • 按需更新
    • 使用 reload_if_changed() 做轻量轮询
    • 或者把后端的 watch/long-poll 变更事件转成“触发 reload”的信号

常见问题(FAQ)

1) 为什么 get 返回 JSON,而不是 toml::Value?

为了更好的通用性与 DX:

  • JSON Value 在 Rust 生态中更常用(与 Web/日志/动态配置集成更方便)
  • get_as 直接走 serde_json::from_value,类型反序列化体验更统一

2) 多线程读写安全吗?

安全。内部使用 Arc<RwLock<ConfigSnapshot>>

  • 多线程高频读:共享读锁
  • 少量刷新:写锁替换快照

3) 我想把整个配置导出/打印怎么办?

使用 snapshot()

let snap = cfg.snapshot();
println!("{:?}", snap.modules.keys().collect::<Vec<_>>());
Commit count: 0

cargo fmt