Skip to main content

tanzim_source/
impls.rs

1//! [`From`] and [`TryFrom`] conversions for [`Source`], [`SourceBuilder`], and [`OptionValue`].
2
3use crate::{Error, OptionValue, Options, ParseError, Source, SourceBuilder};
4use std::borrow::Cow;
5use std::collections::HashMap;
6
7impl From<Source> for SourceBuilder {
8    fn from(value: Source) -> Self {
9        Self {
10            source: Some(value.source().to_string()),
11            options: value.options().clone(),
12            resource: value.resource().to_string(),
13            resource_colon: value.resource_colon(),
14        }
15    }
16}
17
18impl TryFrom<&str> for SourceBuilder {
19    type Error = Error;
20
21    fn try_from(value: &str) -> Result<Self, Self::Error> {
22        Ok(Source::parse(value)?.into())
23    }
24}
25
26impl TryFrom<String> for SourceBuilder {
27    type Error = Error;
28
29    fn try_from(value: String) -> Result<Self, Self::Error> {
30        Self::try_from(value.as_str())
31    }
32}
33
34impl TryFrom<&String> for SourceBuilder {
35    type Error = Error;
36
37    fn try_from(value: &String) -> Result<Self, Self::Error> {
38        Self::try_from(value.as_str())
39    }
40}
41
42impl TryFrom<Cow<'_, str>> for SourceBuilder {
43    type Error = Error;
44
45    fn try_from(value: Cow<'_, str>) -> Result<Self, Self::Error> {
46        Self::try_from(value.as_ref())
47    }
48}
49
50impl From<bool> for OptionValue {
51    fn from(value: bool) -> Self {
52        Self::Bool(value)
53    }
54}
55
56impl From<i64> for OptionValue {
57    fn from(value: i64) -> Self {
58        Self::Integer(value)
59    }
60}
61
62impl From<i32> for OptionValue {
63    fn from(value: i32) -> Self {
64        Self::Integer(value as i64)
65    }
66}
67
68impl From<i16> for OptionValue {
69    fn from(value: i16) -> Self {
70        Self::Integer(value as i64)
71    }
72}
73
74impl From<i8> for OptionValue {
75    fn from(value: i8) -> Self {
76        Self::Integer(value as i64)
77    }
78}
79
80impl From<u64> for OptionValue {
81    fn from(value: u64) -> Self {
82        Self::Integer(value as i64)
83    }
84}
85
86impl From<u32> for OptionValue {
87    fn from(value: u32) -> Self {
88        Self::Integer(value as i64)
89    }
90}
91
92impl From<u16> for OptionValue {
93    fn from(value: u16) -> Self {
94        Self::Integer(value as i64)
95    }
96}
97
98impl From<u8> for OptionValue {
99    fn from(value: u8) -> Self {
100        Self::Integer(value as i64)
101    }
102}
103
104impl From<isize> for OptionValue {
105    fn from(value: isize) -> Self {
106        Self::Integer(value as i64)
107    }
108}
109
110impl From<usize> for OptionValue {
111    fn from(value: usize) -> Self {
112        Self::Integer(value as i64)
113    }
114}
115
116impl From<f64> for OptionValue {
117    fn from(value: f64) -> Self {
118        Self::Float(value)
119    }
120}
121
122impl From<f32> for OptionValue {
123    fn from(value: f32) -> Self {
124        Self::Float(value as f64)
125    }
126}
127
128impl From<&str> for OptionValue {
129    fn from(value: &str) -> Self {
130        Self::String(value.to_string())
131    }
132}
133
134impl From<String> for OptionValue {
135    fn from(value: String) -> Self {
136        Self::String(value)
137    }
138}
139
140impl From<&String> for OptionValue {
141    fn from(value: &String) -> Self {
142        Self::String(value.clone())
143    }
144}
145
146impl<T: Into<OptionValue>> From<Vec<T>> for OptionValue {
147    fn from(value: Vec<T>) -> Self {
148        Self::List(value.into_iter().map(Into::into).collect())
149    }
150}
151
152impl<T: Into<OptionValue> + Clone> From<&[T]> for OptionValue {
153    fn from(value: &[T]) -> Self {
154        Self::List(value.iter().cloned().map(Into::into).collect())
155    }
156}
157
158impl From<Options> for OptionValue {
159    fn from(value: Options) -> Self {
160        Self::Map(value)
161    }
162}
163
164impl<K: Into<String>, V: Into<OptionValue>> From<HashMap<K, V>> for OptionValue {
165    fn from(value: HashMap<K, V>) -> Self {
166        let mut options = Options::default();
167        for (key, value) in value {
168            options.insert(key, value);
169        }
170        Self::Map(options)
171    }
172}
173
174impl std::str::FromStr for Source {
175    type Err = ParseError;
176
177    fn from_str(value: &str) -> Result<Self, Self::Err> {
178        Self::parse(value)
179    }
180}
181
182impl TryFrom<&str> for Source {
183    type Error = ParseError;
184
185    fn try_from(value: &str) -> Result<Self, Self::Error> {
186        Self::parse(value)
187    }
188}
189
190impl TryFrom<String> for Source {
191    type Error = ParseError;
192
193    fn try_from(value: String) -> Result<Self, Self::Error> {
194        Self::parse(&value)
195    }
196}
197
198impl TryFrom<&String> for Source {
199    type Error = ParseError;
200
201    fn try_from(value: &String) -> Result<Self, Self::Error> {
202        Self::parse(value)
203    }
204}
205
206impl TryFrom<Cow<'_, str>> for Source {
207    type Error = ParseError;
208
209    fn try_from(value: Cow<'_, str>) -> Result<Self, Self::Error> {
210        Self::parse(&value)
211    }
212}
213
214#[cfg(test)]
215mod tests {
216    use super::*;
217    use std::convert::TryFrom;
218    use std::str::FromStr;
219
220    #[test]
221    fn try_from_str() {
222        let source = Source::try_from("file:/tmp/x").unwrap();
223        assert_eq!(source.resource(), "/tmp/x");
224    }
225
226    #[test]
227    fn builder_try_from_str() {
228        let builder = SourceBuilder::try_from("env(prefix=APP_)").unwrap();
229        let source = builder.build().unwrap();
230        assert_eq!(source.source(), "env");
231        assert_eq!(
232            source.options().get("prefix"),
233            Some(&OptionValue::String("APP_".into()))
234        );
235    }
236
237    #[test]
238    fn builder_try_from_invalid_is_parse_error() {
239        let error = SourceBuilder::try_from("env(prefix=)").unwrap_err();
240        assert!(matches!(error, Error::Parse(ParseError::EmptyValue { .. })));
241    }
242
243    #[test]
244    fn source_round_trips_through_builder_from() {
245        let original = Source::try_from("file:/tmp/x").unwrap();
246        let rebuilt = SourceBuilder::from(original.clone()).build().unwrap();
247        assert_eq!(rebuilt.resource(), original.resource());
248        assert_eq!(rebuilt.source(), original.source());
249    }
250
251    #[test]
252    fn source_from_str_matches_try_from() {
253        let from_str = Source::from_str("env(prefix=APP_)").unwrap();
254        let try_from = Source::try_from("env(prefix=APP_)").unwrap();
255        assert_eq!(from_str.source(), try_from.source());
256        assert_eq!(from_str.resource(), try_from.resource());
257    }
258
259    #[test]
260    fn source_try_from_string_and_cow() {
261        let owned = Source::try_from("file:/a".to_string()).unwrap();
262        let borrowed = Source::try_from(&"file:/a".to_string()).unwrap();
263        let cow = Source::try_from(Cow::Borrowed("file:/a")).unwrap();
264        assert_eq!(owned.resource(), "/a");
265        assert_eq!(borrowed.resource(), "/a");
266        assert_eq!(cow.resource(), "/a");
267    }
268
269    #[test]
270    fn builder_try_from_string_and_cow() {
271        let owned = SourceBuilder::try_from("env".to_string()).unwrap();
272        let borrowed = SourceBuilder::try_from(&"env".to_string()).unwrap();
273        let cow = SourceBuilder::try_from(Cow::Borrowed("env")).unwrap();
274        assert_eq!(owned.build().unwrap().source(), "env");
275        assert_eq!(borrowed.build().unwrap().source(), "env");
276        assert_eq!(cow.build().unwrap().source(), "env");
277    }
278
279    #[test]
280    fn option_value_from_scalars() {
281        assert_eq!(OptionValue::from(true), OptionValue::Bool(true));
282        assert_eq!(OptionValue::from(42_i8), OptionValue::Integer(42));
283        assert_eq!(OptionValue::from(42_i16), OptionValue::Integer(42));
284        assert_eq!(OptionValue::from(42_i32), OptionValue::Integer(42));
285        assert_eq!(OptionValue::from(42_i64), OptionValue::Integer(42));
286        assert_eq!(OptionValue::from(42_u8), OptionValue::Integer(42));
287        assert_eq!(OptionValue::from(42_u16), OptionValue::Integer(42));
288        assert_eq!(OptionValue::from(42_u32), OptionValue::Integer(42));
289        assert_eq!(OptionValue::from(42_u64), OptionValue::Integer(42));
290        assert_eq!(OptionValue::from(42_usize), OptionValue::Integer(42));
291        assert_eq!(OptionValue::from(42_isize), OptionValue::Integer(42));
292        assert_eq!(OptionValue::from(1.5_f32), OptionValue::Float(1.5));
293        assert_eq!(OptionValue::from(2.5_f64), OptionValue::Float(2.5));
294    }
295
296    #[test]
297    fn option_value_from_strings_and_collections() {
298        assert_eq!(
299            OptionValue::from("hello"),
300            OptionValue::String("hello".into())
301        );
302        assert_eq!(
303            OptionValue::from("hello".to_string()),
304            OptionValue::String("hello".into())
305        );
306        let text = "hello".to_string();
307        assert_eq!(
308            OptionValue::from(&text),
309            OptionValue::String("hello".into())
310        );
311        assert_eq!(
312            OptionValue::from(vec![1_i64, 2_i64]),
313            OptionValue::List(vec![OptionValue::Integer(1), OptionValue::Integer(2)])
314        );
315        assert_eq!(
316            OptionValue::from([1_i64, 2_i64].as_slice()),
317            OptionValue::List(vec![OptionValue::Integer(1), OptionValue::Integer(2)])
318        );
319        let options = Options::default();
320        assert_eq!(
321            OptionValue::from(options.clone()),
322            OptionValue::Map(options)
323        );
324        assert_eq!(
325            OptionValue::from(HashMap::from([("k", "v")])),
326            OptionValue::Map({
327                let mut map = Options::default();
328                map.insert("k", "v");
329                map
330            })
331        );
332    }
333}