1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use std::collections::BTreeMap;
5
6#[derive(Clone, Debug, PartialEq)]
8pub enum ConfigValue {
9 Null,
11 Bool(bool),
13 Integer(i64),
15 Float(f64),
17 String(String),
19 List(Vec<Self>),
21 Map(BTreeMap<String, Self>),
23}
24
25impl ConfigValue {
26 #[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 #[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 #[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 #[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 #[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 #[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}