tin_config_load/
config.rs1use config::ConfigError as SourceError;
2use std::fmt;
3
4const DEFAULT_FILE_PATH: &str = "./config/default";
5const DEFAULT_ENV_PREFIX: &str = "APP";
6
7struct FileSource {
9 path: String,
10 required: bool,
11}
12
13#[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
41pub 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 pub fn empty() -> Self {
62 Self {
63 files: Vec::new(),
64 env_prefixes: Vec::new(),
65 }
66 }
67
68 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 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 pub fn env(mut self, prefix: &str) -> Self {
88 self.env_prefixes.push(prefix.to_string());
89 self
90 }
91
92 pub fn default_file(self) -> Self {
94 self.optional_file(DEFAULT_FILE_PATH)
95 }
96
97 pub fn default_env(self) -> Self {
99 self.env(DEFAULT_ENV_PREFIX)
100 }
101
102 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}