toml_query/
delete.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/// The Toml Delete extensions
8use toml::Value;
9
10use crate::error::{Error, Result};
11use crate::tokenizer::tokenize_with_seperator;
12use crate::tokenizer::Token;
13
14pub trait TomlValueDeleteExt {
15    /// Extension function for deleting a value in the current toml::Value document
16    /// using a custom seperator.
17    ///
18    /// # Semantics
19    ///
20    /// The function does _not_ delete non-empty data structures. So deleting `array` from
21    ///
22    /// ```toml
23    /// array = [ 1 ]
24    /// ```
25    ///
26    /// does _not_ work.
27    ///
28    /// # Return value
29    ///
30    /// If the delete operation worked correctly, `Ok(Option<Value>)` is returned.
31    ///
32    /// The `Option<Value>` part is `None` if no value was actually removed as there was no value
33    /// there. For example, if you're deleting `table.a` and the Table `table` has no key `a`, then
34    /// `Ok(None)` is returned. Also, if you're deleting from an Array, but there is nothing in the
35    /// array, or the array is shorter than the index you're deleting.
36    /// If the delete operation actually removed something from the toml document, this value is
37    /// returned as `Ok(Some(Value))`.
38    ///
39    /// On failure, `Err(e)` is returned
40    ///
41    fn delete_with_seperator(&mut self, query: &str, sep: char) -> Result<Option<Value>>;
42
43    /// Extension function for deleting a value from the current toml::Value document
44    ///
45    /// See documentation of `TomlValueDeleteExt::delete_with_seperator`
46    fn delete(&mut self, query: &str) -> Result<Option<Value>> {
47        self.delete_with_seperator(query, '.')
48    }
49}
50
51impl TomlValueDeleteExt for Value {
52    fn delete_with_seperator(&mut self, query: &str, sep: char) -> Result<Option<Value>> {
53        use crate::resolver::mut_resolver::resolve;
54        use std::ops::Index;
55
56        let mut tokens = tokenize_with_seperator(query, sep)?;
57        let last_token = tokens.pop_last();
58
59        /// Check whether a structure (Table/Array) is empty. If the Value has not these types,
60        /// the default value is returned
61        #[inline]
62        fn is_empty(val: Option<&Value>, default: bool) -> bool {
63            val.map(|v| match v {
64                Value::Table(ref tab) => tab.is_empty(),
65                Value::Array(ref arr) => arr.is_empty(),
66                _ => default,
67            })
68            .unwrap_or(default)
69        }
70
71        #[inline]
72        fn is_table(val: Option<&Value>) -> bool {
73            val.map(|v| is_match!(v, &Value::Table(_))).unwrap_or(false)
74        }
75
76        #[inline]
77        fn is_array(val: Option<&Value>) -> bool {
78            val.map(|v| is_match!(v, &Value::Array(_))).unwrap_or(false)
79        }
80
81        #[inline]
82        fn name_of_val(val: Option<&Value>) -> &'static str {
83            val.map(crate::util::name_of_val).unwrap_or("None")
84        }
85
86        match last_token {
87            None => match self {
88                Value::Table(ref mut tab) => match tokens {
89                    Token::Identifier { ident, .. } => {
90                        if is_empty(tab.get(&ident), true) {
91                            Ok(tab.remove(&ident))
92                        } else if is_table(tab.get(&ident)) {
93                            Err(Error::CannotDeleteNonEmptyTable(Some(ident)))
94                        } else if is_array(tab.get(&ident)) {
95                            Err(Error::CannotDeleteNonEmptyArray(Some(ident)))
96                        } else {
97                            let act = name_of_val(tab.get(&ident));
98                            let tbl = "table";
99                            Err(Error::CannotAccessBecauseTypeMismatch(tbl, act))
100                        }
101                    }
102                    _ => Ok(None),
103                },
104                Value::Array(ref mut arr) => match tokens {
105                    Token::Identifier { ident, .. } => Err(Error::NoIdentifierInArray(ident)),
106                    Token::Index { idx, .. } => {
107                        if is_empty(Some(arr.index(idx)), true) {
108                            Ok(Some(arr.remove(idx)))
109                        } else if is_table(Some(arr.index(idx))) {
110                            Err(Error::CannotDeleteNonEmptyTable(None))
111                        } else if is_array(Some(arr.index(idx))) {
112                            Err(Error::CannotDeleteNonEmptyArray(None))
113                        } else {
114                            let act = name_of_val(Some(arr.index(idx)));
115                            let tbl = "table";
116                            Err(Error::CannotAccessBecauseTypeMismatch(tbl, act))
117                        }
118                    }
119                },
120                _ => {
121                    let kind = match tokens {
122                        Token::Identifier { ident, .. } => Error::QueryingValueAsTable(ident),
123                        Token::Index { idx, .. } => Error::QueryingValueAsArray(idx),
124                    };
125                    Err(kind)
126                }
127            },
128            Some(last_token) => {
129                let val = resolve(self, &tokens, true)?.unwrap(); // safe because of resolve() guarantees
130                match val {
131                    Value::Table(ref mut tab) => match *last_token {
132                        Token::Identifier { ref ident, .. } => {
133                            if is_empty(tab.get(ident), true) {
134                                Ok(tab.remove(ident))
135                            } else if is_table(tab.get(ident)) {
136                                Err(Error::CannotDeleteNonEmptyTable(Some(ident.clone())))
137                            } else if is_array(tab.get(ident)) {
138                                Err(Error::CannotDeleteNonEmptyArray(Some(ident.clone())))
139                            } else {
140                                let act = name_of_val(tab.get(ident));
141                                let tbl = "table";
142                                Err(Error::CannotAccessBecauseTypeMismatch(tbl, act))
143                            }
144                        }
145                        Token::Index { idx, .. } => Err(Error::NoIndexInTable(idx)),
146                    },
147                    Value::Array(ref mut arr) => match *last_token {
148                        Token::Identifier { ident, .. } => Err(Error::NoIdentifierInArray(ident)),
149                        Token::Index { idx, .. } => {
150                            if idx > arr.len() {
151                                return Err(Error::ArrayIndexOutOfBounds(idx, arr.len()));
152                            }
153                            if is_empty(Some(arr.index(idx)), true) {
154                                Ok(Some(arr.remove(idx)))
155                            } else if is_table(Some(arr.index(idx))) {
156                                Err(Error::CannotDeleteNonEmptyTable(None))
157                            } else if is_array(Some(arr.index(idx))) {
158                                Err(Error::CannotDeleteNonEmptyArray(None))
159                            } else {
160                                let act = name_of_val(Some(arr.index(idx)));
161                                let tbl = "table";
162                                Err(Error::CannotAccessBecauseTypeMismatch(tbl, act))
163                            }
164                        }
165                    },
166                    _ => {
167                        let kind = match *last_token {
168                            Token::Identifier { ident, .. } => Error::QueryingValueAsTable(ident),
169                            Token::Index { idx, .. } => Error::QueryingValueAsArray(idx),
170                        };
171                        Err(kind)
172                    }
173                }
174            }
175        }
176    }
177}
178
179#[cfg(test)]
180mod test {
181    use super::*;
182    use toml::from_str as toml_from_str;
183    use toml::Value;
184
185    #[test]
186    fn test_delete_from_empty_document() {
187        let mut toml: Value = toml_from_str("").unwrap();
188
189        let res = toml.delete_with_seperator(&String::from("a"), '.');
190
191        assert!(res.is_ok());
192
193        let res = res.unwrap();
194        assert!(res.is_none());
195    }
196
197    #[test]
198    fn test_delete_from_empty_table() {
199        let mut toml: Value = toml_from_str(
200            r#"
201        [table]
202        "#,
203        )
204        .unwrap();
205
206        let res = toml.delete_with_seperator(&String::from("table.a"), '.');
207
208        assert!(res.is_ok());
209
210        let res = res.unwrap();
211        assert!(res.is_none());
212    }
213
214    #[test]
215    fn test_delete_integer() {
216        let mut toml: Value = toml_from_str(
217            r#"
218        value = 1
219        "#,
220        )
221        .unwrap();
222
223        let res = toml.delete_with_seperator(&String::from("value"), '.');
224
225        assert!(res.is_ok());
226
227        let res = res.unwrap();
228        assert!(res.is_some());
229        let res = res.unwrap();
230        assert!(is_match!(res, Value::Integer(1)));
231    }
232
233    #[test]
234    fn test_delete_integer_removes_entry_from_document() {
235        let mut toml: Value = toml_from_str(
236            r#"
237        value = 1
238        "#,
239        )
240        .unwrap();
241
242        let res = toml.delete_with_seperator(&String::from("value"), '.');
243
244        assert!(res.is_ok());
245
246        let res = res.unwrap();
247        assert!(res.is_some());
248        let res = res.unwrap();
249        assert!(is_match!(res, Value::Integer(1)));
250
251        match toml {
252            Value::Table(tab) => assert!(tab.is_empty()),
253            _ => unreachable!("Strange things are happening"),
254        }
255    }
256
257    #[test]
258    fn test_delete_string() {
259        let mut toml: Value = toml_from_str(
260            r#"
261        value = "foo"
262        "#,
263        )
264        .unwrap();
265
266        let res = toml.delete_with_seperator(&String::from("value"), '.');
267
268        assert!(res.is_ok());
269
270        let res = res.unwrap();
271        assert!(res.is_some());
272        let res = res.unwrap();
273        assert!(is_match!(res, Value::String(_)));
274        match res {
275            Value::String(ref s) => assert_eq!("foo", s),
276            _ => panic!("What just happened?"),
277        }
278    }
279
280    #[test]
281    fn test_delete_string_removes_entry_from_document() {
282        let mut toml: Value = toml_from_str(
283            r#"
284        value = "foo"
285        "#,
286        )
287        .unwrap();
288
289        let res = toml.delete_with_seperator(&String::from("value"), '.');
290
291        assert!(res.is_ok());
292
293        let res = res.unwrap();
294        assert!(res.is_some());
295        let res = res.unwrap();
296        assert!(is_match!(res, Value::String(_)));
297        match res {
298            Value::String(ref s) => assert_eq!("foo", s),
299            _ => panic!("What just happened?"),
300        }
301
302        match toml {
303            Value::Table(tab) => assert!(tab.is_empty()),
304            _ => unreachable!("Strange things are happening"),
305        }
306    }
307
308    #[test]
309    fn test_delete_empty_table() {
310        let mut toml: Value = toml_from_str(
311            r#"
312        [table]
313        "#,
314        )
315        .unwrap();
316
317        let res = toml.delete_with_seperator(&String::from("table"), '.');
318
319        assert!(res.is_ok());
320
321        let res = res.unwrap();
322        assert!(res.is_some());
323        let res = res.unwrap();
324        assert!(is_match!(res, Value::Table(_)));
325        match res {
326            Value::Table(ref t) => assert!(t.is_empty()),
327            _ => panic!("What just happened?"),
328        }
329    }
330
331    #[test]
332    fn test_delete_empty_table_removes_entry_from_document() {
333        let mut toml: Value = toml_from_str(
334            r#"
335        [table]
336        "#,
337        )
338        .unwrap();
339
340        let res = toml.delete_with_seperator(&String::from("table"), '.');
341
342        assert!(res.is_ok());
343
344        let res = res.unwrap();
345        assert!(res.is_some());
346        let res = res.unwrap();
347        assert!(is_match!(res, Value::Table(_)));
348        match res {
349            Value::Table(ref t) => assert!(t.is_empty()),
350            _ => panic!("What just happened?"),
351        }
352
353        match toml {
354            Value::Table(tab) => assert!(tab.is_empty()),
355            _ => unreachable!("Strange things are happening"),
356        }
357    }
358
359    #[test]
360    fn test_delete_empty_array() {
361        let mut toml: Value = toml_from_str(
362            r#"
363        array = []
364        "#,
365        )
366        .unwrap();
367
368        let res = toml.delete_with_seperator(&String::from("array"), '.');
369
370        assert!(res.is_ok());
371
372        let res = res.unwrap();
373        assert!(res.is_some());
374        let res = res.unwrap();
375        assert!(is_match!(res, Value::Array(_)));
376        match res {
377            Value::Array(ref a) => assert!(a.is_empty()),
378            _ => panic!("What just happened?"),
379        }
380    }
381
382    #[test]
383    fn test_delete_empty_array_removes_entry_from_document() {
384        let mut toml: Value = toml_from_str(
385            r#"
386        array = []
387        "#,
388        )
389        .unwrap();
390
391        let res = toml.delete_with_seperator(&String::from("array"), '.');
392
393        assert!(res.is_ok());
394
395        let res = res.unwrap();
396        assert!(res.is_some());
397        let res = res.unwrap();
398        assert!(is_match!(res, Value::Array(_)));
399        match res {
400            Value::Array(ref a) => assert!(a.is_empty()),
401            _ => panic!("What just happened?"),
402        }
403
404        match toml {
405            Value::Table(tab) => assert!(tab.is_empty()),
406            _ => unreachable!("Strange things are happening"),
407        }
408    }
409
410    #[test]
411    fn test_delete_nonempty_table() {
412        let mut toml: Value = toml_from_str(
413            r#"
414        [table]
415        a = 1
416        "#,
417        )
418        .unwrap();
419
420        let res = toml.delete_with_seperator(&String::from("table"), '.');
421
422        assert!(res.is_err());
423
424        let res = res.unwrap_err();
425        assert!(is_match!(res, Error::CannotDeleteNonEmptyTable(_)));
426    }
427
428    #[test]
429    fn test_delete_nonempty_array() {
430        let mut toml: Value = toml_from_str(
431            r#"
432        array = [ 1 ]
433        "#,
434        )
435        .unwrap();
436
437        let res = toml.delete_with_seperator(&String::from("array"), '.');
438
439        assert!(res.is_err());
440
441        let res = res.unwrap_err();
442        assert!(is_match!(res, Error::CannotDeleteNonEmptyArray(_)));
443    }
444
445    #[test]
446    fn test_delete_int_from_table() {
447        let mut toml: Value = toml_from_str(
448            r#"
449        [table]
450        int = 1
451        "#,
452        )
453        .unwrap();
454
455        let res = toml.delete_with_seperator(&String::from("table.int"), '.');
456
457        assert!(res.is_ok());
458
459        let res = res.unwrap();
460        assert!(is_match!(res, Some(Value::Integer(1))));
461    }
462
463    #[test]
464    fn test_delete_array_from_table() {
465        let mut toml: Value = toml_from_str(
466            r#"
467        [table]
468        array = []
469        "#,
470        )
471        .unwrap();
472
473        let res = toml.delete_with_seperator(&String::from("table.array"), '.');
474
475        assert!(res.is_ok());
476
477        let res = res.unwrap();
478        assert!(is_match!(res, Some(Value::Array(_))));
479    }
480
481    #[test]
482    fn test_delete_int_from_array_from_table() {
483        let mut toml: Value = toml_from_str(
484            r#"
485        [table]
486        array = [ 1 ]
487        "#,
488        )
489        .unwrap();
490
491        let res = toml.delete_with_seperator(&String::from("table.array.[0]"), '.');
492
493        assert!(res.is_ok());
494
495        let res = res.unwrap();
496        assert!(is_match!(res, Some(Value::Integer(1))));
497    }
498
499    #[test]
500    fn test_delete_int_from_array() {
501        let mut toml: Value = toml_from_str(
502            r#"
503        array = [ 1 ]
504        "#,
505        )
506        .unwrap();
507
508        let res = toml.delete_with_seperator(&String::from("array.[0]"), '.');
509
510        assert!(res.is_ok());
511
512        let res = res.unwrap();
513        assert!(is_match!(res, Some(Value::Integer(1))));
514    }
515
516    #[test]
517    fn test_delete_int_from_table_from_array() {
518        let mut toml: Value = toml_from_str(
519            r#"
520        array = [ { table = { int = 1 } } ]
521        "#,
522        )
523        .unwrap();
524
525        let res = toml.delete_with_seperator(&String::from("array.[0].table.int"), '.');
526
527        assert!(res.is_ok());
528
529        let res = res.unwrap();
530        assert!(is_match!(res, Some(Value::Integer(1))));
531    }
532
533    #[test]
534    fn test_delete_from_array_value() {
535        use crate::read::TomlValueReadExt;
536
537        let mut toml: Value = toml_from_str(
538            r#"
539        array = [ 1 ]
540        "#,
541        )
542        .unwrap();
543
544        let ary = toml.read_mut(&String::from("array")).unwrap().unwrap();
545        let res = ary.delete_with_seperator(&String::from("[0]"), '.');
546
547        assert!(res.is_ok());
548
549        let res = res.unwrap();
550        assert!(is_match!(res, Some(Value::Integer(1))));
551    }
552
553    #[test]
554    fn test_delete_from_int_value() {
555        use crate::read::TomlValueReadExt;
556
557        let mut toml: Value = toml_from_str(
558            r#"
559        array = [ 1 ]
560        "#,
561        )
562        .unwrap();
563
564        let ary = toml.read_mut(&String::from("array.[0]")).unwrap().unwrap();
565        let res = ary.delete_with_seperator(&String::from("nonexist"), '.');
566
567        assert!(res.is_err());
568
569        let res = res.unwrap_err();
570        assert!(is_match!(res, Error::QueryingValueAsTable(_)));
571    }
572
573    #[test]
574    fn test_delete_index_from_non_array() {
575        use crate::read::TomlValueReadExt;
576
577        let mut toml: Value = toml_from_str(
578            r#"
579        array = 1
580        "#,
581        )
582        .unwrap();
583
584        let ary = toml.read_mut(&String::from("array")).unwrap().unwrap();
585        let res = ary.delete_with_seperator(&String::from("[0]"), '.');
586
587        assert!(res.is_err());
588
589        let res = res.unwrap_err();
590        assert!(is_match!(res, Error::QueryingValueAsArray(_)));
591    }
592
593    #[test]
594    fn test_delete_index_from_table_in_table() {
595        let mut toml: Value = toml_from_str(
596            r#"
597        table = { another = { int = 1 } }
598        "#,
599        )
600        .unwrap();
601
602        let res = toml.delete_with_seperator(&String::from("table.another.[0]"), '.');
603
604        assert!(res.is_err());
605
606        let res = res.unwrap_err();
607        assert!(is_match!(res, Error::NoIndexInTable(0)));
608    }
609
610    #[test]
611    fn test_delete_identifier_from_array_in_table() {
612        let mut toml: Value = toml_from_str(
613            r#"
614        table = { another = [ 1, 2, 3, 4, 5, 6 ] }
615        "#,
616        )
617        .unwrap();
618
619        let res = toml.delete_with_seperator(&String::from("table.another.nonexist"), '.');
620
621        assert!(res.is_err());
622
623        let res = res.unwrap_err();
624        assert!(is_match!(res, Error::NoIdentifierInArray(_)));
625    }
626
627    #[test]
628    fn test_delete_nonexistent_array_idx() {
629        let mut toml: Value = toml_from_str(
630            r#"
631        array = [ 1, 2, 3 ]
632        "#,
633        )
634        .unwrap();
635
636        let res = toml.delete_with_seperator(&String::from("array.[22]"), '.');
637
638        assert!(res.is_err());
639
640        let res = res.unwrap_err();
641        assert!(is_match!(res, Error::ArrayIndexOutOfBounds(22, 3)));
642    }
643
644    #[test]
645    fn test_delete_non_empty_array_from_array() {
646        let mut toml: Value = toml_from_str(
647            r#"
648        array = [ [ 1 ], [ 2 ] ]
649        "#,
650        )
651        .unwrap();
652
653        let res = toml.delete_with_seperator(&String::from("array.[1]"), '.');
654
655        assert!(res.is_err());
656
657        let res = res.unwrap_err();
658        assert!(is_match!(res, Error::CannotDeleteNonEmptyArray(None)));
659    }
660
661    #[test]
662    fn test_delete_non_empty_table_from_array() {
663        let mut toml: Value = toml_from_str(
664            r#"
665        array = [ { t = 1 }, { t = 2 } ]
666        "#,
667        )
668        .unwrap();
669
670        let res = toml.delete_with_seperator(&String::from("array.[1]"), '.');
671
672        assert!(res.is_err());
673
674        let res = res.unwrap_err();
675        assert!(is_match!(res, Error::CannotDeleteNonEmptyTable(None)));
676    }
677
678    #[test]
679    fn test_delete_non_empty_table_from_top_level_array() {
680        use crate::read::TomlValueReadExt;
681
682        let mut toml: Value = toml_from_str(
683            r#"
684        array = [ { t = 1 }, { t = 2 } ]
685        "#,
686        )
687        .unwrap();
688
689        let ary = toml.read_mut(&String::from("array")).unwrap().unwrap();
690        let res = ary.delete_with_seperator(&String::from("[1]"), '.');
691
692        assert!(res.is_err());
693
694        let res = res.unwrap_err();
695        assert!(is_match!(res, Error::CannotDeleteNonEmptyTable(None)));
696    }
697
698    #[test]
699    fn test_delete_from_value_like_it_was_table() {
700        let mut toml: Value = toml_from_str(
701            r#"
702        val = 5
703        "#,
704        )
705        .unwrap();
706
707        let res = toml.delete_with_seperator(&String::from("val.foo"), '.');
708
709        assert!(res.is_err());
710
711        let res = res.unwrap_err();
712        assert!(is_match!(res, Error::QueryingValueAsTable(_)));
713    }
714
715    #[test]
716    fn test_delete_from_value_like_it_was_array() {
717        let mut toml: Value = toml_from_str(
718            r#"
719        val = 5
720        "#,
721        )
722        .unwrap();
723
724        let res = toml.delete_with_seperator(&String::from("val.[0]"), '.');
725
726        assert!(res.is_err());
727
728        let res = res.unwrap_err();
729        assert!(is_match!(res, Error::QueryingValueAsArray(0)));
730    }
731}