Skip to main content

hiver_config/
loader.rs

1//! Configuration loader module
2//! 配置加载器模块
3//!
4//! # Equivalent to Spring Boot / 等价于 Spring Boot
5//!
6
7//! - `ConfigFileApplicationListener` - `ConfigLoader`
8//! - `EnvironmentPostProcessor` - Loader processors
9//! - File watching and hot reload support
10
11use crate::{Config, ConfigError, ConfigResult, FileFormat, ReloadStrategy};
12use std::collections::HashMap;
13use std::path::PathBuf;
14use std::sync::Arc;
15use std::time::Duration;
16
17/// Configuration loader
18/// 配置加载器
19///
20/// Equivalent to Spring Boot's `ConfigFileApplicationListener`.
21/// 等价于Spring Boot的`ConfigFileApplicationListener`。
22///
23/// Handles loading configuration from various sources with priority ordering.
24/// 处理从各种来源加载具有优先级顺序的配置。
25#[derive(Debug, Clone)]
26pub struct ConfigLoader {
27    /// Config being built
28    /// 正在构建的配置
29    config: Config,
30
31    /// Search paths for config files
32    /// 配置文件的搜索路径
33    search_paths: Vec<PathBuf>,
34
35    /// File names to look for
36    /// 要查找的文件名
37    file_names: Vec<String>,
38
39    /// Active profiles
40    /// 活动配置文件
41    profiles: Vec<String>,
42
43    /// Whether to load environment variables
44    /// 是否加载环境变量
45    load_env: bool,
46
47    /// Whether to load command line args
48    /// 是否加载命令行参数
49    load_args: bool,
50}
51
52impl ConfigLoader {
53    /// Create a new loader
54    /// 创建新的加载器
55    pub fn new() -> Self {
56        Self {
57            config: Config::new(),
58            search_paths: vec![
59                PathBuf::from("./config"),
60                PathBuf::from("."),
61                PathBuf::from("/etc/hiver"),
62            ],
63            file_names: vec!["application".to_string()],
64            profiles: vec!["default".to_string()],
65            load_env: true,
66            load_args: true,
67        }
68    }
69
70    /// Create a loader builder
71    /// 创建加载器构建器
72    pub fn builder() -> ConfigLoaderBuilder {
73        ConfigLoaderBuilder::new()
74    }
75
76    /// Add a search path
77    /// 添加搜索路径
78    pub fn add_search_path(mut self, path: impl Into<PathBuf>) -> Self {
79        self.search_paths.push(path.into());
80        self
81    }
82
83    /// Add a file name to look for
84    /// 添加要查找的文件名
85    pub fn add_file_name(mut self, name: impl Into<String>) -> Self {
86        self.file_names.push(name.into());
87        self
88    }
89
90    /// Add an active profile
91    /// 添加活动配置文件
92    pub fn add_profile(mut self, profile: impl Into<String>) -> Self {
93        self.profiles.push(profile.into());
94        self
95    }
96
97    /// Set whether to load environment variables
98    /// 设置是否加载环境变量
99    pub fn load_env(mut self, load: bool) -> Self {
100        self.load_env = load;
101        self
102    }
103
104    /// Set whether to load command line args
105    /// 设置是否加载命令行参数
106    pub fn load_args(mut self, load: bool) -> Self {
107        self.load_args = load;
108        self
109    }
110
111    /// Load the configuration
112    /// 加载配置
113    pub fn load(mut self) -> ConfigResult<Config> {
114        // Load in order of priority (lowest first)
115        // 1. Application properties files
116        self.load_application_files()?;
117
118        // 2. Profile-specific files
119        self.load_profile_files()?;
120
121        // 3. Environment variables
122        if self.load_env {
123            self.load_environment_vars()?;
124        }
125
126        // 4. Command line arguments
127        if self.load_args {
128            self.load_command_line_args()?;
129        }
130
131        Ok(self.config)
132    }
133
134    /// Load base application files
135    /// 加载基础应用程序文件
136    fn load_application_files(&mut self) -> ConfigResult<()> {
137        let formats = [
138            FileFormat::Properties,
139            FileFormat::Yaml,
140            FileFormat::Toml,
141            FileFormat::Json,
142        ];
143
144        for search_path in &self.search_paths {
145            for file_name in &self.file_names {
146                for format in &formats {
147                    for ext in format.extensions() {
148                        let path = search_path.join(format!("{}.{}", file_name, ext));
149                        if path.exists() {
150                            if let Err(e) = self.config.load_file(&path) {
151                                tracing::debug!("Skipping {:?}: {}", path, e);
152                            } else {
153                                tracing::debug!("Loaded config from {:?}", path);
154                            }
155                        }
156                    }
157                }
158            }
159        }
160
161        Ok(())
162    }
163
164    /// Load profile-specific files
165    /// 加载配置文件特定文件
166    fn load_profile_files(&mut self) -> ConfigResult<()> {
167        let formats = [
168            FileFormat::Properties,
169            FileFormat::Yaml,
170            FileFormat::Toml,
171            FileFormat::Json,
172        ];
173
174        for profile in &self.profiles {
175            for search_path in &self.search_paths {
176                for file_name in &self.file_names {
177                    for format in &formats {
178                        for ext in format.extensions() {
179                            let path =
180                                search_path.join(format!("{}-{}.{}", file_name, profile, ext));
181                            if path.exists() {
182                                if let Err(e) = self.config.load_file(&path) {
183                                    tracing::debug!("Skipping {:?}: {}", path, e);
184                                } else {
185                                    tracing::debug!(
186                                        "Loaded config from {:?} (profile: {})",
187                                        path,
188                                        profile
189                                    );
190                                }
191                            }
192                        }
193                    }
194                }
195            }
196        }
197
198        Ok(())
199    }
200
201    /// Load environment variables
202    /// 加载环境变量
203    fn load_environment_vars(&mut self) -> ConfigResult<()> {
204        use crate::{PropertySourceBuilder, PropertySourceType, Value};
205
206        let mut builder = PropertySourceBuilder::new("environmentVariables")
207            .source_type(PropertySourceType::SystemEnvironment)
208            .order(200);
209
210        for (key, value) in std::env::vars() {
211            // Convert ENV_VAR to env.var format, and also keep original
212            let config_key = key.to_lowercase().replace('_', ".");
213            builder.put(config_key, Value::string(value.clone()));
214            builder.put(key, Value::string(value));
215        }
216
217        self.config.add_property_source(builder.build());
218        Ok(())
219    }
220
221    /// Load command line arguments
222    /// 加载命令行参数
223    fn load_command_line_args(&mut self) -> ConfigResult<()> {
224        use crate::{PropertySourceBuilder, PropertySourceType, Value};
225
226        let mut builder = PropertySourceBuilder::new("commandLineArgs")
227            .source_type(PropertySourceType::CommandLine)
228            .order(100);
229
230        let args: Vec<String> = std::env::args().collect();
231
232        for arg in args.iter().skip(1) {
233            if let Some(arg) = arg.strip_prefix("--") {
234                if let Some((key, value)) = arg.split_once('=') {
235                    builder.put(key, Value::string(value));
236                } else {
237                    // Flag without value
238                    builder.put(arg, Value::bool(true));
239                }
240            }
241        }
242
243        self.config.add_property_source(builder.build());
244        Ok(())
245    }
246}
247
248impl Default for ConfigLoader {
249    fn default() -> Self {
250        Self::new()
251    }
252}
253
254/// Configuration loader builder
255/// 配置加载器构建器
256///
257/// Provides a fluent API for building a `ConfigLoader`.
258/// `为构建ConfigLoader提供流畅的API`。
259pub struct ConfigLoaderBuilder {
260    loader: ConfigLoader,
261}
262
263impl ConfigLoaderBuilder {
264    /// Create a new builder
265    /// 创建新的构建器
266    pub fn new() -> Self {
267        Self {
268            loader: ConfigLoader::new(),
269        }
270    }
271
272    /// Add a search path
273    /// 添加搜索路径
274    pub fn search_path(mut self, path: impl Into<PathBuf>) -> Self {
275        self.loader = self.loader.add_search_path(path);
276        self
277    }
278
279    /// Add multiple search paths
280    /// 添加多个搜索路径
281    pub fn search_paths(mut self, paths: Vec<PathBuf>) -> Self {
282        for path in paths {
283            self.loader = self.loader.add_search_path(path);
284        }
285        self
286    }
287
288    /// Add a file name
289    /// 添加文件名
290    pub fn file_name(mut self, name: impl Into<String>) -> Self {
291        self.loader = self.loader.add_file_name(name);
292        self
293    }
294
295    /// Add multiple file names
296    /// 添加多个文件名
297    pub fn file_names(mut self, names: Vec<String>) -> Self {
298        for name in names {
299            self.loader = self.loader.add_file_name(name);
300        }
301        self
302    }
303
304    /// Add a profile
305    /// 添加配置文件
306    pub fn profile(mut self, profile: impl Into<String>) -> Self {
307        self.loader = self.loader.add_profile(profile);
308        self
309    }
310
311    /// Add multiple profiles
312    /// 添加多个配置文件
313    pub fn profiles(mut self, profiles: Vec<String>) -> Self {
314        for profile in profiles {
315            self.loader = self.loader.add_profile(profile);
316        }
317        self
318    }
319
320    /// Enable/disable environment variable loading
321    /// 启用/禁用环境变量加载
322    pub fn load_env(mut self, load: bool) -> Self {
323        self.loader = self.loader.load_env(load);
324        self
325    }
326
327    /// Enable/disable command line argument loading
328    /// 启用/禁用命令行参数加载
329    pub fn load_args(mut self, load: bool) -> Self {
330        self.loader = self.loader.load_args(load);
331        self
332    }
333
334    /// Build the loader
335    /// 构建加载器
336    pub fn build(self) -> ConfigLoader {
337        self.loader
338    }
339
340    /// Build and load configuration
341    /// 构建并加载配置
342    pub fn load(self) -> ConfigResult<Config> {
343        self.loader.load()
344    }
345}
346
347impl Default for ConfigLoaderBuilder {
348    fn default() -> Self {
349        Self::new()
350    }
351}
352
353/// File watcher for configuration hot reload
354/// 配置热重载的文件监视器
355///
356/// Equivalent to Spring Cloud Config's watch functionality.
357/// 等价于Spring Cloud Config的watch功能。
358pub struct Watcher {
359    /// Config to watch
360    /// 要监视的配置
361    config: Arc<Config>,
362
363    /// Watched files with their last modified times
364    /// 被监视的文件及其最后修改时间
365    watched_files: Arc<std::sync::RwLock<HashMap<PathBuf, std::time::SystemTime>>>,
366
367    /// Reload strategy
368    /// 重新加载策略
369    strategy: ReloadStrategy,
370
371    /// Check interval
372    /// 检查间隔
373    interval: Duration,
374
375    /// Running flag
376    /// 运行标志
377    running: Arc<std::sync::atomic::AtomicBool>,
378}
379
380impl Watcher {
381    /// Create a new watcher
382    /// 创建新的监视器
383    pub fn new(config: Arc<Config>) -> Self {
384        let strategy = config.reload_strategy();
385
386        Self {
387            config,
388            watched_files: Arc::new(std::sync::RwLock::new(HashMap::new())),
389            strategy,
390            interval: Duration::from_secs(5),
391            running: Arc::new(false.into()),
392        }
393    }
394
395    /// Set check interval
396    /// 设置检查间隔
397    pub fn interval(mut self, interval: Duration) -> Self {
398        self.interval = interval;
399        self
400    }
401
402    /// Add a file to watch
403    /// 添加要监视的文件
404    pub fn watch_file(&self, path: PathBuf) {
405        if let Ok(metadata) = std::fs::metadata(&path)
406            && let Ok(modified) = metadata.modified()
407        {
408            let mut files = self
409                .watched_files
410                .write()
411                .unwrap_or_else(std::sync::PoisonError::into_inner);
412            files.insert(path, modified);
413        }
414    }
415
416    /// Start watching
417    /// 开始监视
418    pub fn start(&self) -> ConfigResult<()> {
419        if self.strategy != ReloadStrategy::Watch {
420            return Err(ConfigError::OverrideNotAllowed(
421                "Watcher requires ReloadStrategy::Watch".to_string(),
422            ));
423        }
424
425        self.running
426            .store(true, std::sync::atomic::Ordering::SeqCst);
427
428        let config = self.config.clone();
429        let watched_files = self.watched_files.clone();
430        let running = self.running.clone();
431        let interval = self.interval;
432
433        std::thread::spawn(move || {
434            while running.load(std::sync::atomic::Ordering::SeqCst) {
435                std::thread::sleep(interval);
436
437                let mut files = watched_files
438                    .write()
439                    .unwrap_or_else(std::sync::PoisonError::into_inner);
440                let mut changed = Vec::new();
441
442                for (path, last_modified) in files.iter() {
443                    if let Ok(metadata) = std::fs::metadata(path)
444                        && let Ok(modified) = metadata.modified()
445                        && modified != *last_modified
446                    {
447                        changed.push((path.clone(), modified));
448                    }
449                }
450
451                for (path, modified) in changed {
452                    tracing::info!("Config file changed: {:?}, reloading...", path);
453
454                    // Reload config
455                    if let Err(e) = config.load_file(&path) {
456                        tracing::error!("Failed to reload config {:?}: {}", path, e);
457                    } else {
458                        tracing::info!("Successfully reloaded config from {:?}", path);
459                    }
460
461                    files.insert(path, modified);
462                }
463            }
464        });
465
466        Ok(())
467    }
468
469    /// Stop watching
470    /// 停止监视
471    pub fn stop(&self) {
472        self.running
473            .store(false, std::sync::atomic::Ordering::SeqCst);
474    }
475}
476
477/// Configuration post-processor trait
478/// 配置后处理器trait
479///
480/// Equivalent to Spring's `EnvironmentPostProcessor`.
481/// 等价于Spring的`EnvironmentPostProcessor`。
482///
483/// Allows customizing the configuration after loading but before use.
484/// 允许在加载后但在使用前自定义配置。
485pub(crate) trait ConfigPostProcessor: Send + Sync {
486    /// Post-process the configuration
487    /// 后处理配置
488    fn post_process(&self, config: &mut Config) -> ConfigResult<()>;
489}
490
491/// Standard configuration post-processors
492/// 标准配置后处理器
493pub(crate) struct StandardPostProcessors;
494
495impl StandardPostProcessors {
496    /// Create a post-processor that expands placeholders
497    /// 创建展开占位符的后处理器
498    pub(crate) fn placeholder_expander() -> impl ConfigPostProcessor {
499        PlaceholderExpander
500    }
501
502    /// Create a post-processor that validates required properties
503    /// 创建验证必需属性的后处理器
504    pub(crate) fn required_validator(required: Vec<String>) -> impl ConfigPostProcessor {
505        RequiredValidator { required }
506    }
507}
508
509/// Placeholder expander post-processor
510/// 占位符展开器后处理器
511struct PlaceholderExpander;
512
513impl ConfigPostProcessor for PlaceholderExpander {
514    fn post_process(&self, _config: &mut Config) -> ConfigResult<()> {
515        // This would expand ${...} placeholders in property values
516        // Implementation would iterate through all properties and expand placeholders
517        Ok(())
518    }
519}
520
521/// Required properties validator post-processor
522/// 必需属性验证器后处理器
523struct RequiredValidator {
524    required: Vec<String>,
525}
526
527impl ConfigPostProcessor for RequiredValidator {
528    fn post_process(&self, config: &mut Config) -> ConfigResult<()> {
529        for key in &self.required {
530            if !config.contains_key(key) {
531                return Err(ConfigError::MissingProperty(key.clone()));
532            }
533        }
534        Ok(())
535    }
536}
537
538#[cfg(test)]
539mod tests {
540    use super::*;
541    use crate::{PropertySource, Value};
542
543    #[test]
544    fn test_loader_builder() {
545        let loader = ConfigLoaderBuilder::new()
546            .search_path("./test")
547            .profile("test")
548            .load_env(true)
549            .build();
550
551        assert_eq!(loader.profiles.len(), 2); // "default" + "test"
552    }
553
554    /// Test ConfigLoader::new has sensible defaults
555    /// 测试ConfigLoader::new有合理的默认值
556    #[test]
557    fn test_loader_new_defaults() {
558        let loader = ConfigLoader::new();
559        assert_eq!(loader.search_paths.len(), 3);
560        assert_eq!(loader.file_names.len(), 1);
561        assert_eq!(loader.file_names[0], "application");
562        assert_eq!(loader.profiles.len(), 1);
563        assert_eq!(loader.profiles[0], "default");
564        assert!(loader.load_env);
565        assert!(loader.load_args);
566    }
567
568    /// Test ConfigLoader default trait
569    /// 测试ConfigLoader的Default trait
570    #[test]
571    fn test_loader_default() {
572        let loader = ConfigLoader::default();
573        assert_eq!(loader.search_paths.len(), 3);
574    }
575
576    /// Test ConfigLoaderBuilder search_paths bulk add
577    /// 测试ConfigLoaderBuilder批量添加搜索路径
578    #[test]
579    fn test_loader_builder_search_paths() {
580        let loader = ConfigLoaderBuilder::new()
581            .search_paths(vec![PathBuf::from("/a"), PathBuf::from("/b")])
582            .build();
583        // Default 3 paths + 2 added = 5
584        assert!(loader.search_paths.len() >= 5);
585    }
586
587    /// Test ConfigLoaderBuilder file_names bulk add
588    /// 测试ConfigLoaderBuilder批量添加文件名
589    #[test]
590    fn test_loader_builder_file_names() {
591        let loader = ConfigLoaderBuilder::new()
592            .file_names(vec!["custom".to_string(), "override".to_string()])
593            .build();
594        // Default 1 name + 2 added = 3
595        assert!(loader.file_names.len() >= 3);
596    }
597
598    /// Test ConfigLoaderBuilder profiles bulk add
599    /// 测试ConfigLoaderBuilder批量添加配置文件
600    #[test]
601    fn test_loader_builder_profiles() {
602        let loader = ConfigLoaderBuilder::new()
603            .profiles(vec!["staging".to_string(), "prod".to_string()])
604            .build();
605        // Default "default" + 2 = 3
606        assert!(loader.profiles.len() >= 3);
607    }
608
609    /// Test ConfigLoaderBuilder load_env(false) and load_args(false)
610    /// 测试ConfigLoaderBuilder禁用环境变量和命令行参数
611    #[test]
612    fn test_loader_builder_disable_env_and_args() {
613        let loader = ConfigLoaderBuilder::new()
614            .load_env(false)
615            .load_args(false)
616            .build();
617        assert!(!loader.load_env);
618        assert!(!loader.load_args);
619    }
620
621    /// Test ConfigLoader add methods chain correctly
622    /// 测试ConfigLoader的add方法链式调用
623    #[test]
624    fn test_loader_add_methods() {
625        let loader = ConfigLoader::new()
626            .add_search_path("/custom/path")
627            .add_file_name("myapp")
628            .add_profile("dev");
629
630        assert!(loader.search_paths.len() > 3);
631        assert!(loader.file_names.contains(&"myapp".to_string()));
632        assert!(loader.profiles.contains(&"dev".to_string()));
633    }
634
635    /// Test ConfigLoaderBuilder::new() default
636    /// 测试ConfigLoaderBuilder的Default trait
637    #[test]
638    fn test_loader_builder_default() {
639        let loader = ConfigLoaderBuilder::default().build();
640        assert_eq!(loader.search_paths.len(), 3);
641    }
642
643    /// Test RequiredValidator post-processor succeeds when all keys present
644    /// 测试RequiredValidator后处理器在所有键都存在时成功
645    #[test]
646    fn test_required_validator_pass() {
647        let mut config = Config::new();
648        let mut source = PropertySource::new("test");
649        source.put("db.url", Value::string("postgres://localhost"));
650        source.put("db.user", Value::string("admin"));
651        config.add_property_source(source);
652
653        let validator = StandardPostProcessors::required_validator(vec![
654            "db.url".to_string(),
655            "db.user".to_string(),
656        ]);
657        assert!(validator.post_process(&mut config).is_ok());
658    }
659
660    /// Test RequiredValidator post-processor fails when key missing
661    /// 测试RequiredValidator后处理器在键缺失时失败
662    #[test]
663    fn test_required_validator_fail() {
664        let mut config = Config::new();
665        let source = PropertySource::new("test");
666        config.add_property_source(source);
667
668        let validator = StandardPostProcessors::required_validator(vec!["missing.key".to_string()]);
669        assert!(validator.post_process(&mut config).is_err());
670    }
671
672    /// Test PlaceholderExpander post-processor runs without error
673    /// 测试PlaceholderExpander后处理器无错误运行
674    #[test]
675    fn test_placeholder_expander() {
676        let mut config = Config::new();
677        let expander = StandardPostProcessors::placeholder_expander();
678        assert!(expander.post_process(&mut config).is_ok());
679    }
680
681    /// Test ConfigLoader load with no existing files succeeds (graceful)
682    /// 测试ConfigLoader在无现有文件时加载成功(优雅处理)
683    #[test]
684    fn test_loader_load_no_files() {
685        let result = ConfigLoaderBuilder::new()
686            .search_path("/nonexistent_hiver_path")
687            .load_env(false)
688            .load_args(false)
689            .load();
690        assert!(result.is_ok());
691    }
692}