toml_query/
insert.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 serde::Serialize;
9use toml::Value;
10
11use crate::error::{Error, Result};
12use crate::tokenizer::tokenize_with_seperator;
13use crate::tokenizer::Token;
14
15/// The Toml Insert extensions
16pub trait TomlValueInsertExt {
17    /// Extension function for inserting a value in the current toml::Value document
18    /// using a custom seperator.
19    ///
20    /// For difference to TomlSetExt::set() and friends, read [#semantics].
21    ///
22    /// # Semantics
23    ///
24    /// The function automatically creates intermediate data structures based on the query string.
25    /// That means, if the query string is `"a.b.c.[0]"`, but only a table `"a"` exists in the
26    /// document, the function automatically creates a table `"b"` inside `"a"` and `"c"` inside
27    /// `"b"`, and an array in `"c"`. The array index is ignored if the array is created.
28    ///
29    /// If an Array exists, but the specified index is larger than the last index, the array will
30    /// be expanded by one element: If the array has a length of 3, but the query string specifies
31    /// that the element should be put at 1000, the function ignores the large index and simply
32    /// appends the value to the index.
33    ///
34    /// If a Value is inserted into an Array, the array indexes are shifted. Semantically this is
35    /// the same as doing a `array.insert(4, _)` (see the standard library).
36    ///
37    /// ## Known Bugs
38    ///
39    /// The current implementation does _not_ create intermediate Arrays as described above.
40    /// This is a known bug. So queries like "foo.bar.[0].baz" (or any query which has an array
41    /// element) will fail with an error rather than work.
42    ///
43    /// # Return value
44    ///
45    /// If the insert operation worked correctly, `Ok(None)` is returned.
46    /// If the insert operation replaced an existing value `Ok(Some(old_value))` is returned
47    /// On failure, `Err(e)` is returned
48    ///
49    /// # Examples
50    ///
51    /// The following example shows a working `insert_with_seperator()` call on an empty toml
52    /// document. The Value is inserted as `"foo.bar = 1"` in the document.
53    ///
54    /// ```rust
55    /// extern crate toml;
56    /// extern crate toml_query;
57    ///
58    /// let mut toml : toml::Value = toml::from_str("").unwrap();
59    /// let query = "foo.bar";
60    /// let sep = '.';
61    /// let val = toml::Value::Integer(1);
62    ///
63    /// let res = toml_query::insert::TomlValueInsertExt::insert_with_seperator(&mut toml, query, sep, val);
64    /// assert!(res.is_ok());
65    /// let res = res.unwrap();
66    /// assert!(res.is_none());
67    /// ```
68    ///
69    /// The following example shows a failing `insert_with_seperator()` call on an empty toml
70    /// document. The Query does contain an array token, which does not yet work.
71    ///
72    /// ```rust,should_panic
73    /// extern crate toml;
74    /// extern crate toml_query;
75    ///
76    /// let mut toml : toml::Value = toml::from_str("").unwrap();
77    /// let query = "foo.[0]";
78    /// let sep = '.';
79    /// let val = toml::Value::Integer(1);
80    ///
81    /// let res = toml_query::insert::TomlValueInsertExt::insert_with_seperator(&mut toml, query, sep, val);
82    /// assert!(res.is_ok()); // panics
83    /// ```
84    ///
85    fn insert_with_seperator(
86        &mut self,
87        query: &str,
88        sep: char,
89        value: Value,
90    ) -> Result<Option<Value>>;
91
92    /// Extension function for inserting a value from the current toml::Value document
93    ///
94    /// See documentation of `TomlValueInsertExt::insert_with_seperator`
95    fn insert(&mut self, query: &str, value: Value) -> Result<Option<Value>> {
96        self.insert_with_seperator(query, '.', value)
97    }
98
99    /// A convenience method for inserting any arbitrary serializable value.
100    #[cfg(feature = "typed")]
101    fn insert_serialized<S: Serialize>(&mut self, query: &str, value: S) -> Result<Option<Value>> {
102        let value = Value::try_from(value).map_err(Error::TomlSerialize)?;
103        self.insert(query, value)
104    }
105}
106
107impl TomlValueInsertExt for Value {
108    fn insert_with_seperator(
109        &mut self,
110        query: &str,
111        sep: char,
112        value: Value,
113    ) -> Result<Option<Value>> {
114        use crate::resolver::mut_creating_resolver::resolve;
115
116        let mut tokens = tokenize_with_seperator(query, sep)?;
117        let (val, last) = match tokens.pop_last() {
118            None => (self, Box::new(tokens)),
119            Some(last) => (resolve(self, &tokens)?, last),
120        };
121
122        match *last {
123            Token::Identifier { ident, .. } => match val {
124                Value::Table(ref mut t) => Ok(t.insert(ident, value)),
125                _ => Err(Error::NoIdentifierInArray(ident)),
126            },
127
128            Token::Index { idx, .. } => match val {
129                Value::Array(ref mut a) => {
130                    if a.len() > idx {
131                        a.insert(idx, value);
132                        Ok(None)
133                    } else {
134                        a.push(value);
135                        Ok(None)
136                    }
137                }
138                _ => Err(Error::NoIndexInTable(idx)),
139            },
140        }
141    }
142}
143
144#[cfg(test)]
145mod test {
146    use super::*;
147    use toml::from_str as toml_from_str;
148    use toml::Value;
149
150    #[test]
151    fn test_insert_one_token() {
152        use toml::map::Map;
153        let mut toml = Value::Table(Map::new());
154
155        let res = toml.insert(&String::from("value"), Value::Integer(1));
156        println!("TOML: {:?}", toml);
157        assert!(res.is_ok());
158
159        let res = res.unwrap();
160        assert!(res.is_none());
161
162        assert!(is_match!(toml, Value::Table(_)));
163        match toml {
164            Value::Table(ref t) => {
165                assert!(!t.is_empty());
166
167                let val = t.get("value");
168                assert!(
169                    val.is_some(),
170                    "'value' from table {:?} should be Some(_), is None",
171                    t
172                );
173                let val = val.unwrap();
174
175                assert!(is_match!(val, Value::Integer(1)), "Is not one: {:?}", val);
176            }
177            _ => panic!("What just happenend?"),
178        }
179    }
180
181    #[test]
182    fn test_insert_with_seperator_into_table() {
183        let mut toml: Value = toml_from_str(
184            r#"
185        [table]
186        "#,
187        )
188        .unwrap();
189
190        let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1));
191
192        assert!(res.is_ok());
193
194        let res = res.unwrap();
195        assert!(res.is_none());
196
197        assert!(is_match!(toml, Value::Table(_)));
198        match toml {
199            Value::Table(ref t) => {
200                assert!(!t.is_empty());
201
202                let table = t.get("table");
203                assert!(table.is_some());
204
205                let table = table.unwrap();
206                assert!(is_match!(table, Value::Table(_)));
207                match table {
208                    Value::Table(ref t) => {
209                        assert!(!t.is_empty());
210
211                        let a = t.get("a");
212                        assert!(a.is_some());
213
214                        let a = a.unwrap();
215                        assert!(is_match!(a, Value::Integer(1)));
216                    }
217                    _ => panic!("What just happenend?"),
218                }
219            }
220            _ => panic!("What just happenend?"),
221        }
222    }
223
224    #[test]
225    fn test_insert_with_seperator_into_array() {
226        use std::ops::Index;
227
228        let mut toml: Value = toml_from_str(
229            r#"
230        array = []
231        "#,
232        )
233        .unwrap();
234
235        let res = toml.insert_with_seperator(&String::from("array.[0]"), '.', Value::Integer(1));
236
237        assert!(res.is_ok());
238
239        let res = res.unwrap();
240        assert!(res.is_none());
241
242        assert!(is_match!(toml, Value::Table(_)));
243        match toml {
244            Value::Table(ref t) => {
245                assert!(!t.is_empty());
246
247                let array = t.get("array");
248                assert!(array.is_some());
249
250                let array = array.unwrap();
251                assert!(is_match!(array, Value::Array(_)));
252                match array {
253                    Value::Array(ref a) => {
254                        assert!(!a.is_empty());
255                        assert!(is_match!(a.index(0), Value::Integer(1)));
256                    }
257                    _ => panic!("What just happenend?"),
258                }
259            }
260            _ => panic!("What just happenend?"),
261        }
262    }
263
264    #[test]
265    fn test_insert_with_seperator_into_nested_table() {
266        let mut toml: Value = toml_from_str(
267            r#"
268        [a.b.c]
269        "#,
270        )
271        .unwrap();
272
273        let res = toml.insert_with_seperator(&String::from("a.b.c.d"), '.', Value::Integer(1));
274
275        assert!(res.is_ok());
276
277        let res = res.unwrap();
278        assert!(res.is_none());
279
280        assert!(is_match!(toml, Value::Table(_)));
281        match toml {
282            Value::Table(ref outer) => {
283                assert!(!outer.is_empty());
284                let a_tab = outer.get("a");
285                assert!(a_tab.is_some());
286
287                let a_tab = a_tab.unwrap();
288                assert!(is_match!(a_tab, Value::Table(_)));
289                match a_tab {
290                    Value::Table(ref a) => {
291                        assert!(!a.is_empty());
292
293                        let b_tab = a.get("b");
294                        assert!(b_tab.is_some());
295
296                        let b_tab = b_tab.unwrap();
297                        assert!(is_match!(b_tab, Value::Table(_)));
298                        match b_tab {
299                            Value::Table(ref b) => {
300                                assert!(!b.is_empty());
301
302                                let c_tab = b.get("c");
303                                assert!(c_tab.is_some());
304
305                                let c_tab = c_tab.unwrap();
306                                assert!(is_match!(c_tab, Value::Table(_)));
307                                match c_tab {
308                                    Value::Table(ref c) => {
309                                        assert!(!c.is_empty());
310
311                                        let d = c.get("d");
312                                        assert!(d.is_some());
313
314                                        let d = d.unwrap();
315                                        assert!(is_match!(d, Value::Integer(1)));
316                                    }
317                                    _ => panic!("What just happenend?"),
318                                }
319                            }
320                            _ => panic!("What just happenend?"),
321                        }
322                    }
323                    _ => panic!("What just happenend?"),
324                }
325            }
326            _ => panic!("What just happened?"),
327        }
328    }
329
330    #[test]
331    fn test_insert_with_seperator_into_table_where_array_is() {
332        let mut toml: Value = toml_from_str(
333            r#"
334        table = []
335        "#,
336        )
337        .unwrap();
338
339        let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1));
340
341        assert!(res.is_err());
342
343        let err = res.unwrap_err();
344        assert!(is_match!(err, Error::NoIdentifierInArray(_)));
345    }
346
347    #[test]
348    fn test_insert_with_seperator_into_array_where_table_is() {
349        let mut toml: Value = toml_from_str(
350            r#"
351        [table]
352        "#,
353        )
354        .unwrap();
355
356        let res = toml.insert_with_seperator(&String::from("table.[0]"), '.', Value::Integer(1));
357
358        assert!(res.is_err());
359
360        let err = res.unwrap_err();
361        assert!(is_match!(err, Error::NoIndexInTable(_)));
362    }
363
364    #[test]
365    fn test_insert_with_seperator_into_array_between_values() {
366        use std::ops::Index;
367
368        let mut toml: Value = toml_from_str(
369            r#"
370        array = [1, 2, 3, 4, 5]
371        "#,
372        )
373        .unwrap();
374
375        let res = toml.insert_with_seperator(&String::from("array.[2]"), '.', Value::Integer(6));
376
377        assert!(res.is_ok());
378
379        let res = res.unwrap();
380        assert!(res.is_none());
381
382        assert!(is_match!(toml, Value::Table(_)));
383        match toml {
384            Value::Table(ref t) => {
385                assert!(!t.is_empty());
386
387                let array = t.get("array");
388                assert!(array.is_some());
389
390                let array = array.unwrap();
391                assert!(is_match!(array, Value::Array(_)));
392                match array {
393                    Value::Array(ref a) => {
394                        assert!(!a.is_empty());
395                        assert!(is_match!(a.index(0), Value::Integer(1)));
396                        assert!(is_match!(a.index(1), Value::Integer(2)));
397                        assert!(is_match!(a.index(2), Value::Integer(6)));
398                        assert!(is_match!(a.index(3), Value::Integer(3)));
399                        assert!(is_match!(a.index(4), Value::Integer(4)));
400                        assert!(is_match!(a.index(5), Value::Integer(5)));
401                    }
402                    _ => panic!("What just happenend?"),
403                }
404            }
405            _ => panic!("What just happenend?"),
406        }
407    }
408
409    #[test]
410    fn test_insert_with_seperator_into_table_with_nonexisting_keys() {
411        let mut toml: Value = toml_from_str(
412            r#"
413        "#,
414        )
415        .unwrap();
416
417        let res = toml.insert_with_seperator(&String::from("table.a"), '.', Value::Integer(1));
418
419        assert!(res.is_ok());
420
421        let res = res.unwrap();
422        assert!(res.is_none());
423
424        assert!(is_match!(toml, Value::Table(_)));
425        match toml {
426            Value::Table(ref t) => {
427                assert!(!t.is_empty());
428
429                let table = t.get("table");
430                assert!(table.is_some());
431
432                let table = table.unwrap();
433                assert!(is_match!(table, Value::Table(_)));
434                match table {
435                    Value::Table(ref t) => {
436                        assert!(!t.is_empty());
437
438                        let a = t.get("a");
439                        assert!(a.is_some());
440
441                        let a = a.unwrap();
442                        assert!(is_match!(a, Value::Integer(1)));
443                    }
444                    _ => panic!("What just happenend?"),
445                }
446            }
447            _ => panic!("What just happenend?"),
448        }
449    }
450}