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<(), ConfigError> {
53        let extra = source.collect()?;
54        self.parameters.extend(extra);
55
56        Ok(())
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).ok_or_else(|| ConfigError::Required(name.to_string()))
74    }
75}
76
77/// Describes a generic source of configuration parameters
78pub trait ConfigSource {
79    /// Collect all the configuration parameters from the source
80    fn collect(&self) -> Result<HashMap<String, String>, ConfigError>;
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86
87    struct TestSource {
88        params: HashMap<String, String>,
89    }
90
91    impl<const N: usize> From<[(&str, &str); N]> for TestSource {
92        fn from(arr: [(&str, &str); N]) -> Self {
93            TestSource {
94                params: arr.into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect(),
95            }
96        }
97    }
98
99    impl ConfigSource for TestSource {
100        fn collect(&self) -> Result<HashMap<String, String>, ConfigError> {
101            Ok(self.params.clone())
102        }
103    }
104
105    #[test]
106    fn test_config_constructor() {
107        let config = ConfigParameters::build(&[("pika", "chu")]);
108
109        assert_eq!(
110            ConfigParameters {
111                parameters: [("pika".to_string(), "chu".to_string())].into_iter().collect()
112            },
113            config
114        );
115    }
116    #[test]
117    fn test_config_set() {
118        let mut config = ConfigParameters::default();
119        config.add_parameter("pika", "chu");
120
121        assert_eq!(
122            ConfigParameters {
123                parameters: [("pika".to_string(), "chu".to_string())].into_iter().collect()
124            },
125            config
126        );
127    }
128
129    #[test]
130    fn test_config_get() {
131        let mut config = ConfigParameters::default();
132        config.add_parameter("pika", "chu");
133
134        assert_eq!("chu".to_string(), config.get("pika").unwrap());
135        assert!(config.get("whatever").is_none());
136    }
137
138    #[test]
139    fn test_config_default() {
140        let mut config = ConfigParameters::default();
141        config.add_parameter("pika", "chu");
142
143        assert_eq!("chu".to_string(), config.get("pika").unwrap());
144        assert_eq!("default".to_string(), config.get_or("whatever", "default"));
145    }
146
147    #[test]
148    fn test_config_require() {
149        let mut config = ConfigParameters::default();
150        config.add_parameter("pika", "chu");
151
152        assert_eq!("chu".to_string(), config.require("pika").unwrap());
153        config.require("whatever").unwrap_err();
154    }
155
156    #[test]
157    fn test_add_source_to_config() {
158        let mut config = ConfigParameters::build(&[("pika", "chu"), ("chari", "zard")]);
159        config.add_source(&TestSource::from([("jiggly", "puff")])).unwrap();
160
161        assert_eq!(
162            ConfigParameters {
163                parameters: HashMap::from([
164                    ("pika".to_string(), "chu".to_string()),
165                    ("chari".to_string(), "zard".to_string()),
166                    ("jiggly".to_string(), "puff".to_string())
167                ])
168            },
169            config
170        );
171    }
172
173    #[test]
174    fn test_add_source_replace_existing_value() {
175        let mut config = ConfigParameters::build(&[("pika", "pika")]);
176        config.add_source(&TestSource::from([("pika", "not chu")])).unwrap();
177
178        assert_eq!(
179            ConfigParameters {
180                parameters: HashMap::from([("pika".to_string(), "not chu".to_string()),])
181            },
182            config
183        );
184    }
185}