toml_query/
set.rs

1/// The Toml Set extensions
2
3#[cfg(feature = "typed")]
4use serde::Serialize;
5use toml::Value;
6
7use crate::error::{Error, Result};
8use crate::tokenizer::tokenize_with_seperator;
9use crate::tokenizer::Token;
10
11pub trait TomlValueSetExt {
12    /// Extension function for setting a value in the current toml::Value document
13    /// using a custom seperator
14    ///
15    /// # Semantics
16    ///
17    /// The function _never_ creates intermediate data structures (Tables or Arrays) in the
18    /// document.
19    ///
20    /// # Return value
21    ///
22    /// * If the set operation worked correctly, `Ok(None)` is returned.
23    /// * If the set operation replaced an existing value `Ok(Some(old_value))` is returned
24    /// * On failure, `Err(e)` is returned:
25    ///     * If the query is `"a.b.c"` but there is no table `"b"`: error
26    ///     * If the query is `"a.b.[0]"` but "`b"` is not an array: error
27    ///     * If the query is `"a.b.[3]"` but the array at "`b"` has no index `3`: error
28    ///     * etc.
29    ///
30    fn set_with_seperator(&mut self, query: &str, sep: char, value: Value)
31        -> Result<Option<Value>>;
32
33    /// Extension function for setting a value from the current toml::Value document
34    ///
35    /// See documentation of `TomlValueSetExt::set_with_seperator`
36    fn set(&mut self, query: &str, value: Value) -> Result<Option<Value>> {
37        self.set_with_seperator(query, '.', value)
38    }
39
40    /// A convenience method for setting any arbitrary serializable value.
41    #[cfg(feature = "typed")]
42    fn set_serialized<S: Serialize>(&mut self, query: &str, value: S) -> Result<Option<Value>> {
43        let value = Value::try_from(value).map_err(Error::TomlSerialize)?;
44        self.set(query, value)
45    }
46}
47
48impl TomlValueSetExt for Value {
49    fn set_with_seperator(
50        &mut self,
51        query: &str,
52        sep: char,
53        value: Value,
54    ) -> Result<Option<Value>> {
55        use crate::resolver::mut_resolver::resolve;
56
57        let mut tokens = tokenize_with_seperator(query, sep)?;
58        let last = tokens.pop_last();
59
60        let val = resolve(self, &tokens, true)?.unwrap(); // safe because of resolve() guarantees
61        let last = last.unwrap_or_else(|| Box::new(tokens));
62
63        match *last {
64            Token::Identifier { ident, .. } => match val {
65                Value::Table(ref mut t) => Ok(t.insert(ident, value)),
66                Value::Array(_) => Err(Error::NoIdentifierInArray(ident)),
67                _ => Err(Error::QueryingValueAsTable(ident)),
68            },
69
70            Token::Index { idx, .. } => match val {
71                Value::Array(ref mut a) => {
72                    if a.len() > idx {
73                        let result = a.swap_remove(idx);
74                        a.insert(idx, value);
75                        Ok(Some(result))
76                    } else {
77                        a.push(value);
78                        Ok(None)
79                    }
80                }
81                Value::Table(_) => Err(Error::NoIndexInTable(idx)),
82                _ => Err(Error::QueryingValueAsArray(idx)),
83            },
84        }
85    }
86}
87
88#[cfg(test)]
89mod test {
90    use super::*;
91    use toml::from_str as toml_from_str;
92    use toml::Value;
93
94    #[test]
95    fn test_set_with_seperator_into_table() {
96        let mut toml: Value = toml_from_str(
97            r#"
98        [table]
99        a = 0
100        "#,
101        )
102        .unwrap();
103
104        let res = toml.set_with_seperator(&String::from("table.a"), '.', Value::Integer(1));
105
106        assert!(res.is_ok());
107
108        let res = res.unwrap();
109        assert!(res.is_some());
110        let res = res.unwrap();
111        assert!(is_match!(res, Value::Integer(0)));
112
113        assert!(is_match!(toml, Value::Table(_)));
114        match toml {
115            Value::Table(ref t) => {
116                assert!(!t.is_empty());
117
118                let inner = t.get("table");
119                assert!(inner.is_some());
120
121                let inner = inner.unwrap();
122                assert!(is_match!(inner, Value::Table(_)));
123                match inner {
124                    Value::Table(ref t) => {
125                        assert!(!t.is_empty());
126
127                        let a = t.get("a");
128                        assert!(a.is_some());
129
130                        let a = a.unwrap();
131                        assert!(is_match!(a, Value::Integer(1)));
132                    }
133                    _ => panic!("What just happenend?"),
134                }
135            }
136            _ => panic!("What just happenend?"),
137        }
138    }
139
140    #[test]
141    fn test_set_with_seperator_into_table_key_nonexistent() {
142        let mut toml: Value = toml_from_str(
143            r#"
144        [table]
145        "#,
146        )
147        .unwrap();
148
149        let res = toml.set_with_seperator(&String::from("table.a"), '.', Value::Integer(1));
150
151        assert!(res.is_ok());
152        let res = res.unwrap();
153
154        assert!(res.is_none());
155
156        assert!(is_match!(toml, Value::Table(_)));
157        match toml {
158            Value::Table(ref t) => {
159                assert!(!t.is_empty());
160
161                let inner = t.get("table");
162                assert!(inner.is_some());
163
164                let inner = inner.unwrap();
165                assert!(is_match!(inner, Value::Table(_)));
166                match inner {
167                    Value::Table(ref t) => {
168                        assert!(!t.is_empty());
169
170                        let a = t.get("a");
171                        assert!(a.is_some());
172
173                        let a = a.unwrap();
174                        assert!(is_match!(a, Value::Integer(1)));
175                    }
176                    _ => panic!("What just happenend?"),
177                }
178            }
179            _ => panic!("What just happenend?"),
180        }
181    }
182
183    #[test]
184    fn test_set_with_seperator_into_array() {
185        use std::ops::Index;
186
187        let mut toml: Value = toml_from_str(
188            r#"
189        array = [ 0 ]
190        "#,
191        )
192        .unwrap();
193
194        let res = toml.set_with_seperator(&String::from("array.[0]"), '.', Value::Integer(1));
195
196        assert!(res.is_ok());
197
198        let res = res.unwrap();
199        assert!(res.is_some());
200        let res = res.unwrap();
201        assert!(is_match!(res, Value::Integer(0)));
202
203        assert!(is_match!(toml, Value::Table(_)));
204        match toml {
205            Value::Table(ref t) => {
206                assert!(!t.is_empty());
207
208                let inner = t.get("array");
209                assert!(inner.is_some());
210
211                let inner = inner.unwrap();
212                assert!(is_match!(inner, Value::Array(_)));
213                match inner {
214                    Value::Array(ref a) => {
215                        assert!(!a.is_empty());
216                        assert!(is_match!(a.index(0), Value::Integer(1)));
217                    }
218                    _ => panic!("What just happenend?"),
219                }
220            }
221            _ => panic!("What just happenend?"),
222        }
223    }
224
225    #[test]
226    fn test_set_with_seperator_into_table_index_nonexistent() {
227        use std::ops::Index;
228
229        let mut toml: Value = toml_from_str(
230            r#"
231        array = []
232        "#,
233        )
234        .unwrap();
235
236        let res = toml.set_with_seperator(&String::from("array.[0]"), '.', Value::Integer(1));
237
238        assert!(res.is_ok());
239
240        let res = res.unwrap();
241        assert!(res.is_none());
242
243        assert!(is_match!(toml, Value::Table(_)));
244        match toml {
245            Value::Table(ref t) => {
246                assert!(!t.is_empty());
247
248                let inner = t.get("array");
249                assert!(inner.is_some());
250
251                let inner = inner.unwrap();
252                assert!(is_match!(inner, Value::Array(_)));
253                match inner {
254                    Value::Array(ref a) => {
255                        assert!(!a.is_empty());
256                        assert!(is_match!(a.index(0), Value::Integer(1)));
257                    }
258                    _ => panic!("What just happenend?"),
259                }
260            }
261            _ => panic!("What just happenend?"),
262        }
263    }
264
265    #[test]
266    #[allow(clippy::cognitive_complexity)]
267    fn test_set_with_seperator_into_nested_table() {
268        let mut toml: Value = toml_from_str(
269            r#"
270        [a.b.c]
271        d = 0
272        "#,
273        )
274        .unwrap();
275
276        let res = toml.set_with_seperator(&String::from("a.b.c.d"), '.', Value::Integer(1));
277
278        assert!(res.is_ok());
279
280        let res = res.unwrap();
281        assert!(res.is_some());
282        let res = res.unwrap();
283        assert!(is_match!(res, Value::Integer(0)));
284
285        assert!(is_match!(toml, Value::Table(_)));
286        match toml {
287            Value::Table(ref t) => {
288                assert!(!t.is_empty());
289
290                let a = t.get("a");
291                assert!(a.is_some());
292
293                let a = a.unwrap();
294                assert!(is_match!(a, Value::Table(_)));
295                match a {
296                    Value::Table(ref a) => {
297                        assert!(!a.is_empty());
298
299                        let b_tab = a.get("b");
300                        assert!(b_tab.is_some());
301
302                        let b_tab = b_tab.unwrap();
303                        assert!(is_match!(b_tab, Value::Table(_)));
304                        match b_tab {
305                            Value::Table(ref b) => {
306                                assert!(!b.is_empty());
307
308                                let c_tab = b.get("c");
309                                assert!(c_tab.is_some());
310
311                                let c_tab = c_tab.unwrap();
312                                assert!(is_match!(c_tab, Value::Table(_)));
313                                match c_tab {
314                                    Value::Table(ref c) => {
315                                        assert!(!c.is_empty());
316
317                                        let d = c.get("d");
318                                        assert!(d.is_some());
319
320                                        let d = d.unwrap();
321                                        assert!(is_match!(d, Value::Integer(1)));
322                                    }
323                                    _ => panic!("What just happenend?"),
324                                }
325                            }
326                            _ => panic!("What just happenend?"),
327                        }
328                    }
329                    _ => panic!("What just happenend?"),
330                }
331            }
332            _ => panic!("What just happenend?"),
333        }
334    }
335
336    #[test]
337    fn test_set_with_seperator_into_nonexistent_table() {
338        let mut toml: Value = toml_from_str("").unwrap();
339
340        let res = toml.set_with_seperator(&String::from("table.a"), '.', Value::Integer(1));
341
342        assert!(res.is_err());
343
344        let res = res.unwrap_err();
345        assert!(is_match!(res, Error::IdentifierNotFoundInDocument(_)));
346    }
347
348    #[test]
349    fn test_set_with_seperator_into_nonexistent_array() {
350        let mut toml: Value = toml_from_str("").unwrap();
351
352        let res = toml.set_with_seperator(&String::from("[0]"), '.', Value::Integer(1));
353
354        assert!(res.is_err());
355
356        let res = res.unwrap_err();
357        assert!(is_match!(res, Error::NoIndexInTable(0)));
358    }
359
360    #[test]
361    fn test_set_with_seperator_ident_into_ary() {
362        let mut toml: Value = toml_from_str(
363            r#"
364        array = [ 0 ]
365        "#,
366        )
367        .unwrap();
368
369        let res = toml.set_with_seperator(&String::from("array.foo"), '.', Value::Integer(2));
370
371        assert!(res.is_err());
372        let res = res.unwrap_err();
373
374        assert!(is_match!(res, Error::NoIdentifierInArray(_)));
375    }
376
377    #[test]
378    fn test_set_with_seperator_index_into_table() {
379        let mut toml: Value = toml_from_str(
380            r#"
381        foo = { bar = 1 }
382        "#,
383        )
384        .unwrap();
385
386        let res = toml.set_with_seperator(&String::from("foo.[0]"), '.', Value::Integer(2));
387
388        assert!(res.is_err());
389        let res = res.unwrap_err();
390
391        assert!(is_match!(res, Error::NoIndexInTable(_)));
392    }
393
394    #[test]
395    fn test_set_with_seperator_ident_into_non_structure() {
396        let mut toml: Value = toml_from_str(
397            r#"
398        val = 0
399        "#,
400        )
401        .unwrap();
402
403        let res = toml.set_with_seperator(&String::from("val.foo"), '.', Value::Integer(2));
404
405        assert!(res.is_err());
406        let res = res.unwrap_err();
407
408        assert!(is_match!(res, Error::QueryingValueAsTable(_)));
409    }
410
411    #[test]
412    fn test_set_with_seperator_index_into_non_structure() {
413        let mut toml: Value = toml_from_str(
414            r#"
415        foo = 1
416        "#,
417        )
418        .unwrap();
419
420        let res = toml.set_with_seperator(&String::from("foo.[0]"), '.', Value::Integer(2));
421
422        assert!(res.is_err());
423        let res = res.unwrap_err();
424
425        assert!(is_match!(res, Error::QueryingValueAsArray(_)));
426    }
427
428    #[cfg(feature = "typed")]
429    #[test]
430    fn test_serialize() {
431        use crate::insert::TomlValueInsertExt;
432        use toml::map::Map;
433
434        #[derive(Serialize, Deserialize, Debug)]
435        struct Test {
436            a: u64,
437            s: String,
438        }
439
440        let mut toml = Value::Table(Map::new());
441        let test = Test {
442            a: 15,
443            s: String::from("Helloworld"),
444        };
445
446        assert!(toml
447            .insert_serialized("table.value", test)
448            .unwrap()
449            .is_none());
450
451        eprintln!("{:#}", toml);
452
453        match toml {
454            Value::Table(ref tab) => match tab.get("table").unwrap() {
455                Value::Table(ref inner) => match inner.get("value").unwrap() {
456                    Value::Table(ref data) => {
457                        assert!(is_match!(data.get("a").unwrap(), Value::Integer(15)));
458                        match data.get("s").unwrap() {
459                            Value::String(ref s) => assert_eq!(s, "Helloworld"),
460                            _ => unreachable!(),
461                        };
462                    }
463                    _ => unreachable!(),
464                },
465                _ => unreachable!(),
466            },
467            _ => unreachable!(),
468        }
469    }
470}