1use std::{borrow::Borrow, collections::HashMap, hash::Hash};
5
6#[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 pub fn new(base: ConfigValue, overrides: HashMap<Key, ConfigValue>) -> Self {
16 Self { base, overrides }
17 }
18
19 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 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}