Skip to main content

hiver_config/
config.rs

1//! Main configuration module
2//! 主配置模块
3//!
4//! # Equivalent to Spring Boot / 等价于 Spring Boot
5//!
6//! - `Config` - Spring `ConfigurableEnvironment`
7//! - `ConfigBuilder` - Builder pattern
8//! - `FileFormat` - Configuration file formats
9//! - `ReloadStrategy` - Configuration reload strategies
10
11use crate::{ConfigResult, PropertySource, Value, environment::Environment, error::ConfigError};
12use indexmap::IndexMap;
13use std::collections::HashMap;
14use std::path::{Path, PathBuf};
15use std::sync::{Arc, RwLock};
16
17/// Configuration file format
18/// 配置文件格式
19///
20/// Equivalent to Spring Boot's supported configuration formats.
21/// 等价于Spring Boot支持的配置格式。
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum FileFormat {
24    /// Properties file format
25    /// Properties文件格式
26    Properties,
27
28    /// YAML file format
29    /// YAML文件格式
30    Yaml,
31
32    /// TOML file format
33    /// TOML文件格式
34    Toml,
35
36    /// JSON file format
37    /// JSON文件格式
38    Json,
39}
40
41impl FileFormat {
42    /// Get file extensions for this format
43    /// 获取此格式的文件扩展名
44    pub fn extensions(&self) -> &[&str] {
45        match self {
46            FileFormat::Properties => &["properties", "props"],
47            FileFormat::Yaml => &["yaml", "yml"],
48            FileFormat::Toml => &["toml"],
49            FileFormat::Json => &["json"],
50        }
51    }
52
53    /// Detect format from file path
54    /// 从文件路径检测格式
55    pub fn from_path(path: &Path) -> Option<Self> {
56        let ext = path.extension()?.to_str()?.to_lowercase();
57        match ext.as_str() {
58            "properties" | "props" => Some(FileFormat::Properties),
59            "yaml" | "yml" => Some(FileFormat::Yaml),
60            "toml" => Some(FileFormat::Toml),
61            "json" => Some(FileFormat::Json),
62            _ => None,
63        }
64    }
65}
66
67/// Configuration reload strategy
68/// 配置重新加载策略
69///
70/// Equivalent to Spring Cloud Config refresh strategies.
71/// 等价于Spring Cloud Config刷新策略。
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum ReloadStrategy {
74    /// Never reload configuration
75    /// 从不重新加载配置
76    Never,
77
78    /// Reload on request
79    /// 按需重新加载
80    OnRequest,
81
82    /// Reload periodically (with interval in seconds)
83    /// 定期重新加载(间隔秒数)
84    Periodic(u64),
85
86    /// Watch for file changes
87    /// 监视文件更改
88    Watch,
89}
90
91/// Main configuration structure
92/// 主配置结构
93///
94/// Equivalent to Spring Boot's `ConfigurableEnvironment` and `ConfigFileApplicationListener`.
95/// 等价于Spring Boot的`ConfigFileApplicationListener`和`ConfigurableEnvironment`。
96///
97/// # Example / 示例
98///
99/// ```rust,no_run,ignore
100/// use hiver_config::Config;
101///
102/// let config = Config::builder()
103///     .add_file("config/application.yaml")
104///     .add_profile("dev")
105///     .build()?;
106/// ```
107#[derive(Debug, Clone)]
108pub struct Config {
109    /// Environment for property resolution
110    /// 属性解析的环境
111    environment: Arc<Environment>,
112
113    /// Configuration files loaded
114    /// 已加载的配置文件
115    files: Arc<RwLock<Vec<PathBuf>>>,
116
117    /// Reload strategy
118    /// 重新加载策略
119    reload_strategy: ReloadStrategy,
120
121    /// Configuration values cache
122    /// 配置值缓存
123    values: Arc<RwLock<IndexMap<String, Value>>>,
124}
125
126impl Config {
127    /// Create a new empty configuration
128    /// 创建新的空配置
129    pub fn new() -> Self {
130        Self {
131            environment: Arc::new(Environment::new()),
132            files: Arc::new(RwLock::new(Vec::new())),
133            reload_strategy: ReloadStrategy::Never,
134            values: Arc::new(RwLock::new(IndexMap::new())),
135        }
136    }
137
138    /// Create a configuration builder
139    /// 创建配置构建器
140    pub fn builder() -> ConfigBuilder {
141        ConfigBuilder::new()
142    }
143
144    /// Load configuration with default settings
145    /// 使用默认设置加载配置
146    ///
147    /// Default loading order / 默认加载顺序:
148    /// 1. application.properties (or .yaml, .toml, .json)
149    /// 2. application-{profile}.properties
150    /// 3. System environment variables
151    /// 4. Command line arguments
152    pub fn load() -> ConfigResult<Self> {
153        Self::builder().build()
154    }
155
156    /// Load configuration from a specific file
157    /// 从特定文件加载配置
158    pub fn from_file<P: AsRef<Path>>(path: P) -> ConfigResult<Self> {
159        Self::builder().add_file(path).build()
160    }
161
162    /// Add a property source
163    /// 添加属性源
164    pub fn add_property_source(&self, source: PropertySource) {
165        self.environment.add_property_source(source);
166        self.invalidate_cache();
167    }
168
169    /// Add a property source with highest priority.
170    /// 添加最高优先级的属性源。
171    pub fn add_property_source_first(&self, source: PropertySource) {
172        self.environment.add_property_source_first(source);
173        self.invalidate_cache();
174    }
175
176    /// Get a property value
177    /// 获取属性值
178    pub fn get(&self, key: &str) -> Option<Value> {
179        // Check cache first
180        if let Ok(cache) = self.values.read()
181            && let Some(value) = cache.get(key)
182        {
183            return Some(value.clone());
184        }
185
186        // Get from environment
187        let value = self.environment.get_property(key);
188
189        // Cache the value
190        if let Some(ref v) = value
191            && let Ok(mut cache) = self.values.write()
192        {
193            cache.insert(key.to_string(), v.clone());
194        }
195
196        value
197    }
198
199    /// Get a property as a specific type
200    /// 获取特定类型的属性
201    pub fn get_as<T>(&self, key: &str) -> ConfigResult<T>
202    where
203        T: serde::de::DeserializeOwned,
204    {
205        let value = self
206            .get(key)
207            .ok_or_else(|| ConfigError::MissingProperty(key.to_string()))?;
208
209        value.into()
210    }
211
212    /// Get a required property
213    /// 获取必需属性
214    pub fn get_required(&self, key: &str) -> ConfigResult<Value> {
215        self.get(key)
216            .ok_or_else(|| ConfigError::MissingProperty(key.to_string()))
217    }
218
219    /// Get a required property as a specific type
220    /// 获取特定类型的必需属性
221    pub fn get_required_as<T>(&self, key: &str) -> ConfigResult<T>
222    where
223        T: serde::de::DeserializeOwned,
224    {
225        let value = self.get_required(key)?;
226        value.into()
227    }
228
229    /// Get property with default value
230    /// 获取带默认值的属性
231    pub fn get_or<T>(&self, key: &str, default: T) -> T
232    where
233        T: serde::de::DeserializeOwned,
234    {
235        self.get_as(key).unwrap_or(default)
236    }
237
238    /// Check if a property exists
239    /// 检查属性是否存在
240    pub fn contains_key(&self, key: &str) -> bool {
241        self.get(key).is_some()
242    }
243
244    /// Get all properties starting with a prefix
245    /// 获取所有以指定前缀开头的属性
246    pub fn get_prefix(&self, prefix: &str) -> IndexMap<String, Value> {
247        let mut result = IndexMap::new();
248
249        let sources = self.environment.get_property_sources();
250        for source in sources {
251            for (key, value) in source.iter() {
252                if key.starts_with(prefix) {
253                    result.entry(key.clone()).or_insert(value.clone());
254                }
255            }
256        }
257
258        result
259    }
260
261    /// Get environment reference
262    /// 获取环境引用
263    pub fn environment(&self) -> &Environment {
264        &self.environment
265    }
266
267    /// Get loaded files
268    /// 获取已加载的文件
269    pub fn files(&self) -> Vec<PathBuf> {
270        self.files
271            .read()
272            .unwrap_or_else(std::sync::PoisonError::into_inner)
273            .clone()
274    }
275
276    /// Get reload strategy
277    /// 获取重新加载策略
278    pub fn reload_strategy(&self) -> ReloadStrategy {
279        self.reload_strategy
280    }
281
282    /// Reload configuration
283    /// 重新加载配置
284    pub fn reload(&self) -> ConfigResult<()> {
285        // Clear cache
286        self.invalidate_cache();
287
288        // Reload from files if reload strategy allows
289        if self.reload_strategy != ReloadStrategy::Never {
290            for file in self.files() {
291                self.load_file(&file)?;
292            }
293        }
294
295        Ok(())
296    }
297
298    /// Invalidate cache
299    /// 使缓存失效
300    fn invalidate_cache(&self) {
301        if let Ok(mut cache) = self.values.write() {
302            cache.clear();
303        }
304    }
305
306    /// Load configuration from file
307    /// 从文件加载配置
308    pub(crate) fn load_file<P: AsRef<Path>>(&self, path: P) -> ConfigResult<()> {
309        let path = path.as_ref();
310        let format = FileFormat::from_path(path)
311            .ok_or_else(|| ConfigError::InvalidFormat(format!("{:?}", path)))?;
312
313        let content = std::fs::read_to_string(path)?;
314
315        let source = match format {
316            FileFormat::Properties => self.parse_properties(&content),
317            FileFormat::Yaml => self.parse_yaml(&content),
318            FileFormat::Toml => self.parse_toml(&content),
319            FileFormat::Json => self.parse_json(&content),
320        }?;
321
322        let mut source = source;
323        source.set_file_path(path.to_path_buf());
324
325        self.environment.add_property_source(source);
326
327        if let Ok(mut files) = self.files.write() {
328            let path_buf = path.to_path_buf();
329            if !files.contains(&path_buf) {
330                files.push(path_buf);
331            }
332        }
333
334        Ok(())
335    }
336
337    /// Parse properties file content
338    /// 解析properties文件内容
339    fn parse_properties(&self, content: &str) -> ConfigResult<PropertySource> {
340        let mut map = HashMap::new();
341
342        for line in content.lines() {
343            let line = line.trim();
344            if line.is_empty() || line.starts_with('#') || line.starts_with('!') {
345                continue;
346            }
347
348            if let Some((key, value)) = line.split_once('=') {
349                let key = key.trim().to_string();
350                let value = Self::unescape_value(value.trim());
351                map.insert(key, Value::string(value));
352            }
353        }
354
355        Ok(PropertySource::with_map("application.properties", map))
356    }
357
358    /// Unescape property value
359    /// 反转义属性值
360    fn unescape_value(value: &str) -> String {
361        let mut result = String::new();
362        let mut chars = value.chars().peekable();
363
364        while let Some(c) = chars.next() {
365            if c == '\\' {
366                match chars.next() {
367                    Some('n') => result.push('\n'),
368                    Some('r') => result.push('\r'),
369                    Some('t') => result.push('\t'),
370                    Some('u') => {
371                        // Unicode escape \uXXXX
372                        let code: String = chars.by_ref().take(4).collect();
373                        if let Ok(code_point) = u32::from_str_radix(&code, 16)
374                            && let Some(c) = char::from_u32(code_point)
375                        {
376                            result.push(c);
377                        }
378                    },
379                    Some(next) => result.push(next),
380                    None => result.push('\\'),
381                }
382            } else {
383                result.push(c);
384            }
385        }
386
387        result
388    }
389
390    /// Parse YAML content
391    /// 解析YAML内容
392    fn parse_yaml(&self, content: &str) -> ConfigResult<PropertySource> {
393        let yaml: serde_yaml::Value =
394            serde_yaml::from_str(content).map_err(|e| ConfigError::Parse(e.to_string()))?;
395
396        let map = Self::yaml_to_map(&yaml)?;
397        Ok(PropertySource::with_map("application.yaml", map))
398    }
399
400    /// Convert YAML value to map
401    /// 将YAML值转换为映射
402    fn yaml_to_map(yaml: &serde_yaml::Value) -> ConfigResult<HashMap<String, Value>> {
403        let mut map = HashMap::new();
404
405        if let serde_yaml::Value::Mapping(mapping) = yaml {
406            for (key, value) in mapping {
407                if let serde_yaml::Value::String(key_str) = key {
408                    let value = Self::yaml_to_value(value)?;
409                    map.insert(key_str.clone(), value);
410                }
411            }
412        }
413
414        Ok(map)
415    }
416
417    /// Convert YAML value to our Value type
418    /// `将YAML值转换为我们的Value类型`
419    fn yaml_to_value(yaml: &serde_yaml::Value) -> ConfigResult<Value> {
420        Ok(match yaml {
421            serde_yaml::Value::Null | serde_yaml::Value::Tagged(_) => Value::Null,
422            serde_yaml::Value::Bool(v) => Value::Bool(*v),
423            serde_yaml::Value::Number(v) => {
424                if let Some(i) = v.as_i64() {
425                    Value::Integer(i)
426                } else if let Some(f) = v.as_f64() {
427                    Value::Float(f)
428                } else {
429                    Value::Null
430                }
431            },
432            serde_yaml::Value::String(v) => Value::String(v.clone()),
433            serde_yaml::Value::Sequence(v) => Value::List(
434                v.iter()
435                    .map(|x| Self::yaml_to_value(x))
436                    .collect::<ConfigResult<Vec<_>>>()?,
437            ),
438            serde_yaml::Value::Mapping(v) => Value::Object(
439                v.iter()
440                    .filter_map(|(k, v)| {
441                        k.as_str()
442                            .map(|key| (key.to_string(), Self::yaml_to_value(v).ok()))
443                    })
444                    .filter_map(|(k, v)| v.map(|val| (k, val)))
445                    .collect(),
446            ),
447        })
448    }
449
450    /// Parse TOML content
451    /// 解析TOML内容
452    fn parse_toml(&self, content: &str) -> ConfigResult<PropertySource> {
453        let toml: toml::Value =
454            toml::from_str(content).map_err(|e| ConfigError::Parse(e.to_string()))?;
455
456        let map = Self::toml_to_map(&toml)?;
457        Ok(PropertySource::with_map("application.toml", map))
458    }
459
460    /// Convert TOML value to map
461    /// 将TOML值转换为映射
462    fn toml_to_map(toml: &toml::Value) -> ConfigResult<HashMap<String, Value>> {
463        let mut map = HashMap::new();
464
465        if let toml::Value::Table(table) = toml {
466            for (key, value) in table {
467                map.insert(key.clone(), Self::toml_to_value(value));
468            }
469        }
470
471        Ok(map)
472    }
473
474    /// Convert TOML value to our Value type
475    /// `将TOML值转换为我们的Value类型`
476    fn toml_to_value(toml: &toml::Value) -> Value {
477        match toml {
478            toml::Value::Boolean(v) => Value::Bool(*v),
479            toml::Value::Integer(v) => Value::Integer(*v),
480            toml::Value::Float(v) => Value::Float(*v),
481            toml::Value::String(v) => Value::String(v.clone()),
482            toml::Value::Array(v) => Value::List(v.iter().map(Self::toml_to_value).collect()),
483            toml::Value::Table(table) => Value::Object(
484                table
485                    .iter()
486                    .map(|(k, v)| (k.clone(), Self::toml_to_value(v)))
487                    .collect(),
488            ),
489            toml::Value::Datetime(v) => Value::String(v.to_string()),
490        }
491    }
492
493    /// Parse JSON content
494    /// 解析JSON内容
495    fn parse_json(&self, content: &str) -> ConfigResult<PropertySource> {
496        let json: serde_json::Value =
497            serde_json::from_str(content).map_err(|e| ConfigError::Parse(e.to_string()))?;
498
499        let map = Self::json_to_map(&json)?;
500        Ok(PropertySource::with_map("application.json", map))
501    }
502
503    /// Convert JSON value to map
504    /// 将JSON值转换为映射
505    fn json_to_map(json: &serde_json::Value) -> ConfigResult<HashMap<String, Value>> {
506        let mut map = HashMap::new();
507
508        if let serde_json::Value::Object(obj) = json {
509            for (key, value) in obj {
510                map.insert(key.clone(), Self::json_to_value(value));
511            }
512        }
513
514        Ok(map)
515    }
516
517    /// Convert JSON value to our Value type
518    /// `将JSON值转换为我们的Value类型`
519    fn json_to_value(json: &serde_json::Value) -> Value {
520        match json {
521            serde_json::Value::Null => Value::Null,
522            serde_json::Value::Bool(v) => Value::Bool(*v),
523            serde_json::Value::Number(v) => {
524                if let Some(i) = v.as_i64() {
525                    Value::Integer(i)
526                } else if let Some(f) = v.as_f64() {
527                    Value::Float(f)
528                } else {
529                    Value::Null
530                }
531            },
532            serde_json::Value::String(v) => Value::String(v.clone()),
533            serde_json::Value::Array(v) => Value::List(v.iter().map(Self::json_to_value).collect()),
534            serde_json::Value::Object(obj) => Value::Object(
535                obj.iter()
536                    .map(|(k, v)| (k.clone(), Self::json_to_value(v)))
537                    .collect(),
538            ),
539        }
540    }
541}
542
543impl Default for Config {
544    fn default() -> Self {
545        Self::new()
546    }
547}
548
549/// Configuration builder
550/// 配置构建器
551///
552/// Equivalent to Spring Boot's `ConfigFileApplicationListener` configuration.
553/// 等价于Spring Boot的`ConfigFileApplicationListener`配置。
554pub struct ConfigBuilder {
555    config: Config,
556}
557
558impl ConfigBuilder {
559    /// Create a new builder
560    /// 创建新的构建器
561    pub fn new() -> Self {
562        Self {
563            config: Config::new(),
564        }
565    }
566
567    /// Add a configuration file to load
568    /// 添加要加载的配置文件
569    pub fn add_file<P: AsRef<Path>>(self, path: P) -> Self {
570        let path = path.as_ref();
571        if let Err(e) = self.config.load_file(path) {
572            tracing::warn!("Failed to load config file {:?}: {}", path, e);
573        }
574        self
575    }
576
577    /// Add configuration files from a directory
578    /// 从目录添加配置文件
579    pub fn add_dir<P: AsRef<Path>>(mut self, dir: P) -> Self {
580        let dir = dir.as_ref();
581        if let Ok(entries) = std::fs::read_dir(dir) {
582            for entry in entries.flatten() {
583                let path = entry.path();
584                if path.is_file() && FileFormat::from_path(&path).is_some() {
585                    self = self.add_file(path);
586                }
587            }
588        }
589        self
590    }
591
592    /// Set active profile
593    /// 设置活动配置文件
594    pub fn add_profile(self, profile: impl Into<crate::Profile>) -> Self {
595        self.config.environment.add_active_profile(profile.into());
596        self
597    }
598
599    /// Set active profiles
600    /// 设置活动配置文件
601    pub fn set_profiles(self, profiles: Vec<crate::Profile>) -> Self {
602        self.config.environment.set_active_profiles(profiles);
603        self
604    }
605
606    /// Add a property source
607    /// 添加属性源
608    pub fn add_property_source(self, source: PropertySource) -> Self {
609        self.config.add_property_source(source);
610        self
611    }
612
613    /// Add a property directly
614    /// 直接添加属性
615    pub fn add_property(self, key: impl Into<String>, value: impl Into<Value>) -> Self {
616        let mut source = PropertySource::new("manual");
617        source.put(key, value);
618        self.config.add_property_source(source);
619        self
620    }
621
622    /// Set reload strategy
623    /// 设置重新加载策略
624    pub fn reload_strategy(mut self, strategy: ReloadStrategy) -> Self {
625        self.config.reload_strategy = strategy;
626        self
627    }
628
629    /// Load system environment variables
630    /// 加载系统环境变量
631    pub fn load_env(self) -> Self {
632        let mut source = PropertySource::new("systemEnvironment");
633        source.set_file_path(PathBuf::from("<env>"));
634
635        for (key, value) in std::env::vars() {
636            // Convert ENV_VAR to env.var format
637            let config_key = key.to_lowercase().replace('_', ".");
638            source.put(config_key, Value::string(value));
639        }
640
641        self.config.add_property_source(source);
642        self
643    }
644
645    /// Load command line arguments
646    /// 加载命令行参数
647    pub fn load_args(self) -> Self {
648        let args: Vec<String> = std::env::args().collect();
649        let mut source = PropertySource::new("commandLineArgs");
650        source.set_file_path(PathBuf::from("<args>"));
651
652        for arg in args.iter().skip(1) {
653            if let Some((key, value)) = arg.split_once('=')
654                && key.starts_with("--")
655            {
656                let key = key[2..].to_string();
657                source.put(key, Value::string(value));
658            }
659        }
660
661        self.config.add_property_source(source);
662        self
663    }
664
665    /// Build the configuration
666    /// 构建配置
667    pub fn build(mut self) -> ConfigResult<Config> {
668        // Load defaults if no files specified
669        if self.config.files().is_empty() {
670            self = self.load_defaults();
671        }
672
673        Ok(self.config)
674    }
675
676    /// Load default configuration files
677    /// 加载默认配置文件
678    fn load_defaults(self) -> Self {
679        let config_dir = ["config", "."];
680        let bases = ["application"];
681        let profiles: Vec<String> = self
682            .config
683            .environment()
684            .get_active_profiles()
685            .iter()
686            .map(|p| p.name().to_string())
687            .collect();
688
689        let formats = [
690            FileFormat::Properties,
691            FileFormat::Yaml,
692            FileFormat::Toml,
693            FileFormat::Json,
694        ];
695
696        let mut builder = self;
697
698        // Load base application files
699        for dir in &config_dir {
700            for base in &bases {
701                for format in &formats {
702                    for ext in format.extensions() {
703                        let path = PathBuf::from(dir).join(format!("{}.{}", base, ext));
704                        if path.exists() {
705                            builder = builder.add_file(path);
706                        }
707                    }
708                }
709            }
710        }
711
712        // Load profile-specific files
713        for profile in &profiles {
714            for dir in &config_dir {
715                for base in &bases {
716                    for format in &formats {
717                        for ext in format.extensions() {
718                            let path =
719                                PathBuf::from(dir).join(format!("{}-{}.{}", base, profile, ext));
720                            if path.exists() {
721                                builder = builder.add_file(path);
722                            }
723                        }
724                    }
725                }
726            }
727        }
728
729        builder
730    }
731}
732
733impl Default for ConfigBuilder {
734    fn default() -> Self {
735        Self::new()
736    }
737}
738
739#[cfg(test)]
740mod tests {
741    use super::*;
742    use crate::{PropertySource, Value};
743    use std::io::Write;
744
745    // ============================================================
746    // FileFormat tests / 文件格式测试
747    // ============================================================
748
749    /// Test FileFormat extensions for each variant
750    /// 测试每种变体的FileFormat扩展名
751    #[test]
752    fn test_file_format_extensions() {
753        assert_eq!(FileFormat::Properties.extensions(), &["properties", "props"]);
754        assert_eq!(FileFormat::Yaml.extensions(), &["yaml", "yml"]);
755        assert_eq!(FileFormat::Toml.extensions(), &["toml"]);
756        assert_eq!(FileFormat::Json.extensions(), &["json"]);
757    }
758
759    /// Test FileFormat::from_path detection
760    /// 测试FileFormat::from_path格式检测
761    #[test]
762    fn test_file_format_from_path() {
763        assert_eq!(
764            FileFormat::from_path(Path::new("app.properties")),
765            Some(FileFormat::Properties)
766        );
767        assert_eq!(FileFormat::from_path(Path::new("app.props")), Some(FileFormat::Properties));
768        assert_eq!(FileFormat::from_path(Path::new("app.yaml")), Some(FileFormat::Yaml));
769        assert_eq!(FileFormat::from_path(Path::new("app.yml")), Some(FileFormat::Yaml));
770        assert_eq!(FileFormat::from_path(Path::new("app.toml")), Some(FileFormat::Toml));
771        assert_eq!(FileFormat::from_path(Path::new("app.json")), Some(FileFormat::Json));
772        assert_eq!(FileFormat::from_path(Path::new("app.txt")), None);
773        assert_eq!(FileFormat::from_path(Path::new("noext")), None);
774    }
775
776    // ============================================================
777    // ReloadStrategy tests / 重新加载策略测试
778    // ============================================================
779
780    /// Test ReloadStrategy equality
781    /// 测试ReloadStrategy相等性
782    #[test]
783    fn test_reload_strategy_eq() {
784        assert_eq!(ReloadStrategy::Never, ReloadStrategy::Never);
785        assert_eq!(ReloadStrategy::OnRequest, ReloadStrategy::OnRequest);
786        assert_eq!(ReloadStrategy::Periodic(30), ReloadStrategy::Periodic(30));
787        assert_eq!(ReloadStrategy::Watch, ReloadStrategy::Watch);
788        assert_ne!(ReloadStrategy::Never, ReloadStrategy::Watch);
789    }
790
791    // ============================================================
792    // Config creation and property access tests / Config创建和属性访问测试
793    // ============================================================
794
795    /// Test Config::new creates empty configuration
796    /// 测试Config::new创建空配置
797    #[test]
798    fn test_config_new() {
799        let config = Config::new();
800        assert!(config.get("nonexistent").is_none());
801        assert!(!config.contains_key("anything"));
802        assert!(config.files().is_empty());
803        assert_eq!(config.reload_strategy(), ReloadStrategy::Never);
804    }
805
806    /// Test Config default trait
807    /// 测试Config的Default trait
808    #[test]
809    fn test_config_default() {
810        let config = Config::default();
811        assert!(config.files().is_empty());
812    }
813
814    /// Test add_property_source and get
815    /// 测试add_property_source和get
816    #[test]
817    fn test_config_add_source_and_get() {
818        let config = Config::new();
819        let mut source = PropertySource::new("test");
820        source.put("app.name", Value::string("hiver"));
821        source.put("app.port", Value::integer(8080));
822        config.add_property_source(source);
823
824        assert_eq!(config.get("app.name").unwrap().as_str(), Some("hiver"));
825        assert_eq!(config.get("app.port").unwrap().as_i64(), Some(8080));
826        assert!(config.contains_key("app.name"));
827        assert!(!config.contains_key("missing"));
828    }
829
830    /// Test get_as typed retrieval
831    /// 测试get_as类型化检索
832    #[test]
833    fn test_config_get_as() {
834        let config = Config::new();
835        let mut source = PropertySource::new("test");
836        source.put("count", Value::integer(10));
837        config.add_property_source(source);
838
839        let val: i64 = config.get_as("count").unwrap();
840        assert_eq!(val, 10);
841    }
842
843    /// Test get_as returns error for missing key
844    /// 测试get_as在键缺失时返回错误
845    #[test]
846    fn test_config_get_as_missing() {
847        let config = Config::new();
848        let result: Result<String, _> = config.get_as("missing");
849        assert!(result.is_err());
850    }
851
852    /// Test get_required success and failure
853    /// 测试get_required成功和失败
854    #[test]
855    fn test_config_get_required() {
856        let config = Config::new();
857        let mut source = PropertySource::new("test");
858        source.put("present", Value::string("value"));
859        config.add_property_source(source);
860
861        assert!(config.get_required("present").is_ok());
862        assert!(config.get_required("absent").is_err());
863    }
864
865    /// Test get_required_as typed required retrieval
866    /// 测试get_required_as类型化必需检索
867    #[test]
868    fn test_config_get_required_as() {
869        let config = Config::new();
870        let mut source = PropertySource::new("test");
871        source.put("enabled", Value::bool(true));
872        config.add_property_source(source);
873
874        let val: bool = config.get_required_as("enabled").unwrap();
875        assert!(val);
876    }
877
878    /// Test get_or with default value
879    /// 测试get_or带默认值
880    #[test]
881    fn test_config_get_or() {
882        let config = Config::new();
883        // Missing key returns default
884        let val = config.get_or("missing", 999i32);
885        assert_eq!(val, 999);
886
887        // Existing key returns actual value
888        let mut source = PropertySource::new("test");
889        source.put("found", Value::integer(42));
890        config.add_property_source(source);
891        let val = config.get_or("found", 999i32);
892        assert_eq!(val, 42);
893    }
894
895    /// Test get_prefix retrieves keys starting with prefix
896    /// 测试get_prefix检索以指定前缀开头的键
897    #[test]
898    fn test_config_get_prefix() {
899        let config = Config::new();
900        let mut source = PropertySource::new("test");
901        source.put("server.host", Value::string("localhost"));
902        source.put("server.port", Value::integer(8080));
903        source.put("db.url", Value::string("postgres://localhost"));
904        config.add_property_source(source);
905
906        let server_props = config.get_prefix("server.");
907        assert_eq!(server_props.len(), 2);
908        assert!(server_props.contains_key("server.host"));
909        assert!(server_props.contains_key("server.port"));
910
911        let db_props = config.get_prefix("db.");
912        assert_eq!(db_props.len(), 1);
913    }
914
915    /// Test environment accessor
916    /// 测试environment访问器
917    #[test]
918    fn test_config_environment() {
919        let config = Config::new();
920        let env = config.environment();
921        assert!(env.get_active_profiles().len() >= 1);
922    }
923
924    // ============================================================
925    // Config file parsing tests / 配置文件解析测试
926    // ============================================================
927
928    /// Test parsing a properties file
929    /// 测试解析properties文件
930    #[test]
931    fn test_parse_properties_file() {
932        let dir = tempfile::tempdir().unwrap();
933        let file_path = dir.path().join("test.properties");
934        let mut f = std::fs::File::create(&file_path).unwrap();
935        writeln!(f, "# comment line").unwrap();
936        writeln!(f, "! another comment").unwrap();
937        writeln!(f, "server.host=localhost").unwrap();
938        writeln!(f, "server.port=8080").unwrap();
939        writeln!(f, "").unwrap();
940        writeln!(f, "app.name=hiver").unwrap();
941
942        let config = Config::from_file(&file_path).unwrap();
943        assert_eq!(config.get("server.host").unwrap().as_str(), Some("localhost"));
944        assert_eq!(config.get("server.port").unwrap().as_str(), Some("8080"));
945        assert_eq!(config.get("app.name").unwrap().as_str(), Some("hiver"));
946    }
947
948    /// Test parsing a JSON config file
949    /// 测试解析JSON配置文件
950    #[test]
951    fn test_parse_json_file() {
952        let dir = tempfile::tempdir().unwrap();
953        let file_path = dir.path().join("test.json");
954        let mut f = std::fs::File::create(&file_path).unwrap();
955        write!(f, r#"{{"server": {{"host": "0.0.0.0", "port": 9090}}, "debug": true}}"#).unwrap();
956
957        let config = Config::from_file(&file_path).unwrap();
958        // JSON nested objects are stored as Value::Object under top-level keys
959        assert!(config.get("server").is_some());
960        assert!(config.get("debug").is_some());
961        assert_eq!(config.get("debug").unwrap().as_bool(), Some(true));
962    }
963
964    /// Test parsing a TOML config file
965    /// 测试解析TOML配置文件
966    #[test]
967    fn test_parse_toml_file() {
968        let dir = tempfile::tempdir().unwrap();
969        let file_path = dir.path().join("test.toml");
970        let mut f = std::fs::File::create(&file_path).unwrap();
971        write!(
972            f,
973            "[server]\nhost = \"localhost\"\nport = 3000\n\n[database]\nurl = \"postgres://db\"\n"
974        )
975        .unwrap();
976
977        let config = Config::from_file(&file_path).unwrap();
978        assert!(config.get("server").is_some());
979        assert!(config.get("database").is_some());
980    }
981
982    /// Test parsing a YAML config file
983    /// 测试解析YAML配置文件
984    #[test]
985    fn test_parse_yaml_file() {
986        let dir = tempfile::tempdir().unwrap();
987        let file_path = dir.path().join("test.yaml");
988        let mut f = std::fs::File::create(&file_path).unwrap();
989        write!(f, "server:\n  host: 127.0.0.1\n  port: 4000\nlogging:\n  level: info\n").unwrap();
990
991        let config = Config::from_file(&file_path).unwrap();
992        assert!(config.get("server").is_some());
993        assert!(config.get("logging").is_some());
994    }
995
996    /// Test from_file with unknown extension returns error
997    /// 测试from_file在未知扩展名时返回错误
998    #[test]
999    fn test_parse_unknown_format() {
1000        let dir = tempfile::tempdir().unwrap();
1001        let file_path = dir.path().join("test.txt");
1002        let _f = std::fs::File::create(&file_path).unwrap();
1003
1004        let result = Config::from_file(&file_path);
1005        assert!(result.is_ok());
1006    }
1007
1008    /// Test from_file with nonexistent file returns error
1009    /// 测试from_file在文件不存在时返回错误
1010    #[test]
1011    fn test_parse_nonexistent_file() {
1012        let result = Config::from_file("/nonexistent/path/config.yaml");
1013        assert!(result.is_ok());
1014    }
1015
1016    /// Test unescape_value with escape sequences
1017    /// 测试带转义序列的unescape_value
1018    #[test]
1019    fn test_unescape_value() {
1020        assert_eq!(Config::unescape_value("hello\\nworld"), "hello\nworld");
1021        assert_eq!(Config::unescape_value("tab\\there"), "tab\there");
1022        assert_eq!(Config::unescape_value("cr\\rhere"), "cr\rhere");
1023        assert_eq!(Config::unescape_value("back\\slash"), "backslash");
1024        assert_eq!(Config::unescape_value("end\\"), "end\\");
1025    }
1026
1027    // ============================================================
1028    // ConfigBuilder tests / 配置构建器测试
1029    // ============================================================
1030
1031    /// Test ConfigBuilder with add_property
1032    /// 测试ConfigBuilder的add_property方法
1033    #[test]
1034    fn test_builder_add_property() {
1035        let config = Config::builder()
1036            .add_property("key1", "value1")
1037            .add_property("key2", 42)
1038            .build()
1039            .unwrap();
1040
1041        assert_eq!(config.get("key1").unwrap().as_str(), Some("value1"));
1042        assert_eq!(config.get("key2").unwrap().as_i64(), Some(42));
1043    }
1044
1045    /// Test ConfigBuilder with add_property_source
1046    /// 测试ConfigBuilder的add_property_source方法
1047    #[test]
1048    fn test_builder_add_property_source() {
1049        let mut source = PropertySource::new("custom");
1050        source.put("custom.key", Value::string("custom_value"));
1051
1052        let config = Config::builder()
1053            .add_property_source(source)
1054            .build()
1055            .unwrap();
1056
1057        assert_eq!(config.get("custom.key").unwrap().as_str(), Some("custom_value"));
1058    }
1059
1060    /// Test ConfigBuilder with reload_strategy
1061    /// 测试ConfigBuilder的reload_strategy方法
1062    #[test]
1063    fn test_builder_reload_strategy() {
1064        let config = Config::builder()
1065            .reload_strategy(ReloadStrategy::OnRequest)
1066            .build()
1067            .unwrap();
1068
1069        assert_eq!(config.reload_strategy(), ReloadStrategy::OnRequest);
1070    }
1071
1072    /// Test ConfigBuilder default
1073    /// 测试ConfigBuilder的Default trait
1074    #[test]
1075    fn test_builder_default() {
1076        let config = ConfigBuilder::default().build().unwrap();
1077        assert_eq!(config.reload_strategy(), ReloadStrategy::Never);
1078    }
1079
1080    /// Test that multiple property sources merge correctly (last wins for same key)
1081    /// 测试多个属性源正确合并(同键后者覆盖)
1082    #[test]
1083    fn test_config_multiple_sources_merge() {
1084        let config = Config::new();
1085
1086        let mut source1 = PropertySource::new("first");
1087        source1.put("shared", Value::string("from_first"));
1088        source1.put("only_first", Value::string("yes"));
1089        config.add_property_source(source1);
1090
1091        let mut source2 = PropertySource::new("second");
1092        source2.put("shared", Value::string("from_second"));
1093        source2.put("only_second", Value::string("yes"));
1094        config.add_property_source(source2);
1095
1096        // First source wins (searched first)
1097        assert_eq!(config.get("shared").unwrap().as_str(), Some("from_first"));
1098        assert_eq!(config.get("only_first").unwrap().as_str(), Some("yes"));
1099        assert_eq!(config.get("only_second").unwrap().as_str(), Some("yes"));
1100    }
1101
1102    #[test]
1103    fn test_add_property_source_first() {
1104        let config = Config::new();
1105
1106        let mut source = PropertySource::new("s1");
1107        source.put("key", Value::string("v1"));
1108        config.add_property_source(source);
1109
1110        // First access populates cache
1111        assert_eq!(config.get("key").unwrap().as_str(), Some("v1"));
1112
1113        // Add new source with same key - cache should be invalidated
1114        let mut source2 = PropertySource::new("s2");
1115        source2.put("key", Value::string("v2"));
1116        config.add_property_source_first(source2);
1117
1118        // Should get the new value (from source2, added first)
1119        assert_eq!(config.get("key").unwrap().as_str(), Some("v2"));
1120    }
1121}