okane_core/
utility.rs

1//! Module defining misc utilities.
2//! Eventually each can be individually published as a crate.
3
4use std::{borrow::Borrow, collections::HashMap, hash::Hash};
5
6/// Structured configuration holding overrides with default values.
7#[derive(Debug, Default)]
8pub struct ConfigResolver<Key, ConfigValue> {
9    base: ConfigValue,
10    overrides: HashMap<Key, ConfigValue>,
11}
12
13impl<Key, ConfigValue> ConfigResolver<Key, ConfigValue> {
14    /// Create a new instance of [`Config`].
15    pub fn new(base: ConfigValue, overrides: HashMap<Key, ConfigValue>) -> Self {
16        Self { base, overrides }
17    }
18
19    /// Returns an always-existing field for the given `key`.
20    /// Note field is `FnOnce` so it can be arbitrary function as long as it makes sense.
21    pub fn get<Q, F, Out>(&self, key: &Q, field: F) -> Out
22    where
23        Key: Borrow<Q> + Eq + Hash,
24        Q: Hash + Eq + ?Sized,
25        F: FnOnce(&ConfigValue) -> Out,
26    {
27        match self.overrides.get(key) {
28            None => field(&self.base),
29            Some(set) => field(set),
30        }
31    }
32
33    /// Returns an optional field for the given `key`.
34    /// Note field as `FnMut` returning any type of [`Option`].
35    pub fn get_opt<Q, F, Out>(&self, key: &Q, mut field: F) -> Option<Out>
36    where
37        Key: Borrow<Q> + Eq + Hash,
38        Q: Hash + Eq + ?Sized,
39        F: FnMut(&ConfigValue) -> Option<Out>,
40    {
41        match self.overrides.get(key) {
42            None => field(&self.base),
43            Some(set) => field(set).or_else(|| field(&self.base)),
44        }
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    use maplit::hashmap;
53    use pretty_assertions::assert_eq;
54
55    #[derive(Debug)]
56    struct TestStruct {
57        int_field: i32,
58        str_field: &'static str,
59    }
60
61    impl TestStruct {
62        fn str_opt(&self) -> Option<&'static str> {
63            if self.str_field.is_empty() {
64                None
65            } else {
66                Some(self.str_field)
67            }
68        }
69    }
70
71    fn input() -> ConfigResolver<&'static str, TestStruct> {
72        ConfigResolver::new(
73            TestStruct {
74                int_field: 1,
75                str_field: "base",
76            },
77            hashmap! {
78                "key1" => TestStruct {
79                    int_field: 0,
80                    str_field: "",
81                },
82                "key2" => TestStruct {
83                    int_field: 2,
84                    str_field: "non-empty",
85                },
86            },
87        )
88    }
89
90    #[test]
91    fn get_returns_base_value_if_no_overrides() {
92        assert_eq!(1, input().get("not_existing", |x| x.int_field));
93    }
94
95    #[test]
96    fn get_returns_override_value_if_key_exists() {
97        assert_eq!(2, input().get("key2", |x| x.int_field));
98    }
99
100    #[test]
101    fn get_opt_returns_base_value_if_no_overrides() {
102        assert_eq!(
103            Some("base"),
104            input().get_opt("not_existing", TestStruct::str_opt)
105        );
106    }
107
108    #[test]
109    fn get_opt_returns_base_value_if_overrides_returns_none() {
110        assert_eq!(Some("base"), input().get_opt("key1", TestStruct::str_opt));
111    }
112
113    #[test]
114    fn get_opt_returns_override_value() {
115        assert_eq!(
116            Some("non-empty"),
117            input().get_opt("key2", TestStruct::str_opt)
118        );
119    }
120}