Skip to main content

use_config_value/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::collections::BTreeMap;
5
6/// A primitive deterministic configuration value.
7#[derive(Clone, Debug, PartialEq)]
8pub enum ConfigValue {
9    /// No configured value.
10    Null,
11    /// Boolean value.
12    Bool(bool),
13    /// Signed integer value.
14    Integer(i64),
15    /// Floating-point value.
16    Float(f64),
17    /// Owned string value.
18    String(String),
19    /// Ordered list of configuration values.
20    List(Vec<Self>),
21    /// Deterministically ordered string-keyed map.
22    Map(BTreeMap<String, Self>),
23}
24
25impl ConfigValue {
26    /// Returns the boolean value, if this is a boolean.
27    #[must_use]
28    pub const fn as_bool(&self) -> Option<bool> {
29        match self {
30            Self::Bool(value) => Some(*value),
31            _ => None,
32        }
33    }
34
35    /// Returns the integer value, if this is an integer.
36    #[must_use]
37    pub const fn as_i64(&self) -> Option<i64> {
38        match self {
39            Self::Integer(value) => Some(*value),
40            _ => None,
41        }
42    }
43
44    /// Returns the floating-point value, if this is a float.
45    #[must_use]
46    pub const fn as_f64(&self) -> Option<f64> {
47        match self {
48            Self::Float(value) => Some(*value),
49            _ => None,
50        }
51    }
52
53    /// Returns the string value, if this is a string.
54    #[must_use]
55    pub fn as_str(&self) -> Option<&str> {
56        match self {
57            Self::String(value) => Some(value),
58            _ => None,
59        }
60    }
61
62    /// Returns the list value, if this is a list.
63    #[must_use]
64    pub fn as_list(&self) -> Option<&[Self]> {
65        match self {
66            Self::List(value) => Some(value),
67            _ => None,
68        }
69    }
70
71    /// Returns the map value, if this is a map.
72    #[must_use]
73    pub const fn as_map(&self) -> Option<&BTreeMap<String, Self>> {
74        match self {
75            Self::Map(value) => Some(value),
76            _ => None,
77        }
78    }
79}
80
81impl From<()> for ConfigValue {
82    fn from((): ()) -> Self {
83        Self::Null
84    }
85}
86
87impl From<bool> for ConfigValue {
88    fn from(value: bool) -> Self {
89        Self::Bool(value)
90    }
91}
92
93impl From<i8> for ConfigValue {
94    fn from(value: i8) -> Self {
95        Self::Integer(i64::from(value))
96    }
97}
98
99impl From<i16> for ConfigValue {
100    fn from(value: i16) -> Self {
101        Self::Integer(i64::from(value))
102    }
103}
104
105impl From<i32> for ConfigValue {
106    fn from(value: i32) -> Self {
107        Self::Integer(i64::from(value))
108    }
109}
110
111impl From<i64> for ConfigValue {
112    fn from(value: i64) -> Self {
113        Self::Integer(value)
114    }
115}
116
117impl From<u8> for ConfigValue {
118    fn from(value: u8) -> Self {
119        Self::Integer(i64::from(value))
120    }
121}
122
123impl From<u16> for ConfigValue {
124    fn from(value: u16) -> Self {
125        Self::Integer(i64::from(value))
126    }
127}
128
129impl From<u32> for ConfigValue {
130    fn from(value: u32) -> Self {
131        Self::Integer(i64::from(value))
132    }
133}
134
135impl From<f32> for ConfigValue {
136    fn from(value: f32) -> Self {
137        Self::Float(f64::from(value))
138    }
139}
140
141impl From<f64> for ConfigValue {
142    fn from(value: f64) -> Self {
143        Self::Float(value)
144    }
145}
146
147impl From<String> for ConfigValue {
148    fn from(value: String) -> Self {
149        Self::String(value)
150    }
151}
152
153impl From<&str> for ConfigValue {
154    fn from(value: &str) -> Self {
155        Self::String(value.to_owned())
156    }
157}
158
159impl From<Vec<Self>> for ConfigValue {
160    fn from(value: Vec<Self>) -> Self {
161        Self::List(value)
162    }
163}
164
165impl From<BTreeMap<String, Self>> for ConfigValue {
166    fn from(value: BTreeMap<String, Self>) -> Self {
167        Self::Map(value)
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::ConfigValue;
174    use std::collections::BTreeMap;
175
176    #[test]
177    fn primitive_conversions() {
178        assert_eq!(ConfigValue::from(()), ConfigValue::Null);
179        assert_eq!(ConfigValue::from(true), ConfigValue::Bool(true));
180        assert_eq!(ConfigValue::from(42_i64), ConfigValue::Integer(42));
181        assert_eq!(
182            ConfigValue::from("hello"),
183            ConfigValue::String("hello".to_owned())
184        );
185    }
186
187    #[test]
188    fn accessors_return_expected_values() {
189        let list = ConfigValue::from(vec![ConfigValue::from("a"), ConfigValue::from("b")]);
190        let mut map = BTreeMap::new();
191        map.insert("enabled".to_owned(), ConfigValue::from(true));
192        let map = ConfigValue::from(map);
193
194        assert_eq!(ConfigValue::from(false).as_bool(), Some(false));
195        assert_eq!(ConfigValue::from(12_i64).as_i64(), Some(12));
196        assert_eq!(ConfigValue::from(1.5_f64).as_f64(), Some(1.5));
197        assert_eq!(ConfigValue::from("text").as_str(), Some("text"));
198        assert_eq!(list.as_list().map(<[ConfigValue]>::len), Some(2));
199        assert_eq!(
200            map.as_map()
201                .and_then(|value| value.get("enabled"))
202                .and_then(ConfigValue::as_bool),
203            Some(true)
204        );
205    }
206
207    #[test]
208    fn wrong_type_accessors_return_none() {
209        let value = ConfigValue::from("8080");
210
211        assert_eq!(value.as_bool(), None);
212        assert_eq!(value.as_i64(), None);
213        assert_eq!(value.as_f64(), None);
214        assert_eq!(ConfigValue::from(8080_i64).as_str(), None);
215    }
216
217    #[test]
218    fn map_ordering_is_deterministic() {
219        let mut map = BTreeMap::new();
220        map.insert("z".to_owned(), ConfigValue::from(1_i64));
221        map.insert("a".to_owned(), ConfigValue::from(2_i64));
222        let value = ConfigValue::from(map);
223        let keys: Vec<_> = value
224            .as_map()
225            .expect("map expected")
226            .keys()
227            .cloned()
228            .collect();
229
230        assert_eq!(keys, vec!["a".to_owned(), "z".to_owned()]);
231    }
232}