1#[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
29pub type ConfigChangeCallback = Arc<dyn Fn(&str, &str) + Send + Sync>;
32
33pub 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 fn watch(&self, key: &str) -> bool;
43 fn subscribe(&mut self, key: &str, callback: ConfigChangeCallback);
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
49pub struct ConfigChangeEvent {
50 pub key: String,
51 pub value: String,
52 pub deleted: bool,
53}
54
55pub 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 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
142pub 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
228pub struct ConfigWatcher {
240 last_snapshot: Mutex<HashMap<String, String>>,
242 pub poll_interval_ms: u64,
244 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 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 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 for (k, v) in ¤t {
281 match snapshot.get(k) {
282 Some(old) if old == v => {}
283 _ => changes.push((k.clone(), v.clone(), false)),
284 }
285 }
286 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 let _ = deleted;
305 }
306 }
307 watcher_count
308 }
309
310 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
329pub enum ConfigSourcePriority {
330 File = 0,
332 Remote = 1,
334 Env = 2,
336}
337
338pub struct MultiSourceConfig {
342 file_config: HashMap<String, String>,
344 env_config: HashMap<String, String>,
346 remote_config: HashMap<String, String>,
348 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 pub fn set_file_config(&mut self, config: HashMap<String, String>) {
364 self.file_config = config;
365 self.invalidate_cache();
366 }
367
368 pub fn set_env_config(&mut self, config: HashMap<String, String>) {
370 self.env_config = config;
371 self.invalidate_cache();
372 }
373
374 pub fn set_remote_config(&mut self, config: HashMap<String, String>) {
376 self.remote_config = config;
377 self.invalidate_cache();
378 }
379
380 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 self.env_config.insert(stripped.to_lowercase(), value);
386 }
387 }
388 self.invalidate_cache();
389 }
390
391 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 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 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 pub fn get(&self, key: &str) -> Option<String> {
426 self.merge().get(key).cloned()
427 }
428
429 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#[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#[derive(Debug, Clone, Serialize, Deserialize)]
472pub struct ConfigFieldSchema {
473 pub name: String,
475 pub field_type: ConfigFieldType,
477 pub required: bool,
479 pub min: Option<f64>,
481 pub max: Option<f64>,
483 pub min_length: Option<usize>,
485 pub max_length: Option<usize>,
487 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
528pub struct SchemaValidator {
531 fields: Vec<ConfigFieldSchema>,
532}
533
534#[derive(Debug, Clone, PartialEq, Eq)]
536pub enum ValidationError {
537 MissingRequired(String),
539 TypeMismatch(String, String),
541 OutOfRange(String, String),
543 LengthExceeded(String, String),
545 NotAllowed(String, String),
547 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 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 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
707pub struct ConfigEncryption {
718 key: Vec<u8>,
720}
721
722pub 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 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 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 pub fn is_encrypted(value: &str) -> bool {
762 value.starts_with(ENCRYPTED_PREFIX) && value.ends_with(ENCRYPTED_SUFFIX)
763 }
764
765 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 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
788fn 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
818fn 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 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 #[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); assert_eq!(*last_value.lock().unwrap(), ""); }
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 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 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 #[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(¢er);
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 watcher.poll(¢er);
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(¢er);
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(¢er);
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(¢er);
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(¢er);
1190 let changes = watcher.poll(¢er);
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 #[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 #[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 #[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 assert_eq!(base64_encode(b"Man"), "TWFu");
1580 assert_eq!(base64_encode(b"Ma"), "TWE=");
1582 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}