Skip to main content

tx_di_core/
config.rs

1//! 全局配置管理
2//!
3//! 从 TOML 配置文件加载配置,支持点分路径访问。
4//! 配置组件通过 `#[component(conf = "key")]` 自动反序列化。
5
6use std::fs;
7use std::path::{Path, PathBuf};
8
9use serde::de::DeserializeOwned;
10use toml::Value::Table;
11
12use crate::component::Component;
13use crate::scope::Scope;
14
15/// 全局配置文件
16pub struct AppAllConfig {
17    pub toml_value: toml::Value,
18}
19
20impl AppAllConfig {
21    /// 从指定路径或默认路径加载配置
22    pub fn new<P: Into<PathBuf>>(config_path: Option<P>) -> Self {
23        let final_config_path = if let Some(path) = config_path {
24            path.into()
25        } else {
26            // 默认使用可执行文件所在目录的 config/config.toml
27            let exe_path = std::env::current_exe().unwrap_or_else(|e| {
28                panic!(
29                    "[di] 无法获取可执行文件路径: {}。\n\
30                     请检查程序运行环境,或手动传入配置路径。",
31                    e
32                )
33            });
34
35            let config_dir = exe_path
36                .parent()
37                .unwrap_or_else(|| {
38                    panic!(
39                        "[di] 无法获取可执行文件父目录: {:?}。\n\
40                         请手动传入配置路径。",
41                        exe_path
42                    )
43                })
44                .join("config");
45
46            config_dir.join("config.toml")
47        };
48
49        crate::lifecycle::set_sys_config(
50            crate::lifecycle::CONFIG_PATH,
51            final_config_path.to_str().unwrap().to_string(),
52        );
53        let toml_value = Self::load_config(final_config_path.as_path());
54        AppAllConfig { toml_value }
55    }
56
57    /// 加载配置文件(如果存在)
58    ///
59    /// 配置文件不存在时返回空 Table(允许无配置运行)。
60    /// 配置文件存在但读取/解析失败时 panic,避免使用错误的默认值。
61    fn load_config(path: &Path) -> toml::Value {
62        if !path.exists() {
63            eprintln!("[di] 配置文件不存在: {:?},将使用默认配置", path);
64            return Table(toml::map::Map::new());
65        }
66
67        let content = fs::read_to_string(path).unwrap_or_else(|e| {
68            panic!(
69                "[di] 配置文件读取失败: {:?}\n\
70                 错误: {}\n\
71                 请检查文件权限和路径是否正确。",
72                path, e
73            )
74        });
75
76        let config: toml::Value = toml::from_str(&content).unwrap_or_else(|e| {
77            panic!(
78                "[di] 配置文件解析失败: {:?}\n\
79                 错误: {}\n\
80                 请检查 TOML 语法是否正确。",
81                path, e
82            )
83        });
84        eprintln!("[di] 配置文件加载成功: {:?}", path);
85        config
86    }
87
88    /// 获取配置值并反序列化
89    pub fn get<T: DeserializeOwned>(&self, key: &str) -> Option<T> {
90        let value = self.get_value(key)?;
91        T::deserialize(value.clone()).ok()
92    }
93
94    /// 获取配置值或默认值
95    pub fn get_or_default<T: DeserializeOwned>(&self, key: &str, default: T) -> T {
96        self.get(key).unwrap_or(default)
97    }
98
99    /// 获取原始 TOML 值
100    pub fn get_value(&self, key: &str) -> Option<&toml::Value> {
101        let keys: Vec<&str> = key.split('.').collect();
102        let mut current = &self.toml_value;
103        for k in keys {
104            current = current.get(k)?;
105        }
106        Some(current)
107    }
108}
109
110// AppAllConfig 特殊处理:不走标准 Component 流程
111// 它在 BuildContext::new() 阶段直接构造并放入 Store
112impl Component for AppAllConfig {
113    type Deps = ();
114
115    fn build(_deps: Self::Deps) -> Self {
116        panic!("[di] AppAllConfig 只在 BuildContext::new() 内部构建,不应通过 Component::build() 调用")
117    }
118
119    const SCOPE: Scope = Scope::Singleton;
120}