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