mithril_client_cli/
configuration.rs

1use serde::Deserialize;
2use std::collections::HashMap;
3use thiserror::Error;
4
5use mithril_client::MithrilError;
6
7/// Configuration error
8#[derive(Debug, Error)]
9pub enum ConfigError {
10    /// Error raised when a required parameter is not present.
11    #[error("Parameter '{0}' is mandatory.")]
12    Required(String),
13
14    /// Error raised when a parameter cannot be converted to string.
15    #[error("Parameter '{0}' cannot be converted to string")]
16    Conversion(String, #[source] MithrilError),
17}
18
19/// Configuration parameters holder
20#[derive(Debug, Default, PartialEq, Deserialize)]
21#[serde(default)]
22pub struct ConfigParameters {
23    parameters: HashMap<String, String>,
24}
25
26impl ConfigParameters {
27    /// Constructor
28    pub fn new(parameters: HashMap<String, String>) -> Self {
29        Self { parameters }
30    }
31
32    /// Useful constructor for testing
33    #[cfg(test)]
34    pub fn build(parameters: &[(&str, &str)]) -> Self {
35        let parameters = parameters
36            .iter()
37            .map(|(k, v)| (k.to_string(), v.to_string()))
38            .collect();
39
40        Self::new(parameters)
41    }
42
43    /// Add or replace a parameter in the holder
44    #[cfg(test)]
45    pub fn add_parameter(&mut self, name: &str, value: &str) -> &mut Self {
46        let _ = self.parameters.insert(name.to_string(), value.to_string());
47
48        self
49    }
50
51    /// Fill the holder with parameters from a source
52    pub fn add_source(mut self, source: &impl ConfigSource) -> Result<Self, ConfigError> {
53        let extra = source.collect()?;
54        self.parameters.extend(extra);
55
56        Ok(self)
57    }
58
59    /// Fetch a parameter from the holder.
60    pub fn get(&self, name: &str) -> Option<String> {
61        self.parameters.get(name).cloned()
62    }
63
64    /// Fetch a parameter from the holder. If the parameter is not set, the
65    /// given default value is returned instead.
66    pub fn get_or(&self, name: &str, default: &str) -> String {
67        self.get(name).unwrap_or(default.to_string())
68    }
69
70    /// Fetch a parameter from the holder. If the parameter is not set, an error
71    /// is raised.
72    pub fn require(&self, name: &str) -> Result<String, ConfigError> {
73        self.get(name)
74            .ok_or_else(|| ConfigError::Required(name.to_string()))
75    }
76}
77
78/// Describes a generic source of configuration parameters
79pub trait ConfigSource {
80    /// Collect all the configuration parameters from the source
81    fn collect(&self) -> Result<HashMap<String, String>, ConfigError>;
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    struct TestSource {
89        params: HashMap<String, String>,
90    }
91
92    impl<const N: usize> From<[(&str, &str); N]> for TestSource {
93        fn from(arr: [(&str, &str); N]) -> Self {
94            TestSource {
95                params: arr
96                    .into_iter()
97                    .map(|(k, v)| (k.to_string(), v.to_string()))
98                    .collect(),
99            }
100        }
101    }
102
103    impl ConfigSource for TestSource {
104        fn collect(&self) -> Result<HashMap<String, String>, ConfigError> {
105            Ok(self.params.clone())
106        }
107    }
108
109    #[test]
110    fn test_config_constructor() {
111        let config = ConfigParameters::build(&[("pika", "chu")]);
112
113        assert_eq!(
114            ConfigParameters {
115                parameters: [("pika".to_string(), "chu".to_string())]
116                    .into_iter()
117                    .collect()
118            },
119            config
120        );
121    }
122    #[test]
123    fn test_config_set() {
124        let mut config = ConfigParameters::default();
125        config.add_parameter("pika", "chu");
126
127        assert_eq!(
128            ConfigParameters {
129                parameters: [("pika".to_string(), "chu".to_string())]
130                    .into_iter()
131                    .collect()
132            },
133            config
134        );
135    }
136
137    #[test]
138    fn test_config_get() {
139        let mut config = ConfigParameters::default();
140        config.add_parameter("pika", "chu");
141
142        assert_eq!("chu".to_string(), config.get("pika").unwrap());
143        assert!(config.get("whatever").is_none());
144    }
145
146    #[test]
147    fn test_config_default() {
148        let mut config = ConfigParameters::default();
149        config.add_parameter("pika", "chu");
150
151        assert_eq!("chu".to_string(), config.get("pika").unwrap());
152        assert_eq!("default".to_string(), config.get_or("whatever", "default"));
153    }
154
155    #[test]
156    fn test_config_require() {
157        let mut config = ConfigParameters::default();
158        config.add_parameter("pika", "chu");
159
160        assert_eq!("chu".to_string(), config.require("pika").unwrap());
161        config.require("whatever").unwrap_err();
162    }
163
164    #[test]
165    fn test_add_source_to_config() {
166        let config = ConfigParameters::build(&[("pika", "chu"), ("chari", "zard")])
167            .add_source(&TestSource::from([("jiggly", "puff")]))
168            .unwrap();
169
170        assert_eq!(
171            ConfigParameters {
172                parameters: HashMap::from([
173                    ("pika".to_string(), "chu".to_string()),
174                    ("chari".to_string(), "zard".to_string()),
175                    ("jiggly".to_string(), "puff".to_string())
176                ])
177            },
178            config
179        );
180    }
181
182    #[test]
183    fn test_add_source_replace_existing_value() {
184        let config = ConfigParameters::build(&[("pika", "pika")])
185            .add_source(&TestSource::from([("pika", "not chu")]))
186            .unwrap();
187
188        assert_eq!(
189            ConfigParameters {
190                parameters: HashMap::from([("pika".to_string(), "not chu".to_string()),])
191            },
192            config
193        );
194    }
195}