toml_query/
read.rs

1/// The Toml Read extensions
2
3#[cfg(feature = "typed")]
4use std::fmt::Debug;
5
6#[cfg(feature = "typed")]
7use serde::{Deserialize, Serialize};
8use toml::Value;
9
10use crate::error::{Error, Result};
11use crate::tokenizer::tokenize_with_seperator;
12
13pub trait TomlValueReadExt<'doc> {
14    /// Extension function for reading a value from the current toml::Value document
15    /// using a custom seperator
16    fn read_with_seperator(&'doc self, query: &str, sep: char) -> Result<Option<&'doc Value>>;
17
18    /// Extension function for reading a value from the current toml::Value document mutably
19    /// using a custom seperator
20    fn read_mut_with_seperator(
21        &'doc mut self,
22        query: &str,
23        sep: char,
24    ) -> Result<Option<&'doc mut Value>>;
25
26    /// Extension function for reading a value from the current toml::Value document
27    fn read(&'doc self, query: &str) -> Result<Option<&'doc Value>> {
28        self.read_with_seperator(query, '.')
29    }
30
31    /// Extension function for reading a value from the current toml::Value document mutably
32    fn read_mut(&'doc mut self, query: &str) -> Result<Option<&'doc mut Value>> {
33        self.read_mut_with_seperator(query, '.')
34    }
35
36    #[cfg(feature = "typed")]
37    fn read_deserialized<'de, D: Deserialize<'de>>(&'doc self, query: &str) -> Result<Option<D>> {
38        let raw = self.read(query)?;
39
40        match raw {
41            Some(value) => {
42                let deserialized = value.clone().try_into().map_err(Error::TomlDeserialize)?;
43                Ok(Some(deserialized))
44            }
45            None => Ok(None),
46        }
47    }
48
49    #[cfg(feature = "typed")]
50    fn read_partial<'a, P: Partial<'a>>(&'doc self) -> Result<Option<P::Output>> {
51        self.read_deserialized::<P::Output>(P::LOCATION)
52    }
53}
54
55/// Describes a _part_ of a document
56#[cfg(feature = "typed")]
57pub trait Partial<'a> {
58    // The location ("section") of the header where to find the struct
59    const LOCATION: &'static str;
60
61    // The type which represents the data
62    type Output: Serialize + Deserialize<'a> + Debug;
63}
64
65impl<'doc> TomlValueReadExt<'doc> for Value {
66    fn read_with_seperator(&'doc self, query: &str, sep: char) -> Result<Option<&'doc Value>> {
67        use crate::resolver::non_mut_resolver::resolve;
68
69        tokenize_with_seperator(query, sep).and_then(move |tokens| resolve(self, &tokens, false))
70    }
71
72    fn read_mut_with_seperator(
73        &'doc mut self,
74        query: &str,
75        sep: char,
76    ) -> Result<Option<&'doc mut Value>> {
77        use crate::resolver::mut_resolver::resolve;
78
79        tokenize_with_seperator(query, sep).and_then(move |tokens| resolve(self, &tokens, false))
80    }
81}
82
83pub trait TomlValueReadTypeExt<'doc>: TomlValueReadExt<'doc> {
84    fn read_string(&'doc self, query: &str) -> Result<Option<String>>;
85    fn read_int(&'doc self, query: &str) -> Result<Option<i64>>;
86    fn read_float(&'doc self, query: &str) -> Result<Option<f64>>;
87    fn read_bool(&'doc self, query: &str) -> Result<Option<bool>>;
88}
89
90macro_rules! make_type_getter {
91    ($fnname:ident, $rettype:ty, $typename:expr, $matcher:pat => $implementation:expr) => {
92        fn $fnname(&'doc self, query: &str) -> Result<Option<$rettype>> {
93            self.read_with_seperator(query, '.').and_then(|o| match o {
94                $matcher => Ok(Some($implementation)),
95                Some(o)  => Err(Error::TypeError($typename, crate::util::name_of_val(&o)).into()),
96                None     => Ok(None),
97            })
98        }
99    };
100}
101
102impl<'doc, T> TomlValueReadTypeExt<'doc> for T
103where
104    T: TomlValueReadExt<'doc>,
105{
106    make_type_getter!(read_string, String, "String", Some(&Value::String(ref obj)) => obj.clone());
107    make_type_getter!(read_int, i64, "Integer", Some(&Value::Integer(obj)) => obj);
108    make_type_getter!(read_float, f64, "Float", Some(&Value::Float(obj)) => obj);
109    make_type_getter!(read_bool, bool, "Boolean", Some(&Value::Boolean(obj)) => obj);
110}
111
112#[cfg(test)]
113mod test {
114    use super::*;
115    use toml::from_str as toml_from_str;
116
117    #[test]
118    fn test_read_empty() {
119        let toml: Value = toml_from_str("").unwrap();
120
121        let val = toml.read_with_seperator(&String::from("a"), '.');
122
123        assert!(val.is_ok());
124        let val = val.unwrap();
125
126        assert!(val.is_none());
127    }
128
129    #[test]
130    fn test_read_table() {
131        let toml: Value = toml_from_str(
132            r#"
133        [table]
134        "#,
135        )
136        .unwrap();
137
138        let val = toml.read_with_seperator(&String::from("table"), '.');
139
140        assert!(val.is_ok());
141        let val = val.unwrap();
142
143        assert!(val.is_some());
144        let val = val.unwrap();
145
146        assert!(is_match!(val, &Value::Table(_)));
147        match val {
148            Value::Table(ref t) => assert!(t.is_empty()),
149            _ => panic!("What just happened?"),
150        }
151    }
152
153    #[test]
154    fn test_read_table_value() {
155        let toml: Value = toml_from_str(
156            r#"
157        [table]
158        a = 1
159        "#,
160        )
161        .unwrap();
162
163        let val = toml.read_with_seperator(&String::from("table.a"), '.');
164
165        assert!(val.is_ok());
166        let val = val.unwrap();
167
168        assert!(val.is_some());
169        let val = val.unwrap();
170
171        assert!(is_match!(val, &Value::Integer(1)));
172    }
173
174    #[test]
175    fn test_read_empty_table_value() {
176        let toml: Value = toml_from_str(
177            r#"
178        [table]
179        "#,
180        )
181        .unwrap();
182
183        let val = toml.read_with_seperator(&String::from("table.a"), '.');
184        assert!(val.is_ok());
185        let val = val.unwrap();
186
187        assert!(val.is_none());
188    }
189
190    #[test]
191    fn test_read_table_index() {
192        let toml: Value = toml_from_str(
193            r#"
194        [table]
195        "#,
196        )
197        .unwrap();
198
199        let val = toml.read_with_seperator(&String::from("table.[0]"), '.');
200        assert!(val.is_err());
201        let err = val.unwrap_err();
202
203        assert!(is_match!(err, Error::NoIndexInTable(_)));
204    }
205
206    ///
207    ///
208    /// Querying without specifying the seperator
209    ///
210    ///
211
212    #[test]
213    fn test_read_empty_without_seperator() {
214        let toml: Value = toml_from_str("").unwrap();
215
216        let val = toml.read(&String::from("a"));
217        assert!(val.is_ok());
218        let val = val.unwrap();
219
220        assert!(val.is_none());
221    }
222
223    #[test]
224    fn test_read_table_without_seperator() {
225        let toml: Value = toml_from_str(
226            r#"
227        [table]
228        "#,
229        )
230        .unwrap();
231
232        let val = toml.read(&String::from("table"));
233
234        assert!(val.is_ok());
235        let val = val.unwrap();
236
237        assert!(val.is_some());
238        let val = val.unwrap();
239
240        assert!(is_match!(val, &Value::Table(_)));
241        match val {
242            Value::Table(ref t) => assert!(t.is_empty()),
243            _ => panic!("What just happened?"),
244        }
245    }
246
247    #[test]
248    fn test_read_table_value_without_seperator() {
249        let toml: Value = toml_from_str(
250            r#"
251        [table]
252        a = 1
253        "#,
254        )
255        .unwrap();
256
257        let val = toml.read(&String::from("table.a"));
258
259        assert!(val.is_ok());
260        let val = val.unwrap();
261
262        assert!(val.is_some());
263        let val = val.unwrap();
264
265        assert!(is_match!(val, &Value::Integer(1)));
266    }
267
268    #[test]
269    fn test_read_empty_table_value_without_seperator() {
270        let toml: Value = toml_from_str(
271            r#"
272        [table]
273        "#,
274        )
275        .unwrap();
276
277        let val = toml.read(&String::from("table.a"));
278        assert!(val.is_ok());
279        let val = val.unwrap();
280
281        assert!(val.is_none());
282    }
283
284    #[test]
285    fn test_read_table_index_without_seperator() {
286        let toml: Value = toml_from_str(
287            r#"
288        [table]
289        "#,
290        )
291        .unwrap();
292
293        let val = toml.read(&String::from("table.[0]"));
294        assert!(val.is_err());
295        let err = val.unwrap_err();
296
297        assert!(is_match!(err, Error::NoIndexInTable(_)));
298    }
299}
300
301#[cfg(test)]
302mod high_level_fn_test {
303    use super::*;
304    use toml::from_str as toml_from_str;
305
306    #[test]
307    fn test_read_table_value() {
308        let toml: Value = toml_from_str(
309            r#"
310        [table]
311        a = 1
312        "#,
313        )
314        .unwrap();
315
316        let val = toml.read_int("table.a").unwrap();
317
318        assert_eq!(val.unwrap(), 1);
319    }
320
321    #[cfg(feature = "typed")]
322    #[test]
323    fn test_name() {
324        let toml: Value = toml_from_str(
325            r#"
326        [table]
327        a = 1
328        "#,
329        )
330        .unwrap();
331
332        let val: u32 = toml.read_deserialized("table.a").unwrap().unwrap();
333
334        assert_eq!(val, 1);
335    }
336
337    #[cfg(feature = "typed")]
338    #[test]
339    fn test_deser() {
340        use crate::insert::TomlValueInsertExt;
341        use crate::read::TomlValueReadExt;
342        use toml::map::Map;
343
344        #[derive(Serialize, Deserialize, Debug)]
345        struct Test {
346            a: u64,
347            s: String,
348        }
349
350        let mut toml = Value::Table(Map::new());
351        let test = Test {
352            a: 15,
353            s: String::from("Helloworld"),
354        };
355
356        assert!(toml
357            .insert_serialized("table.value", test)
358            .unwrap()
359            .is_none());
360        let _: Test = toml.read_deserialized("table.value").unwrap().unwrap();
361
362        assert!(true);
363    }
364}
365
366#[cfg(all(test, feature = "typed"))]
367mod partial_tests {
368    use super::*;
369
370    use toml::map::Map;
371    use toml::Value;
372
373    #[derive(Debug, Deserialize, Serialize)]
374    struct TestObj {
375        pub value: String,
376    }
377
378    impl<'a> Partial<'a> for TestObj {
379        const LOCATION: &'static str = "foo";
380        type Output = Self;
381    }
382
383    #[test]
384    fn test_compiles() {
385        let tbl = {
386            let mut tbl = Map::new();
387            tbl.insert(String::from("foo"), {
388                let mut tbl = Map::new();
389                tbl.insert(String::from("value"), Value::String(String::from("foobar")));
390                Value::Table(tbl)
391            });
392            Value::Table(tbl)
393        };
394
395        let obj: TestObj = tbl.read_partial::<TestObj>().unwrap().unwrap();
396        assert_eq!(obj.value, "foobar");
397    }
398}