tin-config-load 0.2.0

A simple Rust library for loading configuration from files and environment variables
Documentation
use config::ConfigError as SourceError;
use std::fmt;

const DEFAULT_FILE_PATH: &str = "./config/default";
const DEFAULT_ENV_PREFIX: &str = "APP";

/// 配置文件 source 的注册信息
struct FileSource {
    path: String,
    required: bool,
}

/// 配置加载失败的错误类型
///
/// [`Build`](Self::Build):读取或解析任一配置源失败(如必选文件不存在);
/// [`Deserialize`](Self::Deserialize):配置已成功构建,但无法反序列化为目标类型。
#[derive(Debug)]
#[non_exhaustive]
pub enum ConfigError {
    Build(SourceError),
    Deserialize(SourceError),
}

impl fmt::Display for ConfigError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Build(e) => write!(f, "构建配置失败: {e}"),
            Self::Deserialize(e) => write!(f, "反序列化配置失败: {e}"),
        }
    }
}

impl std::error::Error for ConfigError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Build(e) | Self::Deserialize(e) => Some(e),
        }
    }
}

/// 配置加载器
///
/// 注册的文件(默认文件及 `file`/`optional_file` 添加的文件)在构建时按注册顺序先加载,
/// 后注册的文件覆盖先注册的文件;所有环境变量 source 随后按注册顺序加载,
/// 所有环境变量 source 随后按注册顺序加载,因此环境变量始终覆盖文件。
pub struct ConfigLoader {
    files: Vec<FileSource>,
    env_prefixes: Vec<String>,
}

impl Default for ConfigLoader {
    fn default() -> Self {
        Self::empty()
            .optional_file(DEFAULT_FILE_PATH)
            .env(DEFAULT_ENV_PREFIX)
    }
}

impl ConfigLoader {
    /// 创建不加载任何默认文件或环境变量的加载器
    pub fn empty() -> Self {
        Self {
            files: Vec::new(),
            env_prefixes: Vec::new(),
        }
    }

    /// 添加必选文件 source;文件缺失时构建失败
    pub fn file(mut self, path: &str) -> Self {
        self.files.push(FileSource {
            path: path.to_string(),
            required: true,
        });
        self
    }

    /// 添加可选文件 source;文件缺失时忽略
    pub fn optional_file(mut self, path: &str) -> Self {
        self.files.push(FileSource {
            path: path.to_string(),
            required: false,
        });
        self
    }

    /// 添加环境变量 source;同键时后添加的 source 覆盖先添加的
    pub fn env(mut self, prefix: &str) -> Self {
        self.env_prefixes.push(prefix.to_string());
        self
    }

    /// 添加默认配置文件(可选),等价于 `optional_file("./config/default")`
    pub fn default_file(self) -> Self {
        self.optional_file(DEFAULT_FILE_PATH)
    }

    /// 添加默认环境变量前缀,等价于 `env("APP")`
    pub fn default_env(self) -> Self {
        self.env(DEFAULT_ENV_PREFIX)
    }

    /// 构建并反序列化为目标类型
    pub fn build<T: serde::de::DeserializeOwned>(self) -> Result<T, ConfigError> {
        let mut builder = config::Config::builder();
        for file in &self.files {
            builder =
                builder.add_source(config::File::with_name(&file.path).required(file.required));
        }
        for prefix in &self.env_prefixes {
            builder = builder.add_source(config::Environment::with_prefix(prefix));
        }
        let built = builder.build().map_err(ConfigError::Build)?;
        built.try_deserialize().map_err(ConfigError::Deserialize)
    }
}