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