toml_query/
delete.rs

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