toml_query/
insert.rs

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