1use crate::path::OwnedSegment;
4use crate::path::OwnedValuePath;
5use crate::value::Kind;
6use crate::value::kind::collection::{CollectionRemove, EmptyState};
7use crate::value::kind::{Collection, Field};
8
9impl Kind {
10 #[allow(clippy::return_self_not_must_use)] pub fn remove(&mut self, path: &OwnedValuePath, prune: bool) -> Self {
14 let removed_type = self.get(path);
15
16 let segments = &path.segments;
17 if segments.is_empty() {
18 let mut new_kind = Self::never();
19 if self.contains_object() {
20 new_kind.add_object(Collection::empty());
21 }
22 if self.contains_array() {
23 new_kind.add_array(Collection::empty());
24 }
25 if self.contains_primitive() {
26 new_kind.add_null();
28 }
29 *self = new_kind;
30 } else {
31 let _compact_options = self.remove_inner(segments, prune);
32 }
33 removed_type
34 }
35
36 #[allow(clippy::too_many_lines)]
37 fn remove_inner(&mut self, segments: &[OwnedSegment], compact: bool) -> CompactOptions {
38 if self.is_never() {
39 *self = Self::never();
42 return CompactOptions::Never;
43 }
44
45 if let Some(first) = segments.first() {
46 match first {
47 OwnedSegment::Field(field) => {
48 let mut at_path_kind = self.at_path(segments);
49
50 self.as_object_mut()
51 .map_or(CompactOptions::Never, |object| {
52 object
54 .known_mut()
55 .get_mut(&Field::from(field.clone()))
56 .unwrap_or(&mut at_path_kind)
57 .remove_inner(&segments[1..], compact)
58 .compact(object, field.clone(), compact)
59 })
60 }
61
62 OwnedSegment::Index(index) => {
63 let mut at_path_kind = self.at_path(segments);
64 if let Some(array) = self.as_array_mut() {
65 let mut index = *index;
66 if index < 0 {
67 let negative_index = (-index) as usize;
68
69 if array.unknown_kind().contains_any_defined() {
70 let original = array.clone();
71 *array = original.clone();
72
73 let min_index = array
74 .largest_known_index()
75 .map_or(0, |x| x + 1 - negative_index);
76
77 if let Some(largest_known_index) = array.largest_known_index() {
78 for i in min_index..=largest_known_index {
79 let mut single_remove = original.clone();
80 if let Some(child) =
81 single_remove.known_mut().get_mut(&i.into())
82 {
83 child.remove_inner(&segments[1..], compact).compact(
84 &mut single_remove,
85 i,
86 compact,
87 );
88 }
89 array.merge(single_remove, false);
90 }
91 }
92 return if array.min_length() <= 1 {
93 CompactOptions::Maybe
94 } else {
95 CompactOptions::Never
96 };
97 } else if let Some(positive_index) = array.get_positive_index(index) {
98 index = positive_index as isize;
99 } else {
100 return CompactOptions::from(EmptyState::Never);
102 }
103 }
104
105 array
107 .known_mut()
108 .get_mut(&(index as usize).into())
109 .unwrap_or(&mut at_path_kind)
110 .remove_inner(&segments[1..], compact)
111 .compact(array, index as usize, compact)
112 } else {
113 CompactOptions::Never
115 }
116 }
117 }
118 } else {
119 CompactOptions::new(self.contains_any_defined(), self.contains_undefined())
120 }
121 }
122}
123
124#[derive(Debug, Copy, Clone, PartialEq, Eq)]
127enum CompactOptions {
128 Always,
130 Maybe,
132 Never,
134}
135
136impl CompactOptions {
137 fn new(compact: bool, dont_compact: bool) -> Self {
138 match (compact, dont_compact) {
139 (true, false) => Self::Always,
140 (false, true) => Self::Never,
141 (true, true) => Self::Maybe,
142 (false, false) => unreachable!("Invalid CompactOptions"),
143 }
144 }
145
146 fn compact<T>(
147 self,
148 collection: &mut Collection<T>,
149 key: impl Into<T>,
150 continue_compact: bool,
151 ) -> Self
152 where
153 T: Ord + Clone + std::fmt::Debug,
154 Collection<T>: CollectionRemove<Key = T>,
155 {
156 let key = &key.into();
157
158 match self {
159 Self::Always => collection.remove_known(key),
160 Self::Maybe => {
161 let not_compacted = collection.clone();
162 collection.remove_known(key);
163 collection.merge(not_compacted, false);
164 }
165 Self::Never => {
166 }
168 }
169
170 Self::from(collection.is_empty())
171 .disable_should_compact(!self.should_compact())
172 .disable_should_compact(!continue_compact)
173 }
174
175 #[allow(clippy::trivially_copy_pass_by_ref)]
176 fn should_compact(&self) -> bool {
177 match self {
178 Self::Always | Self::Maybe => true,
179 Self::Never => false,
180 }
181 }
182
183 fn disable_should_compact(self, value: bool) -> Self {
185 if value { Self::Never } else { self }
186 }
187}
188
189impl From<EmptyState> for CompactOptions {
190 fn from(state: EmptyState) -> Self {
191 match state {
192 EmptyState::Never => Self::Never,
193 EmptyState::Maybe => Self::Maybe,
194 EmptyState::Always => Self::Always,
195 }
196 }
197}
198
199#[cfg(test)]
200mod test {
201 use super::*;
202 use crate::owned_value_path;
203 use std::collections::BTreeMap;
204
205 #[test]
206 #[allow(clippy::too_many_lines)]
207 fn test_remove() {
208 struct TestCase {
209 kind: Kind,
210 path: OwnedValuePath,
211 compact: bool,
212 want: Kind,
213 return_value: Kind,
214 }
215
216 for (
217 title,
218 TestCase {
219 kind,
220 path,
221 compact,
222 want,
223 return_value,
224 },
225 ) in [
226 (
227 "remove integer root",
228 TestCase {
229 kind: Kind::integer(),
230 path: owned_value_path!(),
231 compact: false,
232 want: Kind::null(),
233 return_value: Kind::integer(),
234 },
235 ),
236 (
237 "remove array root",
238 TestCase {
239 kind: Kind::array(Collection::from(BTreeMap::from([(
240 0.into(),
241 Kind::integer(),
242 )]))),
243 path: owned_value_path!(),
244 compact: false,
245 want: Kind::array(Collection::empty()),
246 return_value: Kind::array(Collection::from(BTreeMap::from([(
247 0.into(),
248 Kind::integer(),
249 )]))),
250 },
251 ),
252 (
253 "remove object root",
254 TestCase {
255 kind: Kind::object(Collection::from(BTreeMap::from([(
256 "a".into(),
257 Kind::integer(),
258 )]))),
259 path: owned_value_path!(),
260 compact: false,
261 want: Kind::object(Collection::empty()),
262 return_value: Kind::object(Collection::from(BTreeMap::from([(
263 "a".into(),
264 Kind::integer(),
265 )]))),
266 },
267 ),
268 (
269 "remove object field",
270 TestCase {
271 kind: Kind::object(Collection::from(BTreeMap::from([(
272 "a".into(),
273 Kind::integer(),
274 )]))),
275 path: owned_value_path!("a"),
276 compact: false,
277 want: Kind::object(Collection::empty()),
278 return_value: Kind::integer(),
279 },
280 ),
281 (
282 "remove nested object field",
283 TestCase {
284 kind: Kind::object(Collection::from(BTreeMap::from([(
285 "a".into(),
286 Kind::object(Collection::from(BTreeMap::from([(
287 "b".into(),
288 Kind::integer(),
289 )]))),
290 )]))),
291 path: owned_value_path!("a", "b"),
292 compact: false,
293 want: Kind::object(Collection::from(BTreeMap::from([(
294 "a".into(),
295 Kind::object(Collection::empty()),
296 )]))),
297 return_value: Kind::integer(),
298 },
299 ),
300 (
301 "remove nested object field: compact",
302 TestCase {
303 kind: Kind::object(Collection::from(BTreeMap::from([(
304 "a".into(),
305 Kind::object(Collection::from(BTreeMap::from([(
306 "b".into(),
307 Kind::integer(),
308 )]))),
309 )]))),
310 path: owned_value_path!("a", "b"),
311 compact: true,
312 want: Kind::object(Collection::empty()),
313 return_value: Kind::integer(),
314 },
315 ),
316 (
317 "remove object unknown field",
318 TestCase {
319 kind: Kind::object(
320 Collection::from(BTreeMap::from([("a".into(), Kind::integer())]))
321 .with_unknown(Kind::float()),
322 ),
323 path: owned_value_path!("b"),
324 compact: false,
325 want: Kind::object(
326 Collection::from(BTreeMap::from([("a".into(), Kind::integer())]))
327 .with_unknown(Kind::float()),
328 ),
329 return_value: Kind::float().or_null(),
330 },
331 ),
332 (
333 "remove object unknown field: compact",
334 TestCase {
335 kind: Kind::object(
336 Collection::from(BTreeMap::from([("a".into(), Kind::integer())]))
337 .with_unknown(Kind::float()),
338 ),
339 path: owned_value_path!("b"),
340 compact: true,
341 want: Kind::object(
342 Collection::from(BTreeMap::from([("a".into(), Kind::integer())]))
343 .with_unknown(Kind::float()),
344 ),
345 return_value: Kind::float().or_null(),
346 },
347 ),
348 (
349 "remove nested object field: maybe compact",
350 TestCase {
351 kind: Kind::object(Collection::from(BTreeMap::from([(
352 "a".into(),
353 Kind::object(Collection::empty().with_unknown(Kind::float())),
354 )]))),
355 path: owned_value_path!("a", "b"),
356 compact: true,
357 want: Kind::object(Collection::from(BTreeMap::from([(
358 "a".into(),
359 Kind::object(Collection::empty().with_unknown(Kind::float()))
360 .or_undefined(),
361 )]))),
362 return_value: Kind::float().or_null(),
363 },
364 ),
365 (
366 "remove unknown object field 2",
367 TestCase {
368 kind: Kind::object(Collection::empty().with_unknown(Kind::integer())),
369 path: owned_value_path!("a", "b"),
370 compact: false,
371 want: Kind::object(Collection::empty().with_unknown(Kind::integer())),
372 return_value: Kind::null(),
373 },
374 ),
375 (
376 "remove deep nested unknown object field",
377 TestCase {
378 kind: Kind::object(Collection::from(BTreeMap::from([(
379 "a".into(),
380 Kind::object(Collection::empty().with_unknown(Kind::any_object())),
381 )]))),
382 path: owned_value_path!("a", "b", "c"),
383 compact: false,
384 want: Kind::object(Collection::from(BTreeMap::from([(
385 "a".into(),
386 Kind::object(Collection::empty().with_unknown(Kind::any_object())),
387 )]))),
388 return_value: Kind::any().without_undefined(),
389 },
390 ),
391 (
392 "remove deep nested unknown object field: compact",
393 TestCase {
394 kind: Kind::object(Collection::from(BTreeMap::from([(
395 "a".into(),
396 Kind::object(Collection::empty().with_unknown(Kind::any_object())),
397 )]))),
398 path: owned_value_path!("a", "b", "c"),
399 compact: true,
400 want: Kind::object(Collection::from(BTreeMap::from([(
401 "a".into(),
402 Kind::object(Collection::empty().with_unknown(Kind::any_object()))
403 .or_undefined(),
404 )]))),
405 return_value: Kind::any().without_undefined(),
406 },
407 ),
408 (
409 "remove field from non object",
410 TestCase {
411 kind: Kind::integer(),
412 path: owned_value_path!("a", "b", "c"),
413 compact: true,
414 want: Kind::integer(),
415 return_value: Kind::null(),
416 },
417 ),
418 (
419 "remove index from non array",
420 TestCase {
421 kind: Kind::integer(),
422 path: owned_value_path!(1, 2, 3),
423 compact: true,
424 want: Kind::integer(),
425 return_value: Kind::null(),
426 },
427 ),
428 (
429 "remove known index 0",
430 TestCase {
431 kind: Kind::array(Collection::from(BTreeMap::from([(
432 0.into(),
433 Kind::integer(),
434 )]))),
435 path: owned_value_path!(0),
436 compact: true,
437 want: Kind::array(Collection::empty()),
438 return_value: Kind::integer(),
439 },
440 ),
441 (
442 "remove known index 0, shift elements",
443 TestCase {
444 kind: Kind::array(Collection::from(BTreeMap::from([
445 (0.into(), Kind::integer()),
446 (1.into(), Kind::float()),
447 ]))),
448 path: owned_value_path!(0),
449 compact: true,
450 want: Kind::array(Collection::from(BTreeMap::from([(
451 0.into(),
452 Kind::float(),
453 )]))),
454 return_value: Kind::integer(),
455 },
456 ),
457 (
458 "remove known index 1, shift elements",
459 TestCase {
460 kind: Kind::array(Collection::from(BTreeMap::from([
461 (0.into(), Kind::integer()),
462 (1.into(), Kind::float()),
463 (2.into(), Kind::bytes()),
464 ]))),
465 path: owned_value_path!(1),
466 compact: true,
467 want: Kind::array(Collection::from(BTreeMap::from([
468 (0.into(), Kind::integer()),
469 (1.into(), Kind::bytes()),
470 ]))),
471 return_value: Kind::float(),
472 },
473 ),
474 (
475 "remove field from non-object",
476 TestCase {
477 kind: Kind::integer(),
478 path: owned_value_path!("a"),
479 compact: false,
480 want: Kind::integer(),
481 return_value: Kind::null(),
482 },
483 ),
484 (
485 "remove index from non-array",
486 TestCase {
487 kind: Kind::integer(),
488 path: owned_value_path!(0),
489 compact: false,
490 want: Kind::integer(),
491 return_value: Kind::null(),
492 },
493 ),
494 (
495 "remove index -1",
496 TestCase {
497 kind: Kind::array(Collection::from(BTreeMap::from([(
498 0.into(),
499 Kind::integer(),
500 )]))),
501 path: owned_value_path!(-1),
502 compact: false,
503 want: Kind::array(Collection::empty()),
504 return_value: Kind::integer(),
505 },
506 ),
507 (
508 "remove index -2",
509 TestCase {
510 kind: Kind::array(Collection::from(BTreeMap::from([
511 (0.into(), Kind::integer()),
512 (1.into(), Kind::float()),
513 ]))),
514 path: owned_value_path!(-2),
515 compact: false,
516 want: Kind::array(Collection::from(BTreeMap::from([(
517 0.into(),
518 Kind::float(),
519 )]))),
520 return_value: Kind::integer(),
521 },
522 ),
523 (
524 "remove negative index non-existing element",
525 TestCase {
526 kind: Kind::array(Collection::from(BTreeMap::from([(
527 0.into(),
528 Kind::integer(),
529 )]))),
530 path: owned_value_path!(-2),
531 compact: false,
532 want: Kind::array(Collection::from(BTreeMap::from([(
533 0.into(),
534 Kind::integer(),
535 )]))),
536 return_value: Kind::null(),
537 },
538 ),
539 (
540 "remove negative index empty array",
541 TestCase {
542 kind: Kind::array(Collection::empty()),
543 path: owned_value_path!(-1),
544 compact: false,
545 want: Kind::array(Collection::empty()),
546 return_value: Kind::null(),
547 },
548 ),
549 (
550 "remove negative index with unknown",
551 TestCase {
552 kind: Kind::array(Collection::empty().with_unknown(Kind::integer())),
553 path: owned_value_path!(-1),
554 compact: false,
555 want: Kind::array(Collection::empty().with_unknown(Kind::integer())),
556 return_value: Kind::integer().or_null(),
557 },
558 ),
559 (
560 "remove negative index with unknown 2",
561 TestCase {
562 kind: Kind::array(
563 Collection::from(BTreeMap::from([(0.into(), Kind::float())]))
564 .with_unknown(Kind::integer()),
565 ),
566 path: owned_value_path!(-1),
567 compact: false,
568 want: Kind::array(
569 Collection::from(BTreeMap::from([(
570 0.into(),
571 Kind::float().or_integer().or_undefined(),
572 )]))
573 .with_unknown(Kind::integer()),
574 ),
575 return_value: Kind::integer().or_float(),
576 },
577 ),
578 (
579 "remove negative index with unknown 3",
580 TestCase {
581 kind: Kind::array(
582 Collection::from(BTreeMap::from([
583 (0.into(), Kind::float()),
584 (1.into(), Kind::bytes()),
585 ]))
586 .with_unknown(Kind::integer()),
587 ),
588 path: owned_value_path!(-1),
589 compact: false,
590 want: Kind::array(
591 Collection::from(BTreeMap::from([
592 (0.into(), Kind::float()),
593 (1.into(), Kind::bytes().or_integer().or_undefined()),
594 ]))
595 .with_unknown(Kind::integer()),
596 ),
597 return_value: Kind::integer().or_bytes(),
598 },
599 ),
600 (
601 "remove nested index",
602 TestCase {
603 kind: Kind::array(Collection::from(BTreeMap::from([
604 (
605 0.into(),
606 Kind::array(Collection::from(BTreeMap::from([
607 (0.into(), Kind::float()),
608 (1.into(), Kind::integer()),
609 ]))),
610 ),
611 (1.into(), Kind::bytes()),
612 ]))),
613 path: owned_value_path!(0, 0),
614 compact: false,
615 want: Kind::array(Collection::from(BTreeMap::from([
616 (
617 0.into(),
618 Kind::array(Collection::from(BTreeMap::from([(
619 0.into(),
620 Kind::integer(),
621 )]))),
622 ),
623 (1.into(), Kind::bytes()),
624 ]))),
625 return_value: Kind::float(),
626 },
627 ),
628 (
629 "remove nested index, compact",
630 TestCase {
631 kind: Kind::array(Collection::from(BTreeMap::from([
632 (
633 0.into(),
634 Kind::array(Collection::from(BTreeMap::from([(
635 0.into(),
636 Kind::float(),
637 )]))),
638 ),
639 (1.into(), Kind::bytes()),
640 ]))),
641 path: owned_value_path!(0, 0),
642 compact: true,
643 want: Kind::array(Collection::from(BTreeMap::from([(
644 0.into(),
645 Kind::bytes(),
646 )]))),
647 return_value: Kind::float(),
648 },
649 ),
650 (
651 "remove nested index, maybe compact",
652 TestCase {
653 kind: Kind::array(Collection::from(BTreeMap::from([
654 (
655 0.into(),
656 Kind::array(
657 Collection::from(BTreeMap::from([(0.into(), Kind::float())]))
658 .with_unknown(Kind::regex()),
659 ),
660 ),
661 (1.into(), Kind::bytes()),
662 ]))),
663 path: owned_value_path!(0, 0),
664 compact: true,
665 want: Kind::array(Collection::from(BTreeMap::from([
666 (
667 0.into(),
668 Kind::array(Collection::empty().with_unknown(Kind::regex())).or_bytes(),
669 ),
670 (1.into(), Kind::bytes().or_undefined()),
671 ]))),
672 return_value: Kind::float(),
673 },
674 ),
675 (
676 "remove nested index, maybe compact",
677 TestCase {
678 kind: Kind::array(Collection::from(BTreeMap::from([
679 (
680 0.into(),
681 Kind::array(Collection::empty().with_unknown(Kind::any())),
682 ),
683 (1.into(), Kind::bytes()),
684 ]))),
685 path: owned_value_path!(0, 0, 0),
686 compact: true,
687 want: Kind::array(Collection::from(BTreeMap::from([
688 (
689 0.into(),
690 Kind::array(Collection::empty().with_unknown(Kind::any())).or_bytes(),
691 ),
692 (1.into(), Kind::bytes().or_undefined()),
693 ]))),
694 return_value: Kind::any().without_undefined(),
695 },
696 ),
697 (
698 "remove nested negative index, compact",
699 TestCase {
700 kind: Kind::array(Collection::from(BTreeMap::from([
701 (
702 0.into(),
703 Kind::array(Collection::from(BTreeMap::from([(
704 0.into(),
705 Kind::integer(),
706 )]))),
707 ),
708 (1.into(), Kind::bytes()),
709 ]))),
710 path: owned_value_path!(-2, 0),
711 compact: true,
712 want: Kind::array(Collection::from(BTreeMap::from([(
713 0.into(),
714 Kind::bytes(),
715 )]))),
716 return_value: Kind::integer(),
717 },
718 ),
719 (
720 "remove nested negative unknown index",
721 TestCase {
722 kind: Kind::array(
723 Collection::from(BTreeMap::from([(
724 0.into(),
725 Kind::array(Collection::from(BTreeMap::from([(
726 0.into(),
727 Kind::integer(),
728 )]))),
729 )]))
730 .with_unknown(Kind::float()),
731 ),
732 path: owned_value_path!(-1, 0),
733 compact: false,
734 want: Kind::array(
735 Collection::from(BTreeMap::from([(
736 0.into(),
737 Kind::array(Collection::from(BTreeMap::from([(
738 0.into(),
739 Kind::integer().or_undefined(),
740 )]))),
741 )]))
742 .with_unknown(Kind::float()),
743 ),
744 return_value: Kind::integer().or_null(),
745 },
746 ),
747 (
748 "remove nested negative unknown index - empty array",
749 TestCase {
750 kind: Kind::array(Collection::empty().with_unknown(Kind::float())),
751 path: owned_value_path!(-1, 0),
752 compact: false,
753 want: Kind::array(Collection::empty().with_unknown(Kind::float())),
754 return_value: Kind::null(),
755 },
756 ),
757 ] {
758 let mut actual = kind;
759 let actual_return_value = actual.remove(&path, compact);
760 assert_eq!(
761 actual,
762 want,
763 "Test failed - return value: {:#?}.\nExpected = {:#?}\nActual = {:#?}",
764 title,
765 actual.debug_info(),
766 want.debug_info()
767 );
768 assert_eq!(
769 return_value,
770 actual_return_value,
771 "Test failed - return value: {:#?}.\nExpected = {:#?}\nActual = {:#?}",
772 title,
773 return_value.debug_info(),
774 actual_return_value.debug_info()
775 );
776 }
777 }
778}