toml_query/
read.rs

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