1use std::{env::var, str::FromStr};
2
3use serde::{Deserialize, Serialize};
4
5#[derive(thiserror::Error, Debug)]
7pub enum ExpliconError {
8 #[error("Error while resolving env var: {0}")]
10 Var(#[from] std::env::VarError),
11 #[error("{0}")]
13 Other(String),
14}
15
16pub type Result<T> = std::result::Result<T, ExpliconError>;
18
19#[derive(Debug, Clone, Deserialize, Serialize)]
25#[serde(rename_all = "snake_case")]
26pub enum Sourced<T> {
27 Env(String),
29
30 #[serde(untagged)]
32 Value(T),
33}
34
35
36impl<T> Sourced<T>
37where
38 T: FromStr,
39 T: Clone,
40 <T as FromStr>::Err: ToString,
41{
42 pub fn resolve(&self) -> Result<T> {
54 match self {
55 Self::Value(value) => Ok(value.clone()),
56 Self::Env(var_name) => {
57 let var_value = var(var_name)?; let value = var_value
60 .parse::<T>()
61 .map_err(|e| ExpliconError::Other(e.to_string()))?;
62 Ok(value)
63 }
64 }
65 }
66
67 pub fn resolve_or_default(&self) -> Result<T>
69 where
70 T: Default,
71 {
72 self.resolve().or_else(|_| Ok(T::default()))
73 }
74
75 pub fn resolve_or(&self, fallback: T) -> T {
77 self.resolve().unwrap_or(fallback)
78 }
79
80 pub fn resolve_and_validate<F>(&self, validator: F) -> Result<T>
82 where
83 F: FnOnce(&T) -> bool,
84 {
85 let value = self.resolve()?;
86 if validator(&value) {
87 Ok(value)
88 } else {
89 Err(ExpliconError::Other("Validation failed".into()))
90 }
91 }
92}
93
94impl<T> Sourced<T>
95where
96 T: From<String>,
97 T: Clone,
98{
99 pub fn resolve_from_string(&self) -> Result<T> {
111 match self {
112 Self::Value(value) => Ok(value.clone()),
113 Self::Env(var_name) => {
114 let var_value = var(var_name)?; Ok(T::from(var_value))
117 }
118 }
119 }
120
121 pub fn resolve_from_string_or(&self, fallback: T) -> T {
124 self.resolve_from_string().unwrap_or(fallback)
125 }
126
127 pub fn resolve_from_string_and_validate<F>(&self, validator: F) -> Result<T>
129 where
130 F: FnOnce(&T) -> bool,
131 {
132 let value = self.resolve_from_string()?;
133 if validator(&value) {
134 Ok(value)
135 } else {
136 Err(ExpliconError::Other("Validation failed".into()))
137 }
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn resolve_value() {
147 let sourced = Sourced::Value(42);
148 assert_eq!(sourced.resolve().unwrap(), 42);
149 }
150
151 #[test]
152 fn resolve_env_success() {
153 let var_name = "TEST_RESOLVE_ENV_SUCCESS";
154 let expected_value = 123;
155 unsafe { std::env::set_var(var_name, expected_value.to_string()) };
156 let sourced = Sourced::<i32>::Env(var_name.to_string());
157 let result = sourced.resolve().unwrap();
158 assert_eq!(result, expected_value);
159 unsafe { std::env::remove_var(var_name) };
160 }
161
162 #[test]
163 fn resolve_env_var_not_found() {
164 let var_name = "NON_EXISTENT_VAR_XYZ123";
165 unsafe { std::env::remove_var(var_name) };
166 let sourced = Sourced::<i32>::Env(var_name.to_string());
167 let result = sourced.resolve();
168 assert!(matches!(result, Err(ExpliconError::Var(_))));
169 }
170
171 #[test]
172 fn resolve_env_var_invalid_parse() {
173 let var_name = "TEST_INVALID_PARSE";
174 unsafe { std::env::set_var(var_name, "abc") };
175 let sourced = Sourced::<i32>::Env(var_name.to_string());
176 let result = sourced.resolve();
177 assert!(matches!(result, Err(ExpliconError::Other(_))));
178 unsafe { std::env::remove_var(var_name) };
179 }
180
181 #[test]
182 fn resolve_or_default_env_missing() {
183 let var_name = "NON_EXISTENT_VAR_FOR_DEFAULT";
184 unsafe { std::env::remove_var(var_name) };
185 let sourced = Sourced::<i32>::Env(var_name.to_string());
186 let result = sourced.resolve_or_default().unwrap();
187 assert_eq!(result, i32::default());
188 }
189
190 #[test]
191 fn resolve_or_default_parse_error() {
192 let var_name = "TEST_PARSE_ERROR_DEFAULT";
193 unsafe { std::env::set_var(var_name, "abc") };
194 let sourced = Sourced::<i32>::Env(var_name.to_string());
195 let result = sourced.resolve_or_default().unwrap();
196 assert_eq!(result, i32::default());
197 unsafe { std::env::remove_var(var_name) };
198 }
199
200 #[test]
201 fn resolve_or_default_success() {
202 let var_name = "TEST_RESOLVE_OR_DEFAULT_SUCCESS";
203 unsafe { std::env::set_var(var_name, "5") };
204 let sourced = Sourced::<i32>::Env(var_name.to_string());
205 let result = sourced.resolve_or_default().unwrap();
206 assert_eq!(result, 5);
207 unsafe { std::env::remove_var(var_name) };
208 }
209
210 #[test]
211 fn resolve_and_validate_success() {
212 let sourced = Sourced::Value(5);
213 let result = sourced.resolve_and_validate(|v| *v == 5).unwrap();
214 assert_eq!(result, 5);
215 }
216
217 #[test]
218 fn resolve_and_validate_failure() {
219 let sourced = Sourced::Value(5);
220 let result = sourced.resolve_and_validate(|v| *v == 10);
221 assert!(matches!(result, Err(ExpliconError::Other(_))));
222 }
223
224 #[test]
225 fn resolve_and_validate_env_missing() {
226 let var_name = "NON_EXISTENT_VAR_FOR_VALIDATE";
227 unsafe { std::env::remove_var(var_name) };
228 let sourced = Sourced::<i32>::Env(var_name.to_string());
229 let result = sourced.resolve_and_validate(|_| true);
230 assert!(matches!(result, Err(ExpliconError::Var(_))));
231 }
232
233 #[test]
234 fn resolve_and_validate_env_invalid() {
235 let var_name = "TEST_VALIDATE_ENV_INVALID";
236 unsafe { std::env::set_var(var_name, "10") };
237 let sourced = Sourced::<i32>::Env(var_name.to_string());
238 let result = sourced.resolve_and_validate(|v| *v == 5);
239 assert!(matches!(result, Err(ExpliconError::Other(_))));
240 unsafe { std::env::remove_var(var_name) };
241 }
242
243 #[test]
244 fn resolve_env_string() {
245 let var_name = "TEST_ENV_STRING";
246 let expected = "hello";
247 unsafe { std::env::set_var(var_name, expected) };
248 let sourced = Sourced::<String>::Env(var_name.to_string());
249 let result = sourced.resolve().unwrap();
250 assert_eq!(result, expected);
251 unsafe { std::env::remove_var(var_name) };
252 }
253
254 #[test]
255 fn resolve_env_bool() {
256 let var_name = "TEST_ENV_BOOL";
257 unsafe { std::env::set_var(var_name, "true") };
258 let sourced = Sourced::<bool>::Env(var_name.to_string());
259 let result = sourced.resolve().unwrap();
260 assert!(result);
261 unsafe { std::env::remove_var(var_name) };
262 }
263}