1use std::fmt;
2use std::ops::Deref;
3use std::str::FromStr;
4
5use tracing::{event, info, warn};
6
7use super::configuration_utils::{INFORMATION_LOG_LEVEL, ParsableConfigValue};
8
9#[derive(Clone)]
25pub struct ConfigEnum {
26 value: String,
27 valid_values: &'static [&'static str],
28}
29
30impl ConfigEnum {
31 pub fn new(default: &str, valid_values: &'static [&'static str]) -> Self {
32 let lower = default.to_lowercase();
33 debug_assert!(
34 valid_values.iter().any(|v| v.to_lowercase() == lower),
35 "Default value \"{default}\" is not in the valid values list: {valid_values:?}"
36 );
37 ConfigEnum {
38 value: lower,
39 valid_values,
40 }
41 }
42
43 pub fn new_unchecked(value: impl Into<String>) -> Self {
49 ConfigEnum {
50 value: value.into().to_lowercase(),
51 valid_values: &[],
52 }
53 }
54
55 pub fn as_str(&self) -> &str {
56 &self.value
57 }
58
59 pub fn valid_values(&self) -> &'static [&'static str] {
60 self.valid_values
61 }
62
63 pub fn try_set(&mut self, value: &str) -> Result<(), String> {
65 let lower = value.to_lowercase();
66 if self.valid_values.iter().any(|v| v.to_lowercase() == lower) {
67 self.value = lower;
68 Ok(())
69 } else {
70 Err(format!("\"{value}\" is not a valid value. Valid values are: {:?}", self.valid_values))
71 }
72 }
73
74 pub fn parse<T>(&self) -> Result<T, T::Err>
81 where
82 T: FromStr,
83 T::Err: fmt::Debug + fmt::Display,
84 {
85 #[cfg(debug_assertions)]
86 for v in self.valid_values {
87 if let Err(e) = v.parse::<T>() {
88 panic!("ConfigEnum valid value \"{v}\" cannot be parsed into {}: {e}", std::any::type_name::<T>());
89 }
90 }
91 self.value.parse::<T>()
92 }
93}
94
95impl fmt::Debug for ConfigEnum {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 write!(f, "{:?}", self.value)
98 }
99}
100
101impl fmt::Display for ConfigEnum {
102 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103 write!(f, "{}", self.value)
104 }
105}
106
107impl Deref for ConfigEnum {
108 type Target = str;
109 fn deref(&self) -> &Self::Target {
110 &self.value
111 }
112}
113
114impl AsRef<str> for ConfigEnum {
115 fn as_ref(&self) -> &str {
116 &self.value
117 }
118}
119
120impl PartialEq<str> for ConfigEnum {
121 fn eq(&self, other: &str) -> bool {
122 self.value == other.to_lowercase()
123 }
124}
125
126impl PartialEq<&str> for ConfigEnum {
127 fn eq(&self, other: &&str) -> bool {
128 self.value == other.to_lowercase()
129 }
130}
131
132impl PartialEq for ConfigEnum {
133 fn eq(&self, other: &Self) -> bool {
134 self.value == other.value
135 }
136}
137
138impl Eq for ConfigEnum {}
139
140impl ParsableConfigValue for ConfigEnum {
141 fn parse_user_value(_value: &str) -> Option<Self> {
142 None
143 }
144
145 fn to_config_string(&self) -> String {
146 self.value.clone()
147 }
148
149 fn try_update_in_place(&mut self, value: &str) -> bool {
150 self.try_set(value).is_ok()
151 }
152
153 fn parse_config_value(variable_name: &str, value: Option<String>, default: Self) -> Self {
154 match value {
155 Some(v) => {
156 let lower = v.to_lowercase();
157 if default.valid_values.iter().any(|valid| valid.to_lowercase() == lower) {
158 info!("Config: {variable_name} = {lower:?} (user set)");
159 ConfigEnum {
160 value: lower,
161 valid_values: default.valid_values,
162 }
163 } else {
164 warn!(
165 "Configuration value \"{v}\" for {variable_name} is not valid. \
166 Valid values are: {:?}. Reverting to default \"{}\".",
167 default.valid_values, default.value
168 );
169 info!("Config: {variable_name} = {:?} (default due to invalid value)", default.value);
170 default
171 }
172 },
173 None => {
174 event!(INFORMATION_LOG_LEVEL, "Config: {variable_name} = {:?} (default)", default.value);
175 default
176 },
177 }
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use std::num::ParseIntError;
184
185 use super::*;
186
187 const VALID: &[&str] = &["", "auto", "none", "lz4", "bg4-lz4"];
188 const INT_VALID: &[&str] = &["1", "2", "3"];
189
190 #[test]
191 fn test_new_default_value() {
192 let ce = ConfigEnum::new("auto", VALID);
193 assert_eq!(ce.as_str(), "auto");
194 }
195
196 #[test]
197 fn test_new_normalizes_to_lowercase() {
198 let ce = ConfigEnum::new("AUTO", VALID);
199 assert_eq!(ce.as_str(), "auto");
200 }
201
202 #[test]
203 fn test_deref_and_asref() {
204 let ce = ConfigEnum::new("lz4", VALID);
205 let s: &str = &ce;
206 assert_eq!(s, "lz4");
207 assert_eq!(ce.as_ref(), "lz4");
208 }
209
210 #[test]
211 fn test_partial_eq_str() {
212 let ce = ConfigEnum::new("lz4", VALID);
213 assert_eq!(ce, "lz4");
214 assert_eq!(ce, "LZ4");
215 assert_ne!(ce, "auto");
216 }
217
218 #[test]
219 fn test_partial_eq_str_ref() {
220 let ce = ConfigEnum::new("lz4", VALID);
221 assert_eq!(ce, "lz4");
222 }
223
224 #[test]
225 fn test_partial_eq_self() {
226 let a = ConfigEnum::new("lz4", VALID);
227 let b = ConfigEnum::new("lz4", VALID);
228 assert_eq!(a, b);
229 }
230
231 #[test]
232 fn test_display() {
233 let ce = ConfigEnum::new("bg4-lz4", VALID);
234 assert_eq!(format!("{ce}"), "bg4-lz4");
235 }
236
237 #[test]
238 fn test_debug() {
239 let ce = ConfigEnum::new("bg4-lz4", VALID);
240 assert_eq!(format!("{ce:?}"), "\"bg4-lz4\"");
241 }
242
243 #[test]
244 fn test_parse_valid_value() {
245 let default = ConfigEnum::new("auto", VALID);
246 let result = ConfigEnum::parse_config_value("test", Some("LZ4".to_string()), default);
247 assert_eq!(result.as_str(), "lz4");
248 }
249
250 #[test]
251 fn test_parse_invalid_value_returns_default() {
252 let default = ConfigEnum::new("auto", VALID);
253 let result = ConfigEnum::parse_config_value("test", Some("zstd".to_string()), default);
254 assert_eq!(result.as_str(), "auto");
255 }
256
257 #[test]
258 fn test_parse_none_returns_default() {
259 let default = ConfigEnum::new("auto", VALID);
260 let result = ConfigEnum::parse_config_value("test", None, default);
261 assert_eq!(result.as_str(), "auto");
262 }
263
264 #[test]
265 fn test_parse_empty_string_valid() {
266 let default = ConfigEnum::new("auto", VALID);
267 let result = ConfigEnum::parse_config_value("test", Some("".to_string()), default);
268 assert_eq!(result.as_str(), "");
269 }
270
271 #[test]
272 #[should_panic(expected = "not in the valid values list")]
273 #[cfg(debug_assertions)]
274 fn test_new_invalid_default_panics() {
275 let _ = ConfigEnum::new("invalid", VALID);
276 }
277
278 #[test]
279 fn test_valid_values_accessor() {
280 let ce = ConfigEnum::new("auto", VALID);
281 assert_eq!(ce.valid_values(), VALID);
282 }
283
284 #[test]
285 fn test_try_set_valid() {
286 let mut ce = ConfigEnum::new("auto", VALID);
287 assert!(ce.try_set("lz4").is_ok());
288 assert_eq!(ce.as_str(), "lz4");
289 }
290
291 #[test]
292 fn test_try_set_case_insensitive() {
293 let mut ce = ConfigEnum::new("auto", VALID);
294 assert!(ce.try_set("LZ4").is_ok());
295 assert_eq!(ce.as_str(), "lz4");
296 }
297
298 #[test]
299 fn test_try_set_invalid() {
300 let mut ce = ConfigEnum::new("auto", VALID);
301 assert!(ce.try_set("zstd").is_err());
302 assert_eq!(ce.as_str(), "auto");
303 }
304
305 #[test]
306 fn test_try_set_empty_string() {
307 let mut ce = ConfigEnum::new("auto", VALID);
308 assert!(ce.try_set("").is_ok());
309 assert_eq!(ce.as_str(), "");
310 }
311
312 #[test]
313 fn test_parse_success() {
314 let ce = ConfigEnum::new("2", INT_VALID);
315 let val: Result<u32, ParseIntError> = ce.parse();
316 assert_eq!(val.unwrap(), 2);
317 }
318
319 #[test]
320 fn test_parse_all_values_parseable() {
321 let ce = ConfigEnum::new("1", INT_VALID);
322 let _: u32 = ce.parse().unwrap();
323 }
324
325 #[test]
326 #[should_panic(expected = "cannot be parsed")]
327 #[cfg(debug_assertions)]
328 fn test_parse_panics_on_unparseable_valid_value() {
329 let ce = ConfigEnum::new("auto", VALID);
330 let _: Result<u32, ParseIntError> = ce.parse();
331 }
332}