1use parking_lot::RwLock;
49use std::collections::HashMap;
50use std::path::Path;
51use std::sync::Arc;
52use thiserror::Error;
53use tokio::fs;
54
55#[derive(Debug, Error)]
61pub enum EnvError {
62 #[error(".env 文件读取失败: {path} — {source}")]
64 FileRead {
65 path: String,
67 #[source]
69 source: std::io::Error,
70 },
71 #[error(".env 文件解析失败: {path} — 行 {line}: {message}")]
73 Parse {
74 path: String,
76 line: usize,
78 message: String,
80 },
81}
82
83#[derive(Debug, Clone, Default)]
112pub struct Env {
113 data: Arc<RwLock<HashMap<String, String>>>,
115}
116
117impl Env {
118 pub fn new() -> Self {
120 Self::default()
121 }
122
123 pub async fn load_from_file(&self, path: impl AsRef<Path>) -> Result<(), EnvError> {
146 let path_ref = path.as_ref();
147 let content = fs::read_to_string(path_ref)
148 .await
149 .map_err(|e| EnvError::FileRead {
150 path: path_ref.display().to_string(),
151 source: e,
152 })?;
153
154 self.parse_ini_content(&content, &path_ref.display().to_string())
155 }
156
157 fn parse_ini_content(&self, content: &str, path: &str) -> Result<(), EnvError> {
167 let mut data = self.data.write();
168 let mut current_section: String = String::new();
169
170 for (line_idx, raw_line) in content.lines().enumerate() {
171 let line_no = line_idx + 1;
172 let line = raw_line.trim();
173
174 if line.is_empty() {
176 continue;
177 }
178
179 if line.starts_with('#') || line.starts_with(';') {
181 continue;
182 }
183
184 if line.starts_with('[') {
186 if let Some(end) = line.find(']') {
187 current_section = line[1..end].trim().to_string();
188 } else {
189 return Err(EnvError::Parse {
190 path: path.to_string(),
191 line: line_no,
192 message: "section 头缺少闭合的 ']'".to_string(),
193 });
194 }
195 continue;
196 }
197
198 if let Some(eq_pos) = line.find('=') {
200 let key = line[..eq_pos].trim().to_string();
201 let mut value = line[eq_pos + 1..].trim().to_string();
202
203 if key.is_empty() {
204 return Err(EnvError::Parse {
205 path: path.to_string(),
206 line: line_no,
207 message: "键为空".to_string(),
208 });
209 }
210
211 if value.len() >= 2 {
213 let first = value.chars().next().expect("已检查 value.len() >= 2");
214 let last = value.chars().last().expect("已检查 value.len() >= 2");
215 if (first == '"' && last == '"') || (first == '\'' && last == '\'') {
216 value = value[1..value.len() - 1].to_string();
217 }
218 }
219
220 let full_key = if current_section.is_empty() {
222 key
223 } else {
224 format!("{}.{}", current_section, key)
225 };
226
227 data.insert(full_key, value);
228 } else {
229 return Err(EnvError::Parse {
230 path: path.to_string(),
231 line: line_no,
232 message: "缺少 '=' 分隔符".to_string(),
233 });
234 }
235 }
236
237 Ok(())
238 }
239
240 pub fn get(&self, name: &str) -> Option<String> {
261 if let Ok(value) = std::env::var(name) {
263 if !value.is_empty() {
264 return Some(value);
265 }
266 }
267
268 let data = self.data.read();
270 data.get(name).cloned()
271 }
272
273 pub fn get_with_default(&self, name: &str, default: &str) -> String {
290 self.get(name).unwrap_or_else(|| default.to_string())
291 }
292
293 pub fn has(&self, name: &str) -> bool {
313 if let Ok(value) = std::env::var(name) {
315 if !value.is_empty() {
316 return true;
317 }
318 }
319
320 let data = self.data.read();
322 data.contains_key(name)
323 }
324
325 pub fn set(&self, name: &str, value: &str) {
345 let mut data = self.data.write();
346 data.insert(name.to_string(), value.to_string());
347 }
348
349 pub fn remove(&self, name: &str) -> bool {
363 let mut data = self.data.write();
364 data.remove(name).is_some()
365 }
366
367 pub fn all(&self) -> HashMap<String, String> {
378 let data = self.data.read();
379 data.clone()
380 }
381
382 pub fn clear(&self) {
388 let mut data = self.data.write();
389 data.clear();
390 }
391}
392
393#[cfg(test)]
398mod tests {
399 static ENV_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
400 use super::*;
401 use std::io::Write;
402
403 #[test]
405 fn test_new_env_is_empty() {
406 let env = Env::new();
407 assert!(env.all().is_empty());
408 assert!(!env.has("NON_EXISTENT_KEY"));
409 assert_eq!(env.get("NON_EXISTENT_KEY"), None);
410 }
411
412 #[test]
414 fn test_set_get_remove() {
415 let env = Env::new();
416
417 env.set("APP_KEY", "base64:xxxxxx");
418 assert!(env.has("APP_KEY"));
419 assert_eq!(env.get("APP_KEY"), Some("base64:xxxxxx".to_string()));
420
421 assert!(env.remove("APP_KEY"));
422 assert!(!env.has("APP_KEY"));
423 assert_eq!(env.get("APP_KEY"), None);
424 }
425
426 #[test]
428 fn test_get_with_default() {
429 let env = Env::new();
430
431 assert_eq!(env.get_with_default("MISSING", "fallback"), "fallback");
433
434 env.set("EXISTING", "actual");
436 assert_eq!(env.get_with_default("EXISTING", "fallback"), "actual");
437 }
438
439 #[test]
441 fn test_load_from_ini_content_with_section() {
442 let env = Env::new();
443 let content = r#"
444# 顶层配置
445APP_DEBUG = true
446APP_KEY = "base64:secret"
447
448[database]
449hostname = localhost
450port = 3306
451
452[redis]
453host = "127.0.0.1"
454"#;
455 env.parse_ini_content(content, "<test>").unwrap();
456
457 assert_eq!(env.get("APP_DEBUG"), Some("true".to_string()));
459 assert_eq!(env.get("APP_KEY"), Some("base64:secret".to_string()));
460
461 assert_eq!(env.get("database.hostname"), Some("localhost".to_string()));
463 assert_eq!(env.get("database.port"), Some("3306".to_string()));
464 assert_eq!(env.get("redis.host"), Some("127.0.0.1".to_string()));
465 }
466
467 #[test]
469 fn test_quote_stripping() {
470 let env = Env::new();
471 let content = r#"
472DOUBLE = "value with spaces"
473SINGLE = 'another value'
474NO_QUOTE = plain
475EMPTY = ""
476"#;
477 env.parse_ini_content(content, "<test>").unwrap();
478
479 assert_eq!(env.get("DOUBLE"), Some("value with spaces".to_string()));
480 assert_eq!(env.get("SINGLE"), Some("another value".to_string()));
481 assert_eq!(env.get("NO_QUOTE"), Some("plain".to_string()));
482 assert_eq!(env.get("EMPTY"), Some("".to_string()));
483 }
484
485 #[test]
487 fn test_comment_lines_skipped() {
488 let env = Env::new();
489 let content = r#"
490# 这是注释
491APP_KEY = value1
492; 这也是注释
493APP_DEBUG = value2
494"#;
495 env.parse_ini_content(content, "<test>").unwrap();
496
497 assert_eq!(env.get("APP_KEY"), Some("value1".to_string()));
498 assert_eq!(env.get("APP_DEBUG"), Some("value2".to_string()));
499 }
500
501 #[tokio::test]
503 async fn test_load_from_file() {
504 let temp_dir = std::env::temp_dir().join("sz_rust_env_test");
506 let _ = std::fs::create_dir_all(&temp_dir);
507 let env_file = temp_dir.join(".env");
508
509 let mut file = std::fs::File::create(&env_file).unwrap();
510 writeln!(file, "TEST_KEY = test_value").unwrap();
511 writeln!(file).unwrap();
512 writeln!(file, "[section]").unwrap();
513 writeln!(file, "inner = inner_value").unwrap();
514 drop(file);
515
516 let env = Env::new();
517 env.load_from_file(&env_file).await.unwrap();
518
519 assert_eq!(env.get("TEST_KEY"), Some("test_value".to_string()));
520 assert_eq!(env.get("section.inner"), Some("inner_value".to_string()));
521
522 let _ = std::fs::remove_dir_all(&temp_dir);
523 }
524
525 #[tokio::test]
527 async fn test_load_nonexistent_file_errors() {
528 let env = Env::new();
529 let result = env.load_from_file("/nonexistent/path/.env").await;
530 assert!(result.is_err());
531 match result {
532 Err(EnvError::FileRead { .. }) => {}
533 _ => panic!("期望 FileRead 错误"),
534 }
535 }
536
537 #[test]
539 fn test_parse_unclosed_section_errors() {
540 let env = Env::new();
541 let content = "[unclosed_section\nkey = value";
542 let result = env.parse_ini_content(content, "<test>");
543 assert!(result.is_err());
544 match result {
545 Err(EnvError::Parse { line, .. }) => {
546 assert_eq!(line, 1);
547 }
548 _ => panic!("期望 Parse 错误"),
549 }
550 }
551
552 #[test]
554 fn test_parse_missing_equals_errors() {
555 let env = Env::new();
556 let content = "this_is_not_a_key_value_pair";
557 let result = env.parse_ini_content(content, "<test>");
558 assert!(result.is_err());
559 match result {
560 Err(EnvError::Parse { line, .. }) => {
561 assert_eq!(line, 1);
562 }
563 _ => panic!("期望 Parse 错误"),
564 }
565 }
566
567 #[test]
569 fn test_parse_empty_key_errors() {
570 let env = Env::new();
571 let content = " = value";
572 let result = env.parse_ini_content(content, "<test>");
573 assert!(result.is_err());
574 match result {
575 Err(EnvError::Parse { line, .. }) => {
576 assert_eq!(line, 1);
577 }
578 _ => panic!("期望 Parse 错误"),
579 }
580 }
581
582 #[test]
587 fn test_process_env_takes_priority() {
588 let _guard = ENV_TEST_LOCK.lock();
589 let env = Env::new();
590
591 env.set("SZ_RUST_TEST_ENV_PRIORITY", "internal_value");
593
594 std::env::set_var("SZ_RUST_TEST_ENV_PRIORITY", "process_value");
596
597 assert_eq!(
599 env.get("SZ_RUST_TEST_ENV_PRIORITY"),
600 Some("process_value".to_string())
601 );
602
603 std::env::remove_var("SZ_RUST_TEST_ENV_PRIORITY");
604 }
605
606 #[test]
610 fn test_empty_process_env_falls_back_to_internal() {
611 let _guard = ENV_TEST_LOCK.lock();
612 let env = Env::new();
613
614 env.set("SZ_RUST_TEST_EMPTY_FALLBACK", "internal_value");
616
617 std::env::set_var("SZ_RUST_TEST_EMPTY_FALLBACK", "");
619
620 assert_eq!(
622 env.get("SZ_RUST_TEST_EMPTY_FALLBACK"),
623 Some("internal_value".to_string())
624 );
625
626 std::env::remove_var("SZ_RUST_TEST_EMPTY_FALLBACK");
627 }
628
629 #[test]
631 fn test_clear() {
632 let env = Env::new();
633 env.set("KEY1", "value1");
634 env.set("KEY2", "value2");
635 assert_eq!(env.all().len(), 2);
636
637 env.clear();
638 assert!(env.all().is_empty());
639 }
640
641 #[test]
643 fn test_all_returns_snapshot() {
644 let env = Env::new();
645 env.set("KEY1", "value1");
646 env.set("KEY2", "value2");
647
648 let snapshot = env.all();
649 assert_eq!(snapshot.len(), 2);
650 assert_eq!(snapshot.get("KEY1"), Some(&"value1".to_string()));
651 assert_eq!(snapshot.get("KEY2"), Some(&"value2".to_string()));
652
653 env.set("KEY3", "value3");
655 assert_eq!(snapshot.len(), 2);
656 }
657
658 #[test]
660 fn test_remove_nonexistent_returns_false() {
661 let env = Env::new();
662 assert!(!env.remove("NON_EXISTENT"));
663 }
664
665 #[test]
670 fn test_section_isolation() {
671 let env = Env::new();
672 let content = r#"
673[section1]
674key = value1
675
676[section2]
677key = value2
678"#;
679 env.parse_ini_content(content, "<test>").unwrap();
680
681 assert_eq!(env.get("section1.key"), Some("value1".to_string()));
682 assert_eq!(env.get("section2.key"), Some("value2".to_string()));
683 }
684
685 #[test]
690 fn test_multiple_load_accumulates() {
691 let env = Env::new();
692 let content1 = "KEY1 = value1";
693 let content2 = "KEY2 = value2";
694
695 env.parse_ini_content(content1, "<test1>").unwrap();
696 env.parse_ini_content(content2, "<test2>").unwrap();
697
698 assert_eq!(env.get("KEY1"), Some("value1".to_string()));
699 assert_eq!(env.get("KEY2"), Some("value2".to_string()));
700 }
701
702 #[tokio::test]
713 async fn test_env_concurrent_read_write_consistency() {
714 let env = Env::default();
715 let num_writers = 8;
716 let num_reads = 100;
717
718 for i in 0..num_writers {
720 env.set(&format!("key{}", i), &format!("initial{}", i));
721 }
722
723 let env = std::sync::Arc::new(env);
724 let mut handles = Vec::new();
725
726 for w in 0..num_writers {
728 let env = std::sync::Arc::clone(&env);
729 handles.push(tokio::spawn(async move {
730 for r in 0..num_reads {
731 env.set(&format!("w{}_r{}", w, r), &format!("v{}_{}", w, r));
732 }
733 }));
734 }
735
736 for i in 0..num_writers {
738 let env = std::sync::Arc::clone(&env);
739 handles.push(tokio::spawn(async move {
740 for _ in 0..num_reads {
741 let _ = env.get(&format!("key{}", i));
742 }
743 }));
744 }
745
746 futures::future::join_all(handles).await;
747
748 for w in 0..num_writers {
750 for r in 0..num_reads {
751 let val = env.get(&format!("w{}_r{}", w, r));
752 assert_eq!(
753 val,
754 Some(format!("v{}_{}", w, r)),
755 "P3-CONC-01: 写者 {} 记录 r={} 应可见",
756 w,
757 r
758 );
759 }
760 }
761 }
762
763 #[tokio::test]
769 async fn test_env_no_deadlock_under_contention() {
770 let env = std::sync::Arc::new(Env::default());
771 let num_tasks = 32;
772 let ops_per_task = 200;
773
774 let result = tokio::time::timeout(std::time::Duration::from_secs(10), async {
775 let mut handles = Vec::new();
776 for t in 0..num_tasks {
777 let env = std::sync::Arc::clone(&env);
778 handles.push(tokio::spawn(async move {
779 for i in 0..ops_per_task {
780 let key = format!("k_{}_{}", t, i % 10);
781 if i % 3 == 0 {
782 env.set(&key, &format!("v_{}_{}", t, i));
783 } else {
784 let _ = env.get(&key);
785 }
786 }
787 }));
788 }
789 futures::future::join_all(handles).await;
790 })
791 .await;
792
793 assert!(
794 result.is_ok(),
795 "P3-CONC-02: Env 高并发操作超时(可能存在死锁)"
796 );
797 }
798
799 #[tokio::test]
805 async fn test_env_lock_never_held_across_await() {
806 let env = std::sync::Arc::new(Env::default());
807 let barrier = std::sync::Arc::new(tokio::sync::Barrier::new(4));
808 let mut handles = Vec::new();
809
810 for t in 0..4 {
811 let env = std::sync::Arc::clone(&env);
812 let barrier = std::sync::Arc::clone(&barrier);
813 handles.push(tokio::spawn(async move {
814 barrier.wait().await; for i in 0..50 {
816 let key = format!("concurrent_key_{}", i);
817 env.set(&key, &format!("task{}_val{}", t, i));
818 let _ = env.get(&key);
820 }
821 }));
822 }
823
824 let result = tokio::time::timeout(
825 std::time::Duration::from_secs(5),
826 futures::future::join_all(handles),
827 )
828 .await;
829
830 assert!(
831 result.is_ok(),
832 "P3-CONC-03: 任务饥饿(锁可能跨 await 持有)"
833 );
834 }
835
836 #[tokio::test]
841 async fn test_env_memory_bounded_under_load() {
842 let env = Env::default();
843 let num_keys = 10_000;
844
845 for i in 0..num_keys {
847 env.set(&format!("stress_key_{}", i), &"x".repeat(100));
848 }
849
850 assert_eq!(env.get("stress_key_0"), Some("x".repeat(100)));
852 assert_eq!(env.get("stress_key_9999"), Some("x".repeat(100)));
853
854 for i in 0..num_keys / 2 {
856 env.remove(&format!("stress_key_{}", i));
857 }
858
859 assert_eq!(env.get("stress_key_0"), None);
861 assert_eq!(env.get("stress_key_9999"), Some("x".repeat(100)));
862
863 for i in 0..num_keys / 2 {
865 env.set(&format!("stress_key_{}", i), &"y".repeat(50));
866 }
867 assert_eq!(env.get("stress_key_0"), Some("y".repeat(50)));
868 }
869}