Skip to main content

tin_config_load/
config.rs

1use config::ConfigError as SourceError;
2use std::fmt;
3
4const DEFAULT_FILE_PATH: &str = "./config/default";
5const DEFAULT_ENV_PREFIX: &str = "APP";
6
7/// 配置文件 source 的注册信息
8struct FileSource {
9    path: String,
10    required: bool,
11}
12
13/// 配置加载失败的错误类型
14///
15/// [`Build`](Self::Build):读取或解析任一配置源失败(如必选文件不存在);
16/// [`Deserialize`](Self::Deserialize):配置已成功构建,但无法反序列化为目标类型。
17#[derive(Debug)]
18#[non_exhaustive]
19pub enum ConfigError {
20    Build(SourceError),
21    Deserialize(SourceError),
22}
23
24impl fmt::Display for ConfigError {
25    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26        match self {
27            Self::Build(e) => write!(f, "构建配置失败: {e}"),
28            Self::Deserialize(e) => write!(f, "反序列化配置失败: {e}"),
29        }
30    }
31}
32
33impl std::error::Error for ConfigError {
34    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
35        match self {
36            Self::Build(e) | Self::Deserialize(e) => Some(e),
37        }
38    }
39}
40
41/// 配置加载器
42///
43/// 注册的文件(默认文件及 `file`/`optional_file` 添加的文件)在构建时按注册顺序先加载,
44/// 后注册的文件覆盖先注册的文件;所有环境变量 source 随后按注册顺序加载,
45/// 所有环境变量 source 随后按注册顺序加载,因此环境变量始终覆盖文件。
46pub struct ConfigLoader {
47    files: Vec<FileSource>,
48    env_prefixes: Vec<String>,
49}
50
51impl Default for ConfigLoader {
52    fn default() -> Self {
53        Self::empty()
54            .optional_file(DEFAULT_FILE_PATH)
55            .env(DEFAULT_ENV_PREFIX)
56    }
57}
58
59impl ConfigLoader {
60    /// 创建不加载任何默认文件或环境变量的加载器
61    pub fn empty() -> Self {
62        Self {
63            files: Vec::new(),
64            env_prefixes: Vec::new(),
65        }
66    }
67
68    /// 添加必选文件 source;文件缺失时构建失败
69    pub fn file(mut self, path: &str) -> Self {
70        self.files.push(FileSource {
71            path: path.to_string(),
72            required: true,
73        });
74        self
75    }
76
77    /// 添加可选文件 source;文件缺失时忽略
78    pub fn optional_file(mut self, path: &str) -> Self {
79        self.files.push(FileSource {
80            path: path.to_string(),
81            required: false,
82        });
83        self
84    }
85
86    /// 添加环境变量 source;同键时后添加的 source 覆盖先添加的
87    pub fn env(mut self, prefix: &str) -> Self {
88        self.env_prefixes.push(prefix.to_string());
89        self
90    }
91
92    /// 添加默认配置文件(可选),等价于 `optional_file("./config/default")`
93    pub fn default_file(self) -> Self {
94        self.optional_file(DEFAULT_FILE_PATH)
95    }
96
97    /// 添加默认环境变量前缀,等价于 `env("APP")`
98    pub fn default_env(self) -> Self {
99        self.env(DEFAULT_ENV_PREFIX)
100    }
101
102    /// 构建并反序列化为目标类型
103    pub fn build<T: serde::de::DeserializeOwned>(self) -> Result<T, ConfigError> {
104        let mut builder = config::Config::builder();
105        for file in &self.files {
106            builder =
107                builder.add_source(config::File::with_name(&file.path).required(file.required));
108        }
109        for prefix in &self.env_prefixes {
110            builder = builder.add_source(config::Environment::with_prefix(prefix));
111        }
112        let built = builder.build().map_err(ConfigError::Build)?;
113        built.try_deserialize().map_err(ConfigError::Deserialize)
114    }
115}