Skip to main content

sz_orm_config/
lib.rs

1//! # SZ-ORM Config — Configuration Center
2//!
3//! Provides a configuration center abstraction (Consul/Nacos/etc.) supporting
4//! get/set/delete/list/watch, with callback notification to subscribers on
5//! configuration changes.
6//!
7//! ## Main Types
8//!
9//! - [`ConfigCenter`] trait — Configuration center interface
10//! - [`ConfigChangeEvent`] — Configuration change event
11
12#[cfg(feature = "real-consul")]
13pub mod consul_client;
14#[cfg(feature = "real-nacos")]
15pub mod nacos_client;
16#[cfg(feature = "prod-config-masking")]
17pub mod prod_ready;
18
19pub mod config_validator;
20pub mod hot_reload;
21
22pub use config_validator::{
23    ConfigSchema, ConfigValidationReport, FieldSchema, ValidationResult, ValidationRule,
24};
25
26use serde::{Deserialize, Serialize};
27use std::collections::HashMap;
28use std::sync::{Arc, Mutex};
29
30/// Callback invoked when a configuration value changes.
31/// Arguments: `(key, new_value)`. On delete, `new_value` is empty.
32pub type ConfigChangeCallback = Arc<dyn Fn(&str, &str) + Send + Sync>;
33
34/// Configuration center abstraction (Consul/Nacos/etc).
35pub trait ConfigCenter: Send + Sync {
36    fn get(&self, key: &str) -> Option<String>;
37    fn set(&mut self, key: &str, value: &str);
38    fn delete(&mut self, key: &str) -> bool;
39    fn exists(&self, key: &str) -> bool;
40    fn list(&self) -> Vec<String>;
41    /// Returns true if a watch was successfully registered.
42    /// In this in-memory implementation, registration always succeeds.
43    fn watch(&self, key: &str) -> bool;
44    /// Registers a callback for changes to `key`.
45    fn subscribe(&mut self, key: &str, callback: ConfigChangeCallback);
46}
47
48/// Configuration change event record, useful for testing and auditing.
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ConfigChangeEvent {
51    pub key: String,
52    pub value: String,
53    pub deleted: bool,
54}
55
56/// Consul-style in-memory configuration center.
57pub struct ConsulConfigCenter {
58    data: HashMap<String, String>,
59    subscribers: HashMap<String, Vec<ConfigChangeCallback>>,
60    events: Mutex<Vec<ConfigChangeEvent>>,
61}
62
63impl ConsulConfigCenter {
64    pub fn new() -> Self {
65        Self {
66            data: HashMap::new(),
67            subscribers: HashMap::new(),
68            events: Mutex::new(Vec::new()),
69        }
70    }
71
72    fn notify(&self, key: &str, value: &str, deleted: bool) {
73        if let Some(callbacks) = self.subscribers.get(key) {
74            for cb in callbacks {
75                cb(key, value);
76            }
77        }
78        if let Ok(mut events) = self.events.lock() {
79            events.push(ConfigChangeEvent {
80                key: key.to_string(),
81                value: value.to_string(),
82                deleted,
83            });
84        }
85    }
86
87    /// Returns the ordered list of all change events that have occurred.
88    pub fn events(&self) -> Vec<ConfigChangeEvent> {
89        self.events.lock().map(|e| e.clone()).unwrap_or_default()
90    }
91
92    pub fn subscriber_count(&self, key: &str) -> usize {
93        self.subscribers.get(key).map(|cbs| cbs.len()).unwrap_or(0)
94    }
95}
96
97impl Default for ConsulConfigCenter {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl ConfigCenter for ConsulConfigCenter {
104    fn get(&self, key: &str) -> Option<String> {
105        self.data.get(key).cloned()
106    }
107
108    fn set(&mut self, key: &str, value: &str) {
109        self.data.insert(key.to_string(), value.to_string());
110        self.notify(key, value, false);
111    }
112
113    fn delete(&mut self, key: &str) -> bool {
114        let removed = self.data.remove(key).is_some();
115        if removed {
116            self.notify(key, "", true);
117        }
118        removed
119    }
120
121    fn exists(&self, key: &str) -> bool {
122        self.data.contains_key(key)
123    }
124
125    fn list(&self) -> Vec<String> {
126        let mut keys: Vec<String> = self.data.keys().cloned().collect();
127        keys.sort();
128        keys
129    }
130
131    fn watch(&self, _key: &str) -> bool {
132        true
133    }
134
135    fn subscribe(&mut self, key: &str, callback: ConfigChangeCallback) {
136        self.subscribers
137            .entry(key.to_string())
138            .or_default()
139            .push(callback);
140    }
141}
142
143/// Nacos-style in-memory configuration center.
144pub struct NacosConfigCenter {
145    data: HashMap<String, String>,
146    subscribers: HashMap<String, Vec<ConfigChangeCallback>>,
147    events: Mutex<Vec<ConfigChangeEvent>>,
148}
149
150impl NacosConfigCenter {
151    pub fn new() -> Self {
152        Self {
153            data: HashMap::new(),
154            subscribers: HashMap::new(),
155            events: Mutex::new(Vec::new()),
156        }
157    }
158
159    fn notify(&self, key: &str, value: &str, deleted: bool) {
160        if let Some(callbacks) = self.subscribers.get(key) {
161            for cb in callbacks {
162                cb(key, value);
163            }
164        }
165        if let Ok(mut events) = self.events.lock() {
166            events.push(ConfigChangeEvent {
167                key: key.to_string(),
168                value: value.to_string(),
169                deleted,
170            });
171        }
172    }
173
174    pub fn events(&self) -> Vec<ConfigChangeEvent> {
175        self.events.lock().map(|e| e.clone()).unwrap_or_default()
176    }
177
178    pub fn subscriber_count(&self, key: &str) -> usize {
179        self.subscribers.get(key).map(|cbs| cbs.len()).unwrap_or(0)
180    }
181}
182
183impl Default for NacosConfigCenter {
184    fn default() -> Self {
185        Self::new()
186    }
187}
188
189impl ConfigCenter for NacosConfigCenter {
190    fn get(&self, key: &str) -> Option<String> {
191        self.data.get(key).cloned()
192    }
193
194    fn set(&mut self, key: &str, value: &str) {
195        self.data.insert(key.to_string(), value.to_string());
196        self.notify(key, value, false);
197    }
198
199    fn delete(&mut self, key: &str) -> bool {
200        let removed = self.data.remove(key).is_some();
201        if removed {
202            self.notify(key, "", true);
203        }
204        removed
205    }
206
207    fn exists(&self, key: &str) -> bool {
208        self.data.contains_key(key)
209    }
210
211    fn list(&self) -> Vec<String> {
212        let mut keys: Vec<String> = self.data.keys().cloned().collect();
213        keys.sort();
214        keys
215    }
216
217    fn watch(&self, _key: &str) -> bool {
218        true
219    }
220
221    fn subscribe(&mut self, key: &str, callback: ConfigChangeCallback) {
222        self.subscribers
223            .entry(key.to_string())
224            .or_default()
225            .push(callback);
226    }
227}
228
229// ====================================================================
230// 配置热更新(Watch)— 轮询式监听配置变更
231// ====================================================================
232
233/// Configuration hot-reload watcher: polls the configuration center at a fixed
234/// interval and notifies subscribers when value changes are detected.
235///
236/// Since the current implementation is a pure in-memory model, the watcher detects
237/// changes by recording the last snapshot and comparing it with the current values.
238/// In real-world scenarios this can be replaced with long polling or the Watch API
239/// of Consul/Nacos.
240pub struct ConfigWatcher {
241    /// Key-value pairs from the last snapshot
242    last_snapshot: Mutex<HashMap<String, String>>,
243    /// Polling interval (in milliseconds)
244    pub poll_interval_ms: u64,
245    /// List of change callbacks: (key, callback)
246    watchers: Mutex<Vec<(String, ConfigChangeCallback)>>,
247}
248
249impl ConfigWatcher {
250    pub fn new(poll_interval_ms: u64) -> Self {
251        Self {
252            last_snapshot: Mutex::new(HashMap::new()),
253            poll_interval_ms: poll_interval_ms.max(100),
254            watchers: Mutex::new(Vec::new()),
255        }
256    }
257
258    /// Register a change listener for a key
259    pub fn watch(&self, key: &str, callback: ConfigChangeCallback) {
260        if let Ok(mut watchers) = self.watchers.lock() {
261            watchers.push((key.to_string(), callback));
262        }
263    }
264
265    /// Perform a single poll: compare the current configuration with the last
266    /// snapshot and trigger change callbacks.
267    /// Returns the number of changes detected in this poll.
268    pub fn poll<C: ConfigCenter>(&self, center: &C) -> usize {
269        let current: HashMap<String, String> = center
270            .list()
271            .into_iter()
272            .filter_map(|k| center.get(&k).map(|v| (k, v)))
273            .collect();
274
275        let mut changes = Vec::new();
276        {
277            let Ok(mut snapshot) = self.last_snapshot.lock() else {
278                return 0;
279            };
280            // 检测新增和修改
281            for (k, v) in &current {
282                match snapshot.get(k) {
283                    Some(old) if old == v => {}
284                    _ => changes.push((k.clone(), v.clone(), false)),
285                }
286            }
287            // 检测删除
288            for k in snapshot.keys() {
289                if !current.contains_key(k) {
290                    changes.push((k.clone(), String::new(), true));
291                }
292            }
293            *snapshot = current;
294        }
295
296        let watcher_count = changes.len();
297        if let Ok(watchers) = self.watchers.lock() {
298            for (key, new_value, deleted) in &changes {
299                for (watch_key, cb) in watchers.iter() {
300                    if watch_key == key {
301                        cb(key, new_value);
302                    }
303                }
304                // 标记 deleted 仅用于日志,回调已收到空值
305                let _ = deleted;
306            }
307        }
308        watcher_count
309    }
310
311    /// Returns the number of currently registered watchers
312    pub fn watcher_count(&self) -> usize {
313        self.watchers.lock().map(|w| w.len()).unwrap_or(0)
314    }
315}
316
317impl Default for ConfigWatcher {
318    fn default() -> Self {
319        Self::new(5000)
320    }
321}
322
323// ====================================================================
324// 多源合并(文件 + 环境变量 + 远程)— 按优先级合并配置
325// ====================================================================
326
327/// Configuration source priority: a higher value means higher priority, and
328/// higher priority overrides lower priority.
329#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
330pub enum ConfigSourcePriority {
331    /// File configuration (lowest priority)
332    File = 0,
333    /// Remote configuration center
334    Remote = 1,
335    /// Environment variables (highest priority)
336    Env = 2,
337}
338
339/// Multi-source configuration merger: collects configuration from files,
340/// environment variables, and a remote configuration center, then merges them
341/// by priority.
342pub struct MultiSourceConfig {
343    /// File configuration
344    file_config: HashMap<String, String>,
345    /// Environment variable configuration
346    env_config: HashMap<String, String>,
347    /// Remote configuration center configuration
348    remote_config: HashMap<String, String>,
349    /// Merged result cache
350    merged: Mutex<Option<HashMap<String, String>>>,
351    /// Hot reload manager for dynamic config updates
352    hot_reload: hot_reload::HotReloadManager,
353}
354
355impl MultiSourceConfig {
356    pub fn new() -> Self {
357        Self {
358            file_config: HashMap::new(),
359            env_config: HashMap::new(),
360            remote_config: HashMap::new(),
361            merged: Mutex::new(None),
362            hot_reload: hot_reload::HotReloadManager::new(),
363        }
364    }
365
366    /// 返回热更新管理器引用
367    pub fn hot_reload_manager(&self) -> &hot_reload::HotReloadManager {
368        &self.hot_reload
369    }
370
371    /// 通过热更新机制应用配置变更
372    pub fn apply_hot_reload(
373        &self,
374        changes: &HashMap<String, String>,
375        source: hot_reload::ReloadSource,
376    ) -> Result<hot_reload::ReloadEvent, Vec<String>> {
377        hot_reload::apply_with_validation(&self.hot_reload, changes, source)
378    }
379
380    /// Set the file-source configuration
381    pub fn set_file_config(&mut self, config: HashMap<String, String>) {
382        self.file_config = config;
383        self.invalidate_cache();
384    }
385
386    /// Set the environment-variable-source configuration
387    pub fn set_env_config(&mut self, config: HashMap<String, String>) {
388        self.env_config = config;
389        self.invalidate_cache();
390    }
391
392    /// Set the remote configuration center source configuration
393    pub fn set_remote_config(&mut self, config: HashMap<String, String>) {
394        self.remote_config = config;
395        self.invalidate_cache();
396    }
397
398    /// Load configuration from system environment variables (with prefix filtering)
399    pub fn load_env_vars(&mut self, prefix: &str) {
400        for (key, value) in std::env::vars() {
401            if let Some(stripped) = key.strip_prefix(prefix) {
402                // 去掉前缀并转小写
403                self.env_config.insert(stripped.to_lowercase(), value);
404            }
405        }
406        self.invalidate_cache();
407    }
408
409    /// Load configuration from JSON file content
410    pub fn load_json(&mut self, json: &str) -> Result<(), String> {
411        let parsed: HashMap<String, String> =
412            serde_json::from_str(json).map_err(|e| format!("parse json error: {}", e))?;
413        self.file_config = parsed;
414        self.invalidate_cache();
415        Ok(())
416    }
417
418    /// Merge all sources and return the result (higher priority overrides lower)
419    pub fn merge(&self) -> HashMap<String, String> {
420        if let Ok(cache) = self.merged.lock() {
421            if let Some(cached) = cache.as_ref() {
422                return cached.clone();
423            }
424        }
425        let mut result = HashMap::new();
426        // 按 File -> Remote -> Env 顺序合并
427        for (k, v) in &self.file_config {
428            result.insert(k.clone(), v.clone());
429        }
430        for (k, v) in &self.remote_config {
431            result.insert(k.clone(), v.clone());
432        }
433        for (k, v) in &self.env_config {
434            result.insert(k.clone(), v.clone());
435        }
436        if let Ok(mut cache) = self.merged.lock() {
437            *cache = Some(result.clone());
438        }
439        result
440    }
441
442    /// Get the merged configuration value
443    pub fn get(&self, key: &str) -> Option<String> {
444        self.merge().get(key).cloned()
445    }
446
447    /// Return the source priority of a key
448    pub fn source_of(&self, key: &str) -> Option<ConfigSourcePriority> {
449        if self.env_config.contains_key(key) {
450            Some(ConfigSourcePriority::Env)
451        } else if self.remote_config.contains_key(key) {
452            Some(ConfigSourcePriority::Remote)
453        } else if self.file_config.contains_key(key) {
454            Some(ConfigSourcePriority::File)
455        } else {
456            None
457        }
458    }
459
460    fn invalidate_cache(&self) {
461        if let Ok(mut cache) = self.merged.lock() {
462            *cache = None;
463        }
464    }
465}
466
467impl Default for MultiSourceConfig {
468    fn default() -> Self {
469        Self::new()
470    }
471}
472
473// ====================================================================
474// 配置验证(Schema Validation)
475// ====================================================================
476
477/// Configuration field type constraint
478#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
479pub enum ConfigFieldType {
480    String,
481    Integer,
482    Float,
483    Boolean,
484    Url,
485    Email,
486}
487
488/// Configuration field schema definition
489#[derive(Debug, Clone, Serialize, Deserialize)]
490pub struct ConfigFieldSchema {
491    /// Field name
492    pub name: String,
493    /// Field type
494    pub field_type: ConfigFieldType,
495    /// Whether the field is required
496    pub required: bool,
497    /// Minimum value (for numeric types)
498    pub min: Option<f64>,
499    /// Maximum value (for numeric types)
500    pub max: Option<f64>,
501    /// Minimum length (for string types)
502    pub min_length: Option<usize>,
503    /// Maximum length (for string types)
504    pub max_length: Option<usize>,
505    /// Allowed enum values
506    pub allowed_values: Option<Vec<String>>,
507}
508
509impl ConfigFieldSchema {
510    pub fn new(name: impl Into<String>, field_type: ConfigFieldType) -> Self {
511        Self {
512            name: name.into(),
513            field_type,
514            required: false,
515            min: None,
516            max: None,
517            min_length: None,
518            max_length: None,
519            allowed_values: None,
520        }
521    }
522
523    pub fn required(mut self) -> Self {
524        self.required = true;
525        self
526    }
527
528    pub fn with_range(mut self, min: f64, max: f64) -> Self {
529        self.min = Some(min);
530        self.max = Some(max);
531        self
532    }
533
534    pub fn with_length(mut self, min: usize, max: usize) -> Self {
535        self.min_length = Some(min);
536        self.max_length = Some(max);
537        self
538    }
539
540    pub fn with_allowed_values(mut self, values: Vec<String>) -> Self {
541        self.allowed_values = Some(values);
542        self
543    }
544}
545
546/// Configuration schema validator: validates configuration values against field
547/// definitions.
548pub struct SchemaValidator {
549    fields: Vec<ConfigFieldSchema>,
550}
551
552/// Validation error
553#[derive(Debug, Clone, PartialEq, Eq)]
554pub enum ValidationError {
555    /// Required field is missing
556    MissingRequired(String),
557    /// Type mismatch
558    TypeMismatch(String, String),
559    /// Numeric value out of range
560    OutOfRange(String, String),
561    /// Length exceeds the limit
562    LengthExceeded(String, String),
563    /// Value is not in the allowed enum set
564    NotAllowed(String, String),
565    /// Invalid format
566    InvalidFormat(String, String),
567}
568
569impl std::fmt::Display for ValidationError {
570    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
571        match self {
572            ValidationError::MissingRequired(k) => write!(f, "missing required field: {}", k),
573            ValidationError::TypeMismatch(k, v) => write!(f, "type mismatch for {}: {}", k, v),
574            ValidationError::OutOfRange(k, v) => write!(f, "value out of range for {}: {}", k, v),
575            ValidationError::LengthExceeded(k, v) => {
576                write!(f, "length exceeded for {}: {}", k, v)
577            }
578            ValidationError::NotAllowed(k, v) => write!(f, "value not allowed for {}: {}", k, v),
579            ValidationError::InvalidFormat(k, v) => write!(f, "invalid format for {}: {}", k, v),
580        }
581    }
582}
583
584impl SchemaValidator {
585    pub fn new() -> Self {
586        Self { fields: Vec::new() }
587    }
588
589    pub fn add_field(&mut self, schema: ConfigFieldSchema) {
590        self.fields.push(schema);
591    }
592
593    /// Validate the configuration map, returning all validation errors
594    /// (an empty Vec means validation passed).
595    pub fn validate(&self, config: &HashMap<String, String>) -> Vec<ValidationError> {
596        let mut errors = Vec::new();
597        for field in &self.fields {
598            match config.get(&field.name) {
599                None => {
600                    if field.required {
601                        errors.push(ValidationError::MissingRequired(field.name.clone()));
602                    }
603                }
604                Some(value) => {
605                    if let Err(e) = self.validate_field(field, value) {
606                        errors.push(e);
607                    }
608                }
609            }
610        }
611        errors
612    }
613
614    fn validate_field(
615        &self,
616        schema: &ConfigFieldSchema,
617        value: &str,
618    ) -> Result<(), ValidationError> {
619        // 枚举值检查
620        if let Some(allowed) = &schema.allowed_values {
621            if !allowed.iter().any(|v| v == value) {
622                return Err(ValidationError::NotAllowed(
623                    schema.name.clone(),
624                    value.to_string(),
625                ));
626            }
627        }
628
629        match schema.field_type {
630            ConfigFieldType::String => {
631                if let Some(max_len) = schema.max_length {
632                    if value.len() > max_len {
633                        return Err(ValidationError::LengthExceeded(
634                            schema.name.clone(),
635                            format!("len={} > {}", value.len(), max_len),
636                        ));
637                    }
638                }
639                if let Some(min_len) = schema.min_length {
640                    if value.len() < min_len {
641                        return Err(ValidationError::LengthExceeded(
642                            schema.name.clone(),
643                            format!("len={} < {}", value.len(), min_len),
644                        ));
645                    }
646                }
647            }
648            ConfigFieldType::Integer => {
649                let n: i64 = value.parse().map_err(|_| {
650                    ValidationError::TypeMismatch(schema.name.clone(), value.to_string())
651                })?;
652                if let Some(min) = schema.min {
653                    if (n as f64) < min {
654                        return Err(ValidationError::OutOfRange(
655                            schema.name.clone(),
656                            format!("{} < {}", n, min),
657                        ));
658                    }
659                }
660                if let Some(max) = schema.max {
661                    if (n as f64) > max {
662                        return Err(ValidationError::OutOfRange(
663                            schema.name.clone(),
664                            format!("{} > {}", n, max),
665                        ));
666                    }
667                }
668            }
669            ConfigFieldType::Float => {
670                let n: f64 = value.parse().map_err(|_| {
671                    ValidationError::TypeMismatch(schema.name.clone(), value.to_string())
672                })?;
673                if let Some(min) = schema.min {
674                    if n < min {
675                        return Err(ValidationError::OutOfRange(
676                            schema.name.clone(),
677                            format!("{} < {}", n, min),
678                        ));
679                    }
680                }
681                if let Some(max) = schema.max {
682                    if n > max {
683                        return Err(ValidationError::OutOfRange(
684                            schema.name.clone(),
685                            format!("{} > {}", n, max),
686                        ));
687                    }
688                }
689            }
690            ConfigFieldType::Boolean => {
691                if value != "true" && value != "false" {
692                    return Err(ValidationError::TypeMismatch(
693                        schema.name.clone(),
694                        value.to_string(),
695                    ));
696                }
697            }
698            ConfigFieldType::Url => {
699                if !value.starts_with("http://") && !value.starts_with("https://") {
700                    return Err(ValidationError::InvalidFormat(
701                        schema.name.clone(),
702                        "not a valid URL".to_string(),
703                    ));
704                }
705            }
706            ConfigFieldType::Email => {
707                if !value.contains('@') || !value.contains('.') {
708                    return Err(ValidationError::InvalidFormat(
709                        schema.name.clone(),
710                        "not a valid email".to_string(),
711                    ));
712                }
713            }
714        }
715        Ok(())
716    }
717}
718
719impl Default for SchemaValidator {
720    fn default() -> Self {
721        Self::new()
722    }
723}
724
725// ====================================================================
726// 配置加密 — 对敏感配置值进行加解密
727// ====================================================================
728
729/// Configuration encryptor: performs symmetric encryption on sensitive
730/// configuration values using XOR + Base64.
731///
732/// Applicable scenarios: database passwords, API keys, and other sensitive
733/// configuration should not be stored in plaintext in configuration files.
734/// Encryption format: `ENC(base64(xor(plaintext, key)))`
735pub struct ConfigEncryption {
736    /// Encryption key
737    key: Vec<u8>,
738}
739
740/// Prefix for encrypted values, used to identify encrypted configuration items
741pub const ENCRYPTED_PREFIX: &str = "ENC(";
742pub const ENCRYPTED_SUFFIX: &str = ")";
743
744impl ConfigEncryption {
745    pub fn new(key: impl Into<String>) -> Self {
746        Self {
747            key: key.into().into_bytes(),
748        }
749    }
750
751    /// Encrypt plaintext, returning an `ENC(base64)` formatted string
752    pub fn encrypt(&self, plaintext: &str) -> String {
753        let bytes = plaintext.as_bytes();
754        let encrypted: Vec<u8> = bytes
755            .iter()
756            .enumerate()
757            .map(|(i, &b)| b ^ self.key[i % self.key.len()])
758            .collect();
759        let encoded = base64_encode(&encrypted);
760        format!("{}{}{}", ENCRYPTED_PREFIX, encoded, ENCRYPTED_SUFFIX)
761    }
762
763    /// Decrypt an `ENC(base64)` formatted string, returning the plaintext
764    pub fn decrypt(&self, ciphertext: &str) -> Result<String, String> {
765        let inner = ciphertext
766            .strip_prefix(ENCRYPTED_PREFIX)
767            .and_then(|s| s.strip_suffix(ENCRYPTED_SUFFIX))
768            .ok_or_else(|| "invalid encrypted format, expected ENC(...)".to_string())?;
769        let decoded = base64_decode(inner)?;
770        let decrypted: Vec<u8> = decoded
771            .iter()
772            .enumerate()
773            .map(|(i, &b)| b ^ self.key[i % self.key.len()])
774            .collect();
775        String::from_utf8(decrypted).map_err(|e| format!("decrypt utf8 error: {}", e))
776    }
777
778    /// Check whether a value is encrypted (starts with ENC( )
779    pub fn is_encrypted(value: &str) -> bool {
780        value.starts_with(ENCRYPTED_PREFIX) && value.ends_with(ENCRYPTED_SUFFIX)
781    }
782
783    /// Decrypt the value if it is encrypted; otherwise return it as-is
784    pub fn decrypt_if_needed(&self, value: &str) -> Result<String, String> {
785        if Self::is_encrypted(value) {
786            self.decrypt(value)
787        } else {
788            Ok(value.to_string())
789        }
790    }
791
792    /// Batch-decrypt all encrypted values in a configuration map
793    pub fn decrypt_config(
794        &self,
795        config: &HashMap<String, String>,
796    ) -> Result<HashMap<String, String>, String> {
797        let mut result = HashMap::new();
798        for (k, v) in config {
799            let decrypted = self.decrypt_if_needed(v)?;
800            result.insert(k.clone(), decrypted);
801        }
802        Ok(result)
803    }
804}
805
806/// Simple Base64 encoding (without depending on an external crate)
807fn base64_encode(data: &[u8]) -> String {
808    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
809    let mut result = String::with_capacity(data.len().div_ceil(3) * 4);
810    let mut i = 0;
811    while i + 3 <= data.len() {
812        let n = ((data[i] as u32) << 16) | ((data[i + 1] as u32) << 8) | (data[i + 2] as u32);
813        result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
814        result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
815        result.push(CHARS[((n >> 6) & 0x3F) as usize] as char);
816        result.push(CHARS[(n & 0x3F) as usize] as char);
817        i += 3;
818    }
819    let remaining = data.len() - i;
820    if remaining == 1 {
821        let n = (data[i] as u32) << 16;
822        result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
823        result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
824        result.push('=');
825        result.push('=');
826    } else if remaining == 2 {
827        let n = ((data[i] as u32) << 16) | ((data[i + 1] as u32) << 8);
828        result.push(CHARS[((n >> 18) & 0x3F) as usize] as char);
829        result.push(CHARS[((n >> 12) & 0x3F) as usize] as char);
830        result.push(CHARS[((n >> 6) & 0x3F) as usize] as char);
831        result.push('=');
832    }
833    result
834}
835
836/// Simple Base64 decoding
837fn base64_decode(s: &str) -> Result<Vec<u8>, String> {
838    const CHARS: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
839    fn char_to_val(c: u8) -> Result<u8, String> {
840        CHARS
841            .iter()
842            .position(|&ch| ch == c)
843            .map(|p| p as u8)
844            .ok_or_else(|| format!("invalid base64 char: {}", c as char))
845    }
846    let trimmed = s.trim_end_matches('=');
847    let bytes = trimmed.as_bytes();
848    let mut result = Vec::with_capacity(bytes.len() * 3 / 4);
849    let mut i = 0;
850    while i + 4 <= bytes.len() {
851        let a = char_to_val(bytes[i])? as u32;
852        let b = char_to_val(bytes[i + 1])? as u32;
853        let c = char_to_val(bytes[i + 2])? as u32;
854        let d = char_to_val(bytes[i + 3])? as u32;
855        let n = (a << 18) | (b << 12) | (c << 6) | d;
856        result.push((n >> 16) as u8);
857        result.push((n >> 8) as u8);
858        result.push(n as u8);
859        i += 4;
860    }
861    let remaining = bytes.len() - i;
862    if remaining == 2 {
863        let a = char_to_val(bytes[i])? as u32;
864        let b = char_to_val(bytes[i + 1])? as u32;
865        let n = (a << 18) | (b << 12);
866        result.push((n >> 16) as u8);
867    } else if remaining == 3 {
868        let a = char_to_val(bytes[i])? as u32;
869        let b = char_to_val(bytes[i + 1])? as u32;
870        let c = char_to_val(bytes[i + 2])? as u32;
871        let n = (a << 18) | (b << 12) | (c << 6);
872        result.push((n >> 16) as u8);
873        result.push((n >> 8) as u8);
874    } else if remaining != 0 {
875        return Err(format!("invalid base64 length, remainder: {}", remaining));
876    }
877    Ok(result)
878}
879
880#[cfg(test)]
881mod tests {
882    use super::*;
883    use std::sync::atomic::{AtomicU32, Ordering};
884
885    #[test]
886    fn test_consul_set_and_get() {
887        let mut c = ConsulConfigCenter::new();
888        c.set("k", "v");
889        assert_eq!(c.get("k"), Some("v".to_string()));
890        assert!(c.exists("k"));
891        assert!(!c.exists("missing"));
892    }
893
894    #[test]
895    fn test_consul_get_missing() {
896        let c = ConsulConfigCenter::new();
897        assert_eq!(c.get("missing"), None);
898    }
899
900    #[test]
901    fn test_consul_delete() {
902        let mut c = ConsulConfigCenter::new();
903        c.set("k", "v");
904        assert!(c.delete("k"));
905        assert!(!c.exists("k"));
906        assert_eq!(c.get("k"), None);
907        // Deleting a missing key returns false
908        assert!(!c.delete("missing"));
909    }
910
911    #[test]
912    fn test_consul_list_sorted() {
913        let mut c = ConsulConfigCenter::new();
914        c.set("z", "1");
915        c.set("a", "2");
916        c.set("m", "3");
917        assert_eq!(c.list(), vec!["a", "m", "z"]);
918    }
919
920    #[test]
921    fn test_consul_watch_returns_true() {
922        let c = ConsulConfigCenter::new();
923        assert!(c.watch("any-key"));
924    }
925
926    #[test]
927    fn test_nacos_set_and_get() {
928        let mut c = NacosConfigCenter::new();
929        c.set("k", "v");
930        assert_eq!(c.get("k"), Some("v".to_string()));
931    }
932
933    #[test]
934    fn test_nacos_watch_returns_true() {
935        let c = NacosConfigCenter::new();
936        assert!(c.watch("k"));
937    }
938
939    #[test]
940    fn test_nacos_delete() {
941        let mut c = NacosConfigCenter::new();
942        c.set("k", "v");
943        assert!(c.delete("k"));
944        assert_eq!(c.get("k"), None);
945    }
946
947    #[test]
948    fn test_nacos_list_sorted() {
949        let mut c = NacosConfigCenter::new();
950        c.set("b", "1");
951        c.set("a", "2");
952        assert_eq!(c.list(), vec!["a", "b"]);
953    }
954
955    // ---- Subscribe / notify tests ----
956
957    #[test]
958    fn test_consul_subscribe_receives_set_events() {
959        let mut c = ConsulConfigCenter::new();
960        let count = Arc::new(AtomicU32::new(0));
961        let last_value = Arc::new(Mutex::new(String::new()));
962
963        let cb_count = count.clone();
964        let cb_value = last_value.clone();
965        c.subscribe(
966            "app.config",
967            Arc::new(move |_key, value| {
968                cb_count.fetch_add(1, Ordering::SeqCst);
969                *cb_value.lock().unwrap() = value.to_string();
970            }),
971        );
972
973        c.set("app.config", "v1");
974        assert_eq!(count.load(Ordering::SeqCst), 1);
975        assert_eq!(*last_value.lock().unwrap(), "v1");
976
977        c.set("app.config", "v2");
978        assert_eq!(count.load(Ordering::SeqCst), 2);
979        assert_eq!(*last_value.lock().unwrap(), "v2");
980    }
981
982    #[test]
983    fn test_consul_subscribe_receives_delete_events() {
984        let mut c = ConsulConfigCenter::new();
985        let deleted = Arc::new(AtomicU32::new(0));
986        let last_value = Arc::new(Mutex::new(String::new()));
987
988        let d_count = deleted.clone();
989        let d_value = last_value.clone();
990        c.subscribe(
991            "k",
992            Arc::new(move |_key, value| {
993                d_count.fetch_add(1, Ordering::SeqCst);
994                *d_value.lock().unwrap() = value.to_string();
995            }),
996        );
997
998        c.set("k", "v");
999        c.delete("k");
1000        assert_eq!(deleted.load(Ordering::SeqCst), 2); // set + delete
1001        assert_eq!(*last_value.lock().unwrap(), ""); // delete sends empty
1002    }
1003
1004    #[test]
1005    fn test_consul_multiple_subscribers() {
1006        let mut c = ConsulConfigCenter::new();
1007        let c1 = Arc::new(AtomicU32::new(0));
1008        let c2 = Arc::new(AtomicU32::new(0));
1009
1010        let c1_clone = c1.clone();
1011        c.subscribe(
1012            "k",
1013            Arc::new(move |_key, _value| {
1014                c1_clone.fetch_add(1, Ordering::SeqCst);
1015            }),
1016        );
1017
1018        let c2_clone = c2.clone();
1019        c.subscribe(
1020            "k",
1021            Arc::new(move |_key, _value| {
1022                c2_clone.fetch_add(1, Ordering::SeqCst);
1023            }),
1024        );
1025
1026        assert_eq!(c.subscriber_count("k"), 2);
1027        c.set("k", "v");
1028        assert_eq!(c1.load(Ordering::SeqCst), 1);
1029        assert_eq!(c2.load(Ordering::SeqCst), 1);
1030    }
1031
1032    #[test]
1033    fn test_consul_subscribers_are_keyed() {
1034        let mut c = ConsulConfigCenter::new();
1035        let other_count = Arc::new(AtomicU32::new(0));
1036        let oc = other_count.clone();
1037        c.subscribe(
1038            "other",
1039            Arc::new(move |_key, _value| {
1040                oc.fetch_add(1, Ordering::SeqCst);
1041            }),
1042        );
1043
1044        c.set("this", "v");
1045        // Should not notify subscribers of "other"
1046        assert_eq!(other_count.load(Ordering::SeqCst), 0);
1047
1048        c.set("other", "v");
1049        assert_eq!(other_count.load(Ordering::SeqCst), 1);
1050    }
1051
1052    #[test]
1053    fn test_consul_events_record() {
1054        let mut c = ConsulConfigCenter::new();
1055        c.set("a", "1");
1056        c.set("b", "2");
1057        c.delete("a");
1058
1059        let events = c.events();
1060        assert_eq!(events.len(), 3);
1061        assert_eq!(events[0].key, "a");
1062        assert_eq!(events[0].value, "1");
1063        assert!(!events[0].deleted);
1064        assert_eq!(events[2].key, "a");
1065        assert!(events[2].deleted);
1066    }
1067
1068    #[test]
1069    fn test_nacos_subscribe_receives_events() {
1070        let mut c = NacosConfigCenter::new();
1071        let received = Arc::new(Mutex::new(Vec::<(String, String)>::new()));
1072        let r = received.clone();
1073        c.subscribe(
1074            "cfg",
1075            Arc::new(move |key, value| {
1076                r.lock().unwrap().push((key.to_string(), value.to_string()));
1077            }),
1078        );
1079        c.set("cfg", "v1");
1080        c.set("cfg", "v2");
1081        let received = received.lock().unwrap();
1082        assert_eq!(
1083            *received,
1084            vec![
1085                ("cfg".to_string(), "v1".to_string()),
1086                ("cfg".to_string(), "v2".to_string()),
1087            ]
1088        );
1089    }
1090
1091    #[test]
1092    fn test_nacos_events_record() {
1093        let mut c = NacosConfigCenter::new();
1094        c.set("k", "v");
1095        let events = c.events();
1096        assert_eq!(events.len(), 1);
1097        assert_eq!(events[0].key, "k");
1098        assert_eq!(events[0].value, "v");
1099    }
1100
1101    #[test]
1102    fn test_subscribe_via_trait_object() {
1103        // Verify subscribe works through a boxed trait object.
1104        let mut boxed: Box<dyn ConfigCenter> = Box::new(ConsulConfigCenter::new());
1105        let count = Arc::new(AtomicU32::new(0));
1106        let c = count.clone();
1107        boxed.subscribe(
1108            "k",
1109            Arc::new(move |_key, _value| {
1110                c.fetch_add(1, Ordering::SeqCst);
1111            }),
1112        );
1113        boxed.set("k", "v");
1114        assert_eq!(count.load(Ordering::SeqCst), 1);
1115    }
1116
1117    #[test]
1118    fn test_overwrite_existing_value_notifies() {
1119        let mut c = ConsulConfigCenter::new();
1120        let count = Arc::new(AtomicU32::new(0));
1121        let c1 = count.clone();
1122        c.subscribe(
1123            "k",
1124            Arc::new(move |_key, _value| {
1125                c1.fetch_add(1, Ordering::SeqCst);
1126            }),
1127        );
1128        c.set("k", "v1");
1129        c.set("k", "v2");
1130        c.set("k", "v3");
1131        assert_eq!(count.load(Ordering::SeqCst), 3);
1132    }
1133
1134    // ====================================================================
1135    // ConfigWatcher 测试
1136    // ====================================================================
1137
1138    #[test]
1139    fn test_config_watcher_detects_new_key() {
1140        let mut center = ConsulConfigCenter::new();
1141        let watcher = ConfigWatcher::new(1000);
1142        let received = Arc::new(Mutex::new(String::new()));
1143        let r = received.clone();
1144        watcher.watch(
1145            "app.port",
1146            Arc::new(move |_k, v| {
1147                *r.lock().unwrap() = v.to_string();
1148            }),
1149        );
1150
1151        center.set("app.port", "8080");
1152        let changes = watcher.poll(&center);
1153        assert_eq!(changes, 1);
1154        assert_eq!(*received.lock().unwrap(), "8080");
1155    }
1156
1157    #[test]
1158    fn test_config_watcher_detects_value_change() {
1159        let mut center = ConsulConfigCenter::new();
1160        center.set("k", "v1");
1161        let watcher = ConfigWatcher::new(1000);
1162        // 第一次 poll 建立基线
1163        watcher.poll(&center);
1164
1165        let received = Arc::new(AtomicU32::new(0));
1166        let r = received.clone();
1167        watcher.watch(
1168            "k",
1169            Arc::new(move |_, _| {
1170                r.fetch_add(1, Ordering::SeqCst);
1171            }),
1172        );
1173
1174        center.set("k", "v2");
1175        let changes = watcher.poll(&center);
1176        assert_eq!(changes, 1);
1177        assert_eq!(received.load(Ordering::SeqCst), 1);
1178    }
1179
1180    #[test]
1181    fn test_config_watcher_detects_deletion() {
1182        let mut center = ConsulConfigCenter::new();
1183        center.set("k", "v");
1184        let watcher = ConfigWatcher::new(1000);
1185        watcher.poll(&center);
1186
1187        let deleted_value = Arc::new(Mutex::new("not_empty".to_string()));
1188        let d = deleted_value.clone();
1189        watcher.watch(
1190            "k",
1191            Arc::new(move |_, v| {
1192                *d.lock().unwrap() = v.to_string();
1193            }),
1194        );
1195
1196        center.delete("k");
1197        let changes = watcher.poll(&center);
1198        assert_eq!(changes, 1);
1199        assert_eq!(*deleted_value.lock().unwrap(), "");
1200    }
1201
1202    #[test]
1203    fn test_config_watcher_no_change_returns_zero() {
1204        let mut center = ConsulConfigCenter::new();
1205        center.set("k", "v");
1206        let watcher = ConfigWatcher::new(1000);
1207        watcher.poll(&center);
1208        // 再次 poll 无变更
1209        let changes = watcher.poll(&center);
1210        assert_eq!(changes, 0);
1211    }
1212
1213    #[test]
1214    fn test_config_watcher_watcher_count() {
1215        let watcher = ConfigWatcher::new(1000);
1216        assert_eq!(watcher.watcher_count(), 0);
1217        watcher.watch("a", Arc::new(|_, _| {}));
1218        watcher.watch("b", Arc::new(|_, _| {}));
1219        assert_eq!(watcher.watcher_count(), 2);
1220    }
1221
1222    #[test]
1223    fn test_config_watcher_default_interval() {
1224        let watcher = ConfigWatcher::default();
1225        assert_eq!(watcher.poll_interval_ms, 5000);
1226    }
1227
1228    #[test]
1229    fn test_config_watcher_min_interval_clamped() {
1230        let watcher = ConfigWatcher::new(10);
1231        assert_eq!(watcher.poll_interval_ms, 100);
1232    }
1233
1234    // ====================================================================
1235    // MultiSourceConfig 测试
1236    // ====================================================================
1237
1238    #[test]
1239    fn test_multi_source_env_overrides_remote_overrides_file() {
1240        let mut ms = MultiSourceConfig::new();
1241        let mut file = HashMap::new();
1242        file.insert("k".to_string(), "file_val".to_string());
1243        ms.set_file_config(file);
1244
1245        let mut remote = HashMap::new();
1246        remote.insert("k".to_string(), "remote_val".to_string());
1247        ms.set_remote_config(remote);
1248
1249        let mut env = HashMap::new();
1250        env.insert("k".to_string(), "env_val".to_string());
1251        ms.set_env_config(env);
1252
1253        assert_eq!(ms.get("k"), Some("env_val".to_string()));
1254        assert_eq!(ms.source_of("k"), Some(ConfigSourcePriority::Env));
1255    }
1256
1257    #[test]
1258    fn test_multi_source_remote_overrides_file() {
1259        let mut ms = MultiSourceConfig::new();
1260        let mut file = HashMap::new();
1261        file.insert("k".to_string(), "file_val".to_string());
1262        ms.set_file_config(file);
1263
1264        let mut remote = HashMap::new();
1265        remote.insert("k".to_string(), "remote_val".to_string());
1266        ms.set_remote_config(remote);
1267
1268        assert_eq!(ms.get("k"), Some("remote_val".to_string()));
1269        assert_eq!(ms.source_of("k"), Some(ConfigSourcePriority::Remote));
1270    }
1271
1272    #[test]
1273    fn test_multi_source_file_only() {
1274        let mut ms = MultiSourceConfig::new();
1275        let mut file = HashMap::new();
1276        file.insert("k".to_string(), "file_val".to_string());
1277        ms.set_file_config(file);
1278
1279        assert_eq!(ms.get("k"), Some("file_val".to_string()));
1280        assert_eq!(ms.source_of("k"), Some(ConfigSourcePriority::File));
1281    }
1282
1283    #[test]
1284    fn test_multi_source_missing_key_returns_none() {
1285        let ms = MultiSourceConfig::new();
1286        assert_eq!(ms.get("missing"), None);
1287        assert_eq!(ms.source_of("missing"), None);
1288    }
1289
1290    #[test]
1291    fn test_multi_source_merge_combines_all_keys() {
1292        let mut ms = MultiSourceConfig::new();
1293        let mut file = HashMap::new();
1294        file.insert("file_key".to_string(), "fv".to_string());
1295        ms.set_file_config(file);
1296
1297        let mut remote = HashMap::new();
1298        remote.insert("remote_key".to_string(), "rv".to_string());
1299        ms.set_remote_config(remote);
1300
1301        let mut env = HashMap::new();
1302        env.insert("env_key".to_string(), "ev".to_string());
1303        ms.set_env_config(env);
1304
1305        let merged = ms.merge();
1306        assert_eq!(merged.len(), 3);
1307        assert_eq!(merged.get("file_key"), Some(&"fv".to_string()));
1308        assert_eq!(merged.get("remote_key"), Some(&"rv".to_string()));
1309        assert_eq!(merged.get("env_key"), Some(&"ev".to_string()));
1310    }
1311
1312    #[test]
1313    fn test_multi_source_load_json() {
1314        let mut ms = MultiSourceConfig::new();
1315        ms.load_json(r#"{"host":"localhost","port":"3306"}"#)
1316            .unwrap();
1317        assert_eq!(ms.get("host"), Some("localhost".to_string()));
1318        assert_eq!(ms.get("port"), Some("3306".to_string()));
1319    }
1320
1321    #[test]
1322    fn test_multi_source_load_json_invalid() {
1323        let mut ms = MultiSourceConfig::new();
1324        assert!(ms.load_json("not json").is_err());
1325    }
1326
1327    #[test]
1328    fn test_multi_source_cache_invalidated_on_update() {
1329        let mut ms = MultiSourceConfig::new();
1330        let mut file = HashMap::new();
1331        file.insert("k".to_string(), "v1".to_string());
1332        ms.set_file_config(file);
1333        assert_eq!(ms.get("k"), Some("v1".to_string()));
1334
1335        let mut file2 = HashMap::new();
1336        file2.insert("k".to_string(), "v2".to_string());
1337        ms.set_file_config(file2);
1338        assert_eq!(ms.get("k"), Some("v2".to_string()));
1339    }
1340
1341    // ====================================================================
1342    // SchemaValidator 测试
1343    // ====================================================================
1344
1345    #[test]
1346    fn test_schema_validates_integer_in_range() {
1347        let mut validator = SchemaValidator::new();
1348        validator.add_field(
1349            ConfigFieldSchema::new("port", ConfigFieldType::Integer)
1350                .required()
1351                .with_range(1.0, 65535.0),
1352        );
1353        let mut config = HashMap::new();
1354        config.insert("port".to_string(), "3306".to_string());
1355        let errors = validator.validate(&config);
1356        assert!(errors.is_empty(), "expected no errors, got: {:?}", errors);
1357    }
1358
1359    #[test]
1360    fn test_schema_missing_required_field() {
1361        let mut validator = SchemaValidator::new();
1362        validator.add_field(ConfigFieldSchema::new("port", ConfigFieldType::Integer).required());
1363        let config = HashMap::new();
1364        let errors = validator.validate(&config);
1365        assert_eq!(errors.len(), 1);
1366        assert_eq!(
1367            errors[0],
1368            ValidationError::MissingRequired("port".to_string())
1369        );
1370    }
1371
1372    #[test]
1373    fn test_schema_integer_out_of_range() {
1374        let mut validator = SchemaValidator::new();
1375        validator.add_field(
1376            ConfigFieldSchema::new("port", ConfigFieldType::Integer).with_range(1.0, 65535.0),
1377        );
1378        let mut config = HashMap::new();
1379        config.insert("port".to_string(), "99999".to_string());
1380        let errors = validator.validate(&config);
1381        assert_eq!(errors.len(), 1);
1382        assert!(matches!(errors[0], ValidationError::OutOfRange(_, _)));
1383    }
1384
1385    #[test]
1386    fn test_schema_type_mismatch() {
1387        let mut validator = SchemaValidator::new();
1388        validator.add_field(ConfigFieldSchema::new("port", ConfigFieldType::Integer));
1389        let mut config = HashMap::new();
1390        config.insert("port".to_string(), "not_a_number".to_string());
1391        let errors = validator.validate(&config);
1392        assert_eq!(errors.len(), 1);
1393        assert!(matches!(errors[0], ValidationError::TypeMismatch(_, _)));
1394    }
1395
1396    #[test]
1397    fn test_schema_boolean_valid() {
1398        let mut validator = SchemaValidator::new();
1399        validator.add_field(ConfigFieldSchema::new("debug", ConfigFieldType::Boolean));
1400        let mut config = HashMap::new();
1401        config.insert("debug".to_string(), "true".to_string());
1402        assert!(validator.validate(&config).is_empty());
1403
1404        config.insert("debug".to_string(), "false".to_string());
1405        assert!(validator.validate(&config).is_empty());
1406    }
1407
1408    #[test]
1409    fn test_schema_boolean_invalid() {
1410        let mut validator = SchemaValidator::new();
1411        validator.add_field(ConfigFieldSchema::new("debug", ConfigFieldType::Boolean));
1412        let mut config = HashMap::new();
1413        config.insert("debug".to_string(), "yes".to_string());
1414        let errors = validator.validate(&config);
1415        assert_eq!(errors.len(), 1);
1416        assert!(matches!(errors[0], ValidationError::TypeMismatch(_, _)));
1417    }
1418
1419    #[test]
1420    fn test_schema_string_length() {
1421        let mut validator = SchemaValidator::new();
1422        validator
1423            .add_field(ConfigFieldSchema::new("name", ConfigFieldType::String).with_length(2, 10));
1424        let mut config = HashMap::new();
1425        config.insert("name".to_string(), "ab".to_string());
1426        assert!(validator.validate(&config).is_empty());
1427
1428        config.insert("name".to_string(), "a".to_string());
1429        assert_eq!(validator.validate(&config).len(), 1);
1430
1431        config.insert("name".to_string(), "this_is_way_too_long".to_string());
1432        assert_eq!(validator.validate(&config).len(), 1);
1433    }
1434
1435    #[test]
1436    fn test_schema_allowed_values() {
1437        let mut validator = SchemaValidator::new();
1438        validator.add_field(
1439            ConfigFieldSchema::new("level", ConfigFieldType::String).with_allowed_values(vec![
1440                "info".into(),
1441                "warn".into(),
1442                "error".into(),
1443            ]),
1444        );
1445        let mut config = HashMap::new();
1446        config.insert("level".to_string(), "info".to_string());
1447        assert!(validator.validate(&config).is_empty());
1448
1449        config.insert("level".to_string(), "debug".to_string());
1450        let errors = validator.validate(&config);
1451        assert_eq!(errors.len(), 1);
1452        assert!(matches!(errors[0], ValidationError::NotAllowed(_, _)));
1453    }
1454
1455    #[test]
1456    fn test_schema_url_format() {
1457        let mut validator = SchemaValidator::new();
1458        validator.add_field(ConfigFieldSchema::new("endpoint", ConfigFieldType::Url));
1459        let mut config = HashMap::new();
1460        config.insert("endpoint".to_string(), "https://example.com".to_string());
1461        assert!(validator.validate(&config).is_empty());
1462
1463        config.insert("endpoint".to_string(), "ftp://bad".to_string());
1464        assert_eq!(validator.validate(&config).len(), 1);
1465    }
1466
1467    #[test]
1468    fn test_schema_email_format() {
1469        let mut validator = SchemaValidator::new();
1470        validator.add_field(ConfigFieldSchema::new("email", ConfigFieldType::Email));
1471        let mut config = HashMap::new();
1472        config.insert("email".to_string(), "user@example.com".to_string());
1473        assert!(validator.validate(&config).is_empty());
1474
1475        config.insert("email".to_string(), "not_an_email".to_string());
1476        assert_eq!(validator.validate(&config).len(), 1);
1477    }
1478
1479    #[test]
1480    fn test_schema_float_range() {
1481        let mut validator = SchemaValidator::new();
1482        validator.add_field(
1483            ConfigFieldSchema::new("ratio", ConfigFieldType::Float).with_range(0.0, 1.0),
1484        );
1485        let mut config = HashMap::new();
1486        config.insert("ratio".to_string(), "0.5".to_string());
1487        assert!(validator.validate(&config).is_empty());
1488
1489        config.insert("ratio".to_string(), "1.5".to_string());
1490        assert_eq!(validator.validate(&config).len(), 1);
1491    }
1492
1493    #[test]
1494    fn test_schema_multiple_errors() {
1495        let mut validator = SchemaValidator::new();
1496        validator.add_field(ConfigFieldSchema::new("a", ConfigFieldType::Integer).required());
1497        validator.add_field(ConfigFieldSchema::new("b", ConfigFieldType::Boolean).required());
1498        let config = HashMap::new();
1499        let errors = validator.validate(&config);
1500        assert_eq!(errors.len(), 2);
1501    }
1502
1503    #[test]
1504    fn test_schema_optional_field_missing_ok() {
1505        let mut validator = SchemaValidator::new();
1506        validator.add_field(ConfigFieldSchema::new("optional", ConfigFieldType::String));
1507        let config = HashMap::new();
1508        assert!(validator.validate(&config).is_empty());
1509    }
1510
1511    // ====================================================================
1512    // ConfigEncryption 测试
1513    // ====================================================================
1514
1515    #[test]
1516    fn test_encryption_roundtrip() {
1517        let enc = ConfigEncryption::new("my_secret_key");
1518        let plaintext = "database_password_123";
1519        let ciphertext = enc.encrypt(plaintext);
1520        assert!(ConfigEncryption::is_encrypted(&ciphertext));
1521        let decrypted = enc.decrypt(&ciphertext).unwrap();
1522        assert_eq!(decrypted, plaintext);
1523    }
1524
1525    #[test]
1526    fn test_encryption_is_encrypted_detection() {
1527        assert!(ConfigEncryption::is_encrypted("ENC(abc123)"));
1528        assert!(!ConfigEncryption::is_encrypted("plaintext"));
1529        assert!(!ConfigEncryption::is_encrypted("ENC(incomplete"));
1530    }
1531
1532    #[test]
1533    fn test_encryption_decrypt_if_needed_for_plaintext() {
1534        let enc = ConfigEncryption::new("key");
1535        let result = enc.decrypt_if_needed("plain_value").unwrap();
1536        assert_eq!(result, "plain_value");
1537    }
1538
1539    #[test]
1540    fn test_encryption_decrypt_if_needed_for_ciphertext() {
1541        let enc = ConfigEncryption::new("key");
1542        let ciphertext = enc.encrypt("secret");
1543        let result = enc.decrypt_if_needed(&ciphertext).unwrap();
1544        assert_eq!(result, "secret");
1545    }
1546
1547    #[test]
1548    fn test_encryption_decrypt_invalid_format_errors() {
1549        let enc = ConfigEncryption::new("key");
1550        assert!(enc.decrypt("not_encrypted").is_err());
1551        assert!(enc.decrypt("ENC(incomplete").is_err());
1552    }
1553
1554    #[test]
1555    fn test_encryption_decrypt_config_batch() {
1556        let enc = ConfigEncryption::new("master_key");
1557        let mut config = HashMap::new();
1558        config.insert("host".to_string(), "localhost".to_string());
1559        config.insert("password".to_string(), enc.encrypt("s3cr3t"));
1560        config.insert("api_key".to_string(), enc.encrypt("abc123"));
1561
1562        let decrypted = enc.decrypt_config(&config).unwrap();
1563        assert_eq!(decrypted.get("host"), Some(&"localhost".to_string()));
1564        assert_eq!(decrypted.get("password"), Some(&"s3cr3t".to_string()));
1565        assert_eq!(decrypted.get("api_key"), Some(&"abc123".to_string()));
1566    }
1567
1568    #[test]
1569    fn test_encryption_different_keys_produce_different_output() {
1570        let enc1 = ConfigEncryption::new("key1");
1571        let enc2 = ConfigEncryption::new("key2");
1572        let plaintext = "same_secret";
1573        let c1 = enc1.encrypt(plaintext);
1574        let c2 = enc2.encrypt(plaintext);
1575        assert_ne!(c1, c2);
1576    }
1577
1578    #[test]
1579    fn test_encryption_same_key_same_plaintext_deterministic() {
1580        let enc = ConfigEncryption::new("key");
1581        let c1 = enc.encrypt("secret");
1582        let c2 = enc.encrypt("secret");
1583        assert_eq!(c1, c2);
1584    }
1585
1586    #[test]
1587    fn test_base64_encode_decode_roundtrip() {
1588        let data = b"hello world";
1589        let encoded = base64_encode(data);
1590        let decoded = base64_decode(&encoded).unwrap();
1591        assert_eq!(decoded, data);
1592    }
1593
1594    #[test]
1595    fn test_base64_encode_known_value() {
1596        // "Man" -> "TWFu"
1597        assert_eq!(base64_encode(b"Man"), "TWFu");
1598        // "Ma" -> "TWE="
1599        assert_eq!(base64_encode(b"Ma"), "TWE=");
1600        // "M" -> "TQ=="
1601        assert_eq!(base64_encode(b"M"), "TQ==");
1602    }
1603
1604    #[test]
1605    fn test_base64_decode_invalid_char_errors() {
1606        assert!(base64_decode("!!!").is_err());
1607    }
1608
1609    #[test]
1610    fn test_base64_decode_empty_string() {
1611        let result = base64_decode("").unwrap();
1612        assert!(result.is_empty());
1613    }
1614}