everruns_core/config/
env.rs1use std::str::FromStr;
7use std::time::Duration;
8
9use super::ConfigError;
10
11pub fn env_or<T: FromStr>(var: &str, default: T) -> T {
16 std::env::var(var)
17 .ok()
18 .and_then(|v| v.parse().ok())
19 .unwrap_or(default)
20}
21
22pub fn env_string(var: &str, default: &str) -> String {
24 std::env::var(var).unwrap_or_else(|_| default.to_string())
25}
26
27pub fn env_string_any(vars: &[&str], default: &str) -> String {
29 vars.iter()
30 .find_map(|var| env_opt_string(var))
31 .unwrap_or_else(|| default.to_string())
32}
33
34pub fn env_bool(var: &str, default: bool) -> bool {
36 std::env::var(var)
37 .ok()
38 .map(|v| v == "true" || v == "1")
39 .unwrap_or(default)
40}
41
42pub fn env_duration_secs(var: &str, default: Duration) -> Duration {
44 std::env::var(var)
45 .ok()
46 .and_then(|v| v.parse::<u64>().ok())
47 .map(Duration::from_secs)
48 .unwrap_or(default)
49}
50
51pub fn env_duration_ms(var: &str, default: Duration) -> Duration {
53 std::env::var(var)
54 .ok()
55 .and_then(|v| v.parse::<u64>().ok())
56 .map(Duration::from_millis)
57 .unwrap_or(default)
58}
59
60pub fn env_opt<T: FromStr>(var: &str) -> Option<T> {
63 std::env::var(var)
64 .ok()
65 .filter(|s| !s.is_empty())
66 .and_then(|v| v.parse().ok())
67}
68
69pub fn env_required<T: FromStr>(var: &str) -> Result<T, ConfigError> {
72 let value = std::env::var(var).map_err(|_| ConfigError::Missing {
73 var: var.to_string(),
74 })?;
75 value.parse().map_err(|_| ConfigError::Invalid {
76 var: var.to_string(),
77 value: value.clone(),
78 reason: format!("could not parse as {}", std::any::type_name::<T>()),
79 })
80}
81
82pub fn env_list<T: FromStr>(var: &str) -> Vec<T> {
85 std::env::var(var)
86 .ok()
87 .filter(|s| !s.is_empty())
88 .map(|s| {
89 s.split(',')
90 .filter_map(|item| item.trim().parse().ok())
91 .collect()
92 })
93 .unwrap_or_default()
94}
95
96pub fn env_opt_string(var: &str) -> Option<String> {
98 std::env::var(var).ok().filter(|s| !s.is_empty())
99}
100
101pub fn env_opt_string_any(vars: &[&str]) -> Option<String> {
103 vars.iter().find_map(|var| env_opt_string(var))
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109 use std::env;
110
111 const PREFIX: &str = "EVERRUNS_CONFIG_TEST_";
113
114 fn set(suffix: &str, val: &str) -> String {
115 let key = format!("{PREFIX}{suffix}");
116 unsafe { env::set_var(&key, val) };
119 key
120 }
121
122 fn unset(suffix: &str) -> String {
123 let key = format!("{PREFIX}{suffix}");
124 unsafe { env::remove_var(&key) };
126 key
127 }
128
129 #[test]
130 fn env_or_present() {
131 let key = set("OR_PRESENT", "42");
132 assert_eq!(env_or::<u32>(&key, 0), 42);
133 }
134
135 #[test]
136 fn env_or_missing() {
137 let key = unset("OR_MISSING");
138 assert_eq!(env_or::<u32>(&key, 7), 7);
139 }
140
141 #[test]
142 fn env_or_unparseable() {
143 let key = set("OR_BAD", "not_a_number");
144 assert_eq!(env_or::<u32>(&key, 99), 99);
145 }
146
147 #[test]
148 fn env_string_present() {
149 let key = set("STR_P", "hello");
150 assert_eq!(env_string(&key, "default"), "hello");
151 }
152
153 #[test]
154 fn env_string_missing() {
155 let key = unset("STR_M");
156 assert_eq!(env_string(&key, "fallback"), "fallback");
157 }
158
159 #[test]
160 fn env_string_any_uses_first_non_empty_value() {
161 let first = unset("STR_ANY_FIRST");
162 let second = set("STR_ANY_SECOND", "");
163 let third = set("STR_ANY_THIRD", "value");
164
165 assert_eq!(
166 env_string_any(&[&first, &second, &third], "fallback"),
167 "value"
168 );
169 }
170
171 #[test]
172 fn env_string_any_falls_back_to_default() {
173 let first = unset("STR_ANY_MISSING_FIRST");
174 let second = set("STR_ANY_MISSING_SECOND", "");
175
176 assert_eq!(env_string_any(&[&first, &second], "fallback"), "fallback");
177 }
178
179 #[test]
180 fn env_bool_true_variants() {
181 let key = set("BOOL_T", "true");
182 assert!(env_bool(&key, false));
183 let key = set("BOOL_1", "1");
184 assert!(env_bool(&key, false));
185 }
186
187 #[test]
188 fn env_bool_false() {
189 let key = set("BOOL_F", "false");
190 assert!(!env_bool(&key, true));
191 }
192
193 #[test]
194 fn env_bool_missing() {
195 let key = unset("BOOL_M");
196 assert!(env_bool(&key, true));
197 }
198
199 #[test]
200 fn env_duration_secs_present() {
201 let key = set("DUR_S", "10");
202 assert_eq!(
203 env_duration_secs(&key, Duration::from_secs(1)),
204 Duration::from_secs(10)
205 );
206 }
207
208 #[test]
209 fn env_duration_secs_missing() {
210 let key = unset("DUR_S_M");
211 assert_eq!(
212 env_duration_secs(&key, Duration::from_secs(5)),
213 Duration::from_secs(5)
214 );
215 }
216
217 #[test]
218 fn env_duration_ms_present() {
219 let key = set("DUR_MS", "250");
220 assert_eq!(
221 env_duration_ms(&key, Duration::from_millis(100)),
222 Duration::from_millis(250)
223 );
224 }
225
226 #[test]
227 fn env_opt_present() {
228 let key = set("OPT_P", "42");
229 assert_eq!(env_opt::<u32>(&key), Some(42));
230 }
231
232 #[test]
233 fn env_opt_missing() {
234 let key = unset("OPT_M");
235 assert_eq!(env_opt::<u32>(&key), None::<u32>);
236 }
237
238 #[test]
239 fn env_opt_empty() {
240 let key = set("OPT_E", "");
241 assert_eq!(env_opt::<u32>(&key), None::<u32>);
242 }
243
244 #[test]
245 fn env_required_ok() {
246 let key = set("REQ_OK", "100");
247 assert_eq!(env_required::<u32>(&key).unwrap(), 100);
248 }
249
250 #[test]
251 fn env_required_missing() {
252 let key = unset("REQ_M");
253 let err = env_required::<u32>(&key).unwrap_err();
254 assert!(matches!(err, ConfigError::Missing { .. }));
255 }
256
257 #[test]
258 fn env_required_invalid() {
259 let key = set("REQ_BAD", "xyz");
260 let err = env_required::<u32>(&key).unwrap_err();
261 assert!(matches!(err, ConfigError::Invalid { .. }));
262 }
263
264 #[test]
265 fn env_list_present() {
266 let key = set("LIST_P", "1, 2, 3");
267 assert_eq!(env_list::<u32>(&key), vec![1, 2, 3]);
268 }
269
270 #[test]
271 fn env_list_missing() {
272 let key = unset("LIST_M");
273 assert_eq!(env_list::<u32>(&key), Vec::<u32>::new());
274 }
275
276 #[test]
277 fn env_list_with_bad_items() {
278 let key = set("LIST_B", "1,bad,3");
279 assert_eq!(env_list::<u32>(&key), vec![1, 3]);
280 }
281
282 #[test]
283 fn env_opt_string_present() {
284 let key = set("OSTR_P", "value");
285 assert_eq!(env_opt_string(&key), Some("value".to_string()));
286 }
287
288 #[test]
289 fn env_opt_string_empty() {
290 let key = set("OSTR_E", "");
291 assert_eq!(env_opt_string(&key), None);
292 }
293
294 #[test]
295 fn env_opt_string_missing() {
296 let key = unset("OSTR_M");
297 assert_eq!(env_opt_string(&key), None);
298 }
299
300 #[test]
301 fn env_opt_string_any_uses_first_non_empty_value() {
302 let first = unset("OSTR_ANY_FIRST");
303 let second = set("OSTR_ANY_SECOND", "");
304 let third = set("OSTR_ANY_THIRD", "value");
305
306 assert_eq!(
307 env_opt_string_any(&[&first, &second, &third]),
308 Some("value".to_string())
309 );
310 }
311}