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