use config::ConfigError as SourceError;
use std::fmt;
const DEFAULT_FILE_PATH: &str = "./config/default";
const DEFAULT_ENV_PREFIX: &str = "APP";
struct FileSource {
path: String,
required: bool,
}
#[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),
}
}
}
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(),
}
}
pub fn file(mut self, path: &str) -> Self {
self.files.push(FileSource {
path: path.to_string(),
required: true,
});
self
}
pub fn optional_file(mut self, path: &str) -> Self {
self.files.push(FileSource {
path: path.to_string(),
required: false,
});
self
}
pub fn env(mut self, prefix: &str) -> Self {
self.env_prefixes.push(prefix.to_string());
self
}
pub fn default_file(self) -> Self {
self.optional_file(DEFAULT_FILE_PATH)
}
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)
}
}