use config::{Config, Environment, File};
use serde::{Deserialize, de::DeserializeOwned};
use std::{collections::HashMap, fmt::Debug, path::PathBuf};
use validator::{Validate, ValidationError};
#[derive(Debug, Clone)]
pub struct NamedConfig<T> {
configs: HashMap<String, T>,
}
impl<T> NamedConfig<T>
where
T: DeserializeOwned + Validate + Clone + Send + Sync + Default + 'static,
{
pub fn get(&self, name: &str) -> T {
self.configs.get(name).cloned().unwrap_or_default()
}
}
#[cfg(feature = "sender")]
pub(crate) fn i18n_config() -> Config {
let config_root = std::env::var("UNI_CONFIG_ROOT")
.map(PathBuf::from)
.unwrap_or(PathBuf::from("./config"));
match Config::builder()
.add_source(File::from(config_root.join("i18n")).required(false))
.build()
{
Ok(c) => c,
Err(e) => {
panic!("加载配置失败:{e}");
}
}
}
pub fn build_config() -> Config {
let config_root = std::env::var("UNI_CONFIG_ROOT")
.map(PathBuf::from)
.unwrap_or(PathBuf::from("./config"));
let env = std::env::var("UNI_ENV").unwrap_or("dev".to_owned());
match Config::builder()
.add_source(File::from(config_root.join("default")).required(false))
.add_source(File::from(config_root.join(env)).required(false))
.add_source(
Environment::with_prefix("UNI")
.separator("__")
.list_separator(","),
)
.build()
{
Ok(c) => c,
Err(e) => {
panic!("加载配置失败:{e}");
}
}
}
pub fn load_named_config<T>(config: &Config, section: &str) -> NamedConfig<T>
where
T: DeserializeOwned + Validate + Clone + Default,
{
let configs = match config.get::<HashMap<String, T>>(section) {
Ok(c) => c,
Err(e) => {
panic!("加载命名配置'{section}'失败:{e}");
}
};
for (key, cfg) in &configs {
if let Err(e) = cfg.validate() {
panic!("[{section}.{key}]命名配置验证失败:{e}");
}
}
NamedConfig { configs }
}
pub fn load_named_setting(
config: &config::Config,
section: &str,
) -> HashMap<String, HashMap<String, String>> {
let mut result = HashMap::new();
let input = config
.get::<HashMap<String, config::Value>>(section)
.unwrap_or(HashMap::new());
for (section, value) in input {
let map = match value.try_deserialize::<HashMap<String, String>>() {
Ok(c) => c,
Err(e) => {
panic!("加载命名配置'{section}'失败:{e}");
}
};
result.insert(section, map);
}
result
}
fn validate_key(key: &str) -> Result<(), ValidationError> {
let len = key.chars().count();
if len < 2 || len > 10 {
return Err(
ValidationError::new("cfg_key_length").with_message("长度应介于 2 到 10 之间".into())
);
}
if !key.chars().all(|c| c.is_ascii_alphabetic()) {
return Err(ValidationError::new("cfg_key").with_message("应为 ASCII 字母".into()));
}
Ok(())
}
#[derive(Debug, Deserialize, Validate, Clone)]
#[serde(default)]
pub struct SubscribeConfig {
#[validate(custom(function = "validate_key"))]
pub key: String,
pub hotspot: bool,
pub interval: u64,
pub low: usize,
pub high: usize,
pub retain: u64,
#[validate(range(min = 2, max = 120, message = "分钟时长应介于 2 到 120 之间"))]
pub latest: i64,
#[validate(range(min = 7, message = "信号量至少为 7"))]
pub sems: usize,
pub bufs: usize,
}
impl Default for SubscribeConfig {
fn default() -> Self {
Self {
key: String::default(),
hotspot: false,
interval: 30 * 60,
low: 200,
high: 20000,
retain: 2 * 24 * 60 * 60,
latest: 30,
sems: 100,
bufs: 1024,
}
}
}
#[derive(Debug, Deserialize, Validate, Clone)]
#[serde(default)]
pub struct SendConfig {
#[validate(custom(function = "validate_key"))]
pub key: String,
pub hotspot: bool,
pub interval: u64,
pub retain: u64,
#[validate(range(min = 7, message = "信号量至少为 7"))]
pub sems: usize,
pub bufs: usize,
}
impl Default for SendConfig {
fn default() -> Self {
Self {
key: String::default(),
hotspot: false,
interval: 5,
retain: 30,
sems: 100,
bufs: 1024,
}
}
}