1use toml::Value;
3
4use crate::error::{Error, Result};
5use crate::tokenizer::tokenize_with_seperator;
6use crate::tokenizer::Token;
7
8pub trait TomlValueDeleteExt {
9 fn delete_with_seperator(&mut self, query: &str, sep: char) -> Result<Option<Value>>;
36
37 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 #[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(); 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}