1use std::str::FromStr;
8use std::sync::OnceLock;
9
10pub fn env_parse_or<T: FromStr>(name: &str, default: T) -> T {
15 std::env::var(name)
16 .ok()
17 .and_then(|value| value.parse().ok())
18 .unwrap_or(default)
19}
20
21pub fn env_flag(name: &str, default: bool) -> bool {
26 std::env::var(name)
27 .ok()
28 .map(|value| match value.trim().to_ascii_lowercase().as_str() {
29 "1" | "true" | "yes" | "on" => true,
30 "0" | "false" | "no" | "off" => false,
31 _ => default,
32 })
33 .unwrap_or(default)
34}
35
36pub fn cached_env_flag(name: &str, default: bool, cache: &'static OnceLock<bool>) -> bool {
42 *cache.get_or_init(|| env_flag(name, default))
43}
44
45#[cfg(test)]
46mod tests {
47 use super::{cached_env_flag, env_flag, env_parse_or};
48 use crate::test_process::run_in_child;
49 use std::sync::OnceLock;
50
51 const KEY: &str = "KHIVE_DB_ENV_HELPERS_TEST_VALUE";
52
53 #[test]
54 fn parsed_defaults_preserve_exact_input_and_read_each_time() {
55 if run_in_child(|command| {
56 command.env_remove(KEY);
57 }) {
58 return;
59 }
60 assert_eq!(env_parse_or(KEY, 17_u64), 17);
61 crate::test_process::set_var(KEY, "42");
62 assert_eq!(env_parse_or(KEY, 17_u64), 42);
63 crate::test_process::set_var(KEY, "-7");
64 assert_eq!(env_parse_or(KEY, 17_i64), -7);
65 assert_eq!(env_parse_or(KEY, 17_u64), 17);
66 for value in ["", " 42 ", "invalid", "18446744073709551616"] {
67 crate::test_process::set_var(KEY, value);
68 assert_eq!(env_parse_or(KEY, 17_u64), 17, "{value:?}");
69 }
70 crate::test_process::set_var(KEY, "256");
71 assert_eq!(env_parse_or(KEY, 9_u8), 9);
72 crate::test_process::set_var(KEY, "NaN");
73 assert!(env_parse_or(KEY, 1.0_f64).is_nan());
74 crate::test_process::set_var(KEY, "41");
75 assert_eq!(env_parse_or(KEY, 17_u64), 41);
76 }
77
78 #[test]
79 fn flags_keep_explicit_false_distinct_from_fallback() {
80 if run_in_child(|command| {
81 command.env_remove(KEY);
82 }) {
83 return;
84 }
85 assert!(!env_flag(KEY, false));
86 assert!(env_flag(KEY, true));
87 for value in ["1", "true", "yes", "on", " TrUe ", "\tYES\n"] {
88 crate::test_process::set_var(KEY, value);
89 assert!(env_flag(KEY, false), "{value:?}");
90 }
91 for value in ["0", "false", "no", "off", " FaLsE ", "\tOFF\n"] {
92 crate::test_process::set_var(KEY, value);
93 assert!(!env_flag(KEY, true), "{value:?}");
94 }
95 for value in ["", " ", "enabled", "2"] {
96 crate::test_process::set_var(KEY, value);
97 assert!(!env_flag(KEY, false), "{value:?}");
98 assert!(env_flag(KEY, true), "{value:?}");
99 }
100 }
101
102 #[test]
103 fn cached_flags_keep_the_first_value_and_separate_cells() {
104 static FIRST: OnceLock<bool> = OnceLock::new();
105 static SECOND: OnceLock<bool> = OnceLock::new();
106 if run_in_child(|command| {
107 command.env_remove(KEY);
108 }) {
109 return;
110 }
111 assert!(cached_env_flag(KEY, true, &FIRST));
112 crate::test_process::set_var(KEY, "false");
113 assert!(!env_flag(KEY, true));
114 assert!(cached_env_flag(KEY, false, &FIRST));
115 assert!(!cached_env_flag(KEY, true, &SECOND));
116 crate::test_process::set_var(KEY, "true");
117 assert!(env_flag(KEY, false));
118 assert!(!cached_env_flag(KEY, false, &SECOND));
119 crate::test_process::remove_var(KEY);
120 assert!(!cached_env_flag(KEY, true, &SECOND));
121 }
122
123 #[cfg(unix)]
124 #[test]
125 fn non_unicode_values_use_the_supplied_defaults() {
126 use std::ffi::OsString;
127 use std::os::unix::ffi::OsStringExt;
128 static CACHE: OnceLock<bool> = OnceLock::new();
129 if run_in_child(|command| {
130 command.env_remove(KEY);
131 }) {
132 return;
133 }
134 crate::test_process::set_var(KEY, OsString::from_vec(vec![0xff]));
135 assert_eq!(env_parse_or(KEY, 23_u32), 23);
136 assert!(!env_flag(KEY, false));
137 assert!(env_flag(KEY, true));
138 assert!(cached_env_flag(KEY, true, &CACHE));
139 }
140}