vrl/value/kind/crud/
remove.rs

1//! All code related to removing from a [`Kind`].
2
3use 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    /// Removes the `Kind` at the given `path` within `self`.
11    /// This has the same behavior as `Value::remove`.
12    #[allow(clippy::return_self_not_must_use)] // It is fine to ignore the output here
13    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                // non-collection types are set to null when deleted at the root
27                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            // If `self` is `never`, the program would have already terminated
40            // so this removal can't happen.
41            *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                            // The modified value is discarded here (It's not needed)
53                            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                                // Removing a non-existing index
101                                return CompactOptions::from(EmptyState::Never);
102                            }
103                        }
104
105                        // The modified value is discarded here (It's not needed)
106                        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                        // guaranteed to not delete anything
114                        CompactOptions::Never
115                    }
116                }
117            }
118        } else {
119            CompactOptions::new(self.contains_any_defined(), self.contains_undefined())
120        }
121    }
122}
123
124/// A type definition might not know for sure if compaction will occur or not, so this
125/// keeps track of the current state
126#[derive(Debug, Copy, Clone, PartialEq, Eq)]
127enum CompactOptions {
128    /// Compaction will always happen.
129    Always,
130    /// Compaction may or may not happen. Both possibilities should be merged together.
131    Maybe,
132    /// Compaction will never happen.
133    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                // do nothing, already correct}
167            }
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    /// If the value is true, the `should_compact` option is set to false.
184    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}