1use toml::Value;
9
10use crate::error::{Error, Result};
11use crate::tokenizer::tokenize_with_seperator;
12use crate::tokenizer::Token;
13
14pub trait TomlValueDeleteExt {
15 fn delete_with_seperator(&mut self, query: &str, sep: char) -> Result<Option<Value>>;
42
43 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 #[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(); 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}