vrl/value/kind/crud/
insert.rs

1//! All types related to inserting one [`Kind`] into another.
2
3use crate::path::{BorrowedSegment, ValuePath};
4use crate::value::Kind;
5use crate::value::kind::Collection;
6
7impl Kind {
8    /// Insert the `Kind` at the given `path` within `self`.
9    /// This has the same behavior as `Value::insert`.
10    #[allow(clippy::needless_pass_by_value)] // only reference types implement Path
11    pub fn insert<'a>(&mut self, path: impl ValuePath<'a>, kind: Self) {
12        self.insert_recursive(path.segment_iter(), kind.upgrade_undefined());
13    }
14
15    /// Set the `Kind` at the given `path` within `self`.
16    /// There is a subtle difference
17    /// between this and `Kind::insert` where this function does _not_ convert undefined to null.
18    #[allow(clippy::needless_pass_by_value)] // only reference types implement Path
19    pub fn set_at_path<'a>(&mut self, path: impl ValuePath<'a>, kind: Self) {
20        self.insert_recursive(path.segment_iter(), kind);
21    }
22
23    /// Insert the `Kind` at the given `path` within `self`.
24    /// This has the same behavior as `Value::insert`.
25    ///
26    /// # Panics
27    /// Object/Array not present in `self`.
28    #[allow(clippy::too_many_lines)]
29    #[allow(clippy::needless_pass_by_value)] // only reference types implement Path
30    pub fn insert_recursive<'a, 'b>(
31        &'a mut self,
32        mut iter: impl Iterator<Item = BorrowedSegment<'b>> + Clone,
33        kind: Self,
34    ) {
35        if kind.is_never() {
36            // If `kind` is `never`, the program would have already terminated
37            // so this assignment can't happen.
38            return;
39        }
40
41        if let Some(segment) = iter.next() {
42            match segment {
43                BorrowedSegment::Field(field) => {
44                    // Field insertion converts the value to an object, so remove all other types.
45                    *self = Self::object(self.object.clone().unwrap_or_else(Collection::empty));
46
47                    let collection = self.object.as_mut().expect("object was just inserted");
48                    let unknown_kind = collection.unknown_kind();
49
50                    collection
51                        .known_mut()
52                        .entry(field.into_owned().into())
53                        .or_insert(unknown_kind)
54                        .insert_recursive(iter, kind);
55                }
56                BorrowedSegment::Index(mut index) => {
57                    // Array insertion converts the value to an array, so remove all other types.
58                    *self = Self::array(self.array.clone().unwrap_or_else(Collection::empty));
59                    let collection = self.array.as_mut().expect("array was just inserted");
60
61                    if index < 0 {
62                        let largest_known_index = collection.largest_known_index();
63                        // The minimum size of the resulting array.
64                        let len_required = -index as usize;
65
66                        let unknown_kind = collection.unknown_kind();
67                        if unknown_kind.contains_any_defined() {
68                            // The array may be larger, but this is the largest we can prove the array is from the type information.
69                            let min_length = collection.min_length();
70
71                            if len_required > min_length {
72                                // We can't prove the array is large enough, so "holes" may be created
73                                // which set the value to null.
74                                // Holes are inserted to the front, which shifts everything to the right.
75                                // We don't know the exact number of holes/shifts, but can determine an upper bound.
76                                let max_shifts = len_required - min_length;
77
78                                // The number of possible shifts is 0 ..= max_shifts.
79                                // Each shift will be calculated independently and merged into the collection.
80                                // A shift of 0 is the original collection, so that is skipped.
81                                let zero_shifts = collection.clone();
82                                for shift_count in 1..=max_shifts {
83                                    let mut shifted_collection = zero_shifts.clone();
84                                    // Clear all known values and replace with new ones. (in-place shift can overwrite).
85                                    shifted_collection.known_mut().clear();
86
87                                    // Add the "null" from holes.
88                                    for i in 1..shift_count {
89                                        shifted_collection
90                                            .known_mut()
91                                            .insert(i.into(), Self::null());
92                                    }
93
94                                    // Shift known values by the exact "shift_count".
95                                    for (i, i_kind) in zero_shifts.known() {
96                                        shifted_collection
97                                            .known_mut()
98                                            .insert(*i + shift_count, i_kind.clone());
99                                    }
100
101                                    // Add this shift count as another possible type definition.
102                                    collection.merge(shifted_collection, false);
103                                }
104                            }
105
106                            // We can prove the positive index won't be less than "min_index".
107                            let min_index = (min_length as isize + index).max(0) as usize;
108
109                            // Sanity check: if holes are added to the type, min_index must be 0.
110                            debug_assert!(min_index == 0 || min_length >= len_required);
111
112                            // Apply the current "unknown" to indices that don't have an explicit known
113                            // since the "unknown" is about to change.
114                            for i in 0..len_required {
115                                collection
116                                    .known_mut()
117                                    .entry(i.into())
118                                    .or_insert_with(|| unknown_kind.clone())
119                                    // These indices are guaranteed to exist, so they can't be undefined.
120                                    .remove_undefined();
121                            }
122                            for (i, i_kind) in collection.known_mut() {
123                                // This index might be set by the insertion. Add the insertion type to the existing type.
124                                if i.to_usize() >= min_index {
125                                    let mut kind_with_insertion = i_kind.clone();
126                                    let remaining_path_segments = iter.clone().collect::<Vec<_>>();
127                                    kind_with_insertion
128                                        .insert(&remaining_path_segments, kind.clone());
129                                    *i_kind = i_kind.union(kind_with_insertion);
130                                }
131                            }
132
133                            let mut unknown_kind_with_insertion = unknown_kind.clone();
134                            let remaining_path_segments = iter.clone().collect::<Vec<_>>();
135                            unknown_kind_with_insertion.insert(&remaining_path_segments, kind);
136                            let mut new_unknown_kind = unknown_kind;
137                            new_unknown_kind.merge_keep(unknown_kind_with_insertion, false);
138                            collection.set_unknown(new_unknown_kind);
139
140                            return;
141                        }
142                        debug_assert!(
143                            collection.unknown_kind().is_undefined(),
144                            "all cases with an unknown have been handled"
145                        );
146
147                        // If there is no unknown, the exact position of the negative index can be determined.
148                        let exact_array_len =
149                            largest_known_index.map_or(0, |max_index| max_index + 1);
150
151                        if len_required > exact_array_len {
152                            // Fill in holes from extending to fit a negative index.
153                            for i in exact_array_len..len_required {
154                                // There is no unknown, so the exact type "null" can be inserted.
155                                collection.known_mut().insert(i.into(), Self::null());
156                            }
157                        }
158                        index += (len_required as isize).max(exact_array_len as isize);
159                    }
160
161                    debug_assert!(index >= 0, "all negative cases have been handled");
162                    let index = index as usize;
163
164                    let index_exists = collection.known().contains_key(&index.into());
165                    if !index_exists {
166                        // Add "null" to all holes, adding it to the "unknown" if it exists.
167                        // Holes can never be undefined.
168                        let hole_type = collection.unknown_kind().without_undefined().or_null();
169
170                        for i in 0..index {
171                            collection
172                                .known_mut()
173                                .entry(i.into())
174                                .or_insert_with(|| hole_type.clone());
175                        }
176                    }
177
178                    let unknown_kind = collection.unknown_kind();
179                    collection
180                        .known_mut()
181                        .entry(index.into())
182                        .or_insert(unknown_kind)
183                        .insert_recursive(iter, kind);
184                }
185                BorrowedSegment::Invalid => { /* An invalid path does nothing. */ }
186            }
187        } else {
188            *self = kind;
189        }
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use std::collections::BTreeMap;
196
197    use crate::owned_value_path;
198    use crate::path::{OwnedValuePath, parse_value_path};
199    use crate::value::kind::Collection;
200
201    use super::*;
202
203    #[test]
204    #[allow(clippy::too_many_lines)]
205    fn test_insert() {
206        struct TestCase {
207            this: Kind,
208            path: OwnedValuePath,
209            kind: Kind,
210            expected: Kind,
211        }
212
213        for (
214            title,
215            TestCase {
216                mut this,
217                path,
218                kind,
219                expected,
220            },
221        ) in [
222            (
223                "root insert",
224                TestCase {
225                    this: Kind::bytes(),
226                    path: owned_value_path!(),
227                    kind: Kind::integer(),
228                    expected: Kind::integer(),
229                },
230            ),
231            (
232                "root insert object",
233                TestCase {
234                    this: Kind::bytes(),
235                    path: owned_value_path!(),
236                    kind: Kind::object(BTreeMap::from([("a".into(), Kind::integer())])),
237                    expected: Kind::object(BTreeMap::from([("a".into(), Kind::integer())])),
238                },
239            ),
240            (
241                "empty object insert field",
242                TestCase {
243                    this: Kind::object(Collection::empty()),
244                    path: owned_value_path!("a"),
245                    kind: Kind::integer(),
246                    expected: Kind::object(BTreeMap::from([("a".into(), Kind::integer())])),
247                },
248            ),
249            (
250                "non-empty object insert field",
251                TestCase {
252                    this: Kind::object(BTreeMap::from([("b".into(), Kind::bytes())])),
253                    path: owned_value_path!("a"),
254                    kind: Kind::integer(),
255                    expected: Kind::object(BTreeMap::from([
256                        ("a".into(), Kind::integer()),
257                        ("b".into(), Kind::bytes()),
258                    ])),
259                },
260            ),
261            (
262                "object overwrite field",
263                TestCase {
264                    this: Kind::object(BTreeMap::from([("a".into(), Kind::bytes())])),
265                    path: owned_value_path!("a"),
266                    kind: Kind::integer(),
267                    expected: Kind::object(BTreeMap::from([("a".into(), Kind::integer())])),
268                },
269            ),
270            (
271                "set array index on empty array",
272                TestCase {
273                    this: Kind::array(Collection::empty()),
274                    path: owned_value_path!(0),
275                    kind: Kind::integer(),
276                    expected: Kind::array(BTreeMap::from([(0.into(), Kind::integer())])),
277                },
278            ),
279            (
280                "set array index past the end without unknown",
281                TestCase {
282                    this: Kind::array(Collection::empty()),
283                    path: owned_value_path!(1),
284                    kind: Kind::integer(),
285                    expected: Kind::array(BTreeMap::from([
286                        (0.into(), Kind::null()),
287                        (1.into(), Kind::integer()),
288                    ])),
289                },
290            ),
291            (
292                "set array index past the end with unknown",
293                TestCase {
294                    this: Kind::array(Collection::empty().with_unknown(Kind::integer())),
295                    path: owned_value_path!(1),
296                    kind: Kind::bytes(),
297                    expected: Kind::array(
298                        Collection::from(BTreeMap::from([
299                            (0.into(), Kind::integer().or_null()),
300                            (1.into(), Kind::bytes()),
301                        ]))
302                        .with_unknown(Kind::integer()),
303                    ),
304                },
305            ),
306            (
307                "set array index past the end with unknown, nested",
308                TestCase {
309                    this: Kind::array(Collection::empty().with_unknown(Kind::integer())),
310                    path: owned_value_path!(1, "foo"),
311                    kind: Kind::bytes(),
312                    expected: Kind::array(
313                        Collection::from(BTreeMap::from([
314                            (0.into(), Kind::integer().or_null()),
315                            (
316                                1.into(),
317                                Kind::object(BTreeMap::from([("foo".into(), Kind::bytes())])),
318                            ),
319                        ]))
320                        .with_unknown(Kind::integer()),
321                    ),
322                },
323            ),
324            (
325                "set array index past the end with null unknown",
326                TestCase {
327                    this: Kind::array(Collection::empty().with_unknown(Kind::null())),
328                    path: owned_value_path!(1),
329                    kind: Kind::integer(),
330                    expected: Kind::array(
331                        Collection::from(BTreeMap::from([
332                            (0.into(), Kind::null()),
333                            (1.into(), Kind::integer()),
334                        ]))
335                        .with_unknown(Kind::null()),
336                    ),
337                },
338            ),
339            (
340                "set field on non-object",
341                TestCase {
342                    this: Kind::integer(),
343                    path: owned_value_path!("a"),
344                    kind: Kind::integer(),
345                    expected: Kind::object(BTreeMap::from([("a".into(), Kind::integer())])),
346                },
347            ),
348            (
349                "set array index on non-array",
350                TestCase {
351                    this: Kind::integer(),
352                    path: owned_value_path!(0),
353                    kind: Kind::integer(),
354                    expected: Kind::array(BTreeMap::from([(0.into(), Kind::integer())])),
355                },
356            ),
357            (
358                "set negative array index (no unknown)",
359                TestCase {
360                    this: Kind::array(BTreeMap::from([
361                        (0.into(), Kind::integer()),
362                        (1.into(), Kind::integer()),
363                    ])),
364                    path: owned_value_path!(-1),
365                    kind: Kind::bytes(),
366                    expected: Kind::array(BTreeMap::from([
367                        (0.into(), Kind::integer()),
368                        (1.into(), Kind::bytes()),
369                    ])),
370                },
371            ),
372            (
373                "set negative array index past the end (no unknown)",
374                TestCase {
375                    this: Kind::array(BTreeMap::from([(0.into(), Kind::integer())])),
376                    path: owned_value_path!(-2),
377                    kind: Kind::bytes(),
378                    expected: Kind::array(BTreeMap::from([
379                        (0.into(), Kind::bytes()),
380                        (1.into(), Kind::null()),
381                    ])),
382                },
383            ),
384            (
385                "set negative array index size 1 unknown array",
386                TestCase {
387                    this: Kind::array(
388                        Collection::from(BTreeMap::from([(0.into(), Kind::integer())]))
389                            .with_unknown(Kind::integer()),
390                    ),
391                    path: owned_value_path!(-1),
392                    kind: Kind::bytes(),
393                    expected: Kind::array(
394                        Collection::from(BTreeMap::from([(0.into(), Kind::bytes().or_integer())]))
395                            .with_unknown(Kind::integer().or_bytes().or_undefined()),
396                    ),
397                },
398            ),
399            (
400                "set negative array index empty unknown array",
401                TestCase {
402                    this: Kind::array(Collection::empty().with_unknown(Kind::integer())),
403                    path: owned_value_path!(-1),
404                    kind: Kind::bytes(),
405                    expected: Kind::array(
406                        Collection::from(BTreeMap::from([
407                            // we can prove the first index will not be undefined
408                            (0.into(), Kind::bytes().or_integer()),
409                        ]))
410                        .with_unknown(Kind::integer().or_bytes().or_undefined()),
411                    ),
412                },
413            ),
414            (
415                "set negative array index empty unknown array (2)",
416                TestCase {
417                    this: Kind::array(Collection::empty().with_unknown(Kind::integer())),
418                    path: owned_value_path!(-2),
419                    kind: Kind::bytes(),
420                    expected: Kind::array(
421                        Collection::from(BTreeMap::from([
422                            (0.into(), Kind::integer().or_bytes()),
423                            // This is the only location a hole could potentially be inserted, so it
424                            // is the only index that gets "null", rather than adding it to the
425                            // entire unknown type.
426                            (1.into(), Kind::integer().or_bytes().or_null()),
427                        ]))
428                        .with_unknown(Kind::integer().or_bytes().or_undefined()),
429                    ),
430                },
431            ),
432            (
433                "set negative array index unknown array",
434                TestCase {
435                    this: Kind::array(
436                        Collection::from(BTreeMap::from([
437                            // This would be an invalid type without index 0 (it can't be undefined).
438                            (0.into(), Kind::integer()),
439                            (1.into(), Kind::float()),
440                        ]))
441                        .with_unknown(Kind::integer()),
442                    ),
443                    path: owned_value_path!(-3),
444                    kind: Kind::bytes(),
445                    expected: Kind::array(
446                        Collection::from(BTreeMap::from([
447                            // Either the unknown (integer) or the inserted value, depending on the actual length.
448                            (0.into(), Kind::integer().or_bytes()),
449                            // The original float if it wasn't shifted, or bytes/integer if it was shifted.
450                            // Can't be a hole.
451                            (1.into(), Kind::float().or_bytes().or_integer()),
452                            (2.into(), Kind::float().or_bytes().or_integer()),
453                        ]))
454                        .with_unknown(Kind::integer().or_bytes().or_undefined()),
455                    ),
456                },
457            ),
458            (
459                "set negative array index unknown array no holes",
460                TestCase {
461                    this: Kind::array(
462                        Collection::from(BTreeMap::from([
463                            (0.into(), Kind::float()),
464                            (1.into(), Kind::float()),
465                            (2.into(), Kind::float()),
466                        ]))
467                        .with_unknown(Kind::integer()),
468                    ),
469                    path: owned_value_path!(-3),
470                    kind: Kind::bytes(),
471                    expected: Kind::array(
472                        Collection::from(BTreeMap::from([
473                            (0.into(), Kind::float().or_bytes()),
474                            (1.into(), Kind::float().or_bytes()),
475                            (2.into(), Kind::float().or_bytes()),
476                        ]))
477                        .with_unknown(Kind::integer().or_bytes().or_undefined()),
478                    ),
479                },
480            ),
481            (
482                "set negative array index on non-array",
483                TestCase {
484                    this: Kind::integer(),
485                    path: owned_value_path!(-3),
486                    kind: Kind::bytes(),
487                    expected: Kind::array(Collection::from(BTreeMap::from([
488                        (0.into(), Kind::bytes()),
489                        (1.into(), Kind::null()),
490                        (2.into(), Kind::null()),
491                    ]))),
492                },
493            ),
494            (
495                "set nested negative array index on unknown array",
496                TestCase {
497                    this: Kind::array(Collection::empty().with_unknown(Kind::integer())),
498                    path: owned_value_path!(-3, "foo"),
499                    kind: Kind::bytes(),
500                    expected: Kind::array(
501                        Collection::from(BTreeMap::from([
502                            (
503                                0.into(),
504                                Kind::integer()
505                                    .or_object(BTreeMap::from([("foo".into(), Kind::bytes())])),
506                            ),
507                            (
508                                1.into(),
509                                Kind::integer()
510                                    .or_null()
511                                    .or_object(BTreeMap::from([("foo".into(), Kind::bytes())])),
512                            ),
513                            (
514                                2.into(),
515                                Kind::integer()
516                                    .or_null()
517                                    .or_object(BTreeMap::from([("foo".into(), Kind::bytes())])),
518                            ),
519                        ]))
520                        .with_unknown(
521                            Kind::integer()
522                                .or_undefined()
523                                .or_object(BTreeMap::from([("foo".into(), Kind::bytes())])),
524                        ),
525                    ),
526                },
527            ),
528            (
529                "set nested negative array index on unknown array (no holes)",
530                TestCase {
531                    this: Kind::array(
532                        Collection::from(BTreeMap::from([(0.into(), Kind::integer())]))
533                            .with_unknown(Kind::integer()),
534                    ),
535                    path: owned_value_path!(-1, "foo"),
536                    kind: Kind::bytes(),
537                    expected: Kind::array(
538                        Collection::from(BTreeMap::from([(
539                            0.into(),
540                            Kind::integer()
541                                .or_object(BTreeMap::from([("foo".into(), Kind::bytes())])),
542                        )]))
543                        .with_unknown(
544                            Kind::integer()
545                                .or_undefined()
546                                .or_object(BTreeMap::from([("foo".into(), Kind::bytes())])),
547                        ),
548                    ),
549                },
550            ),
551            (
552                "insert into never",
553                TestCase {
554                    this: Kind::never(),
555                    path: parse_value_path(".").unwrap(),
556                    kind: Kind::bytes(),
557                    expected: Kind::bytes(),
558                },
559            ),
560            (
561                "insert never",
562                TestCase {
563                    this: Kind::object(Collection::empty()),
564                    path: parse_value_path(".x").unwrap(),
565                    kind: Kind::never(),
566                    expected: Kind::object(Collection::empty()),
567                },
568            ),
569            (
570                "insert undefined",
571                TestCase {
572                    this: Kind::object(Collection::empty()),
573                    path: parse_value_path(".x").unwrap(),
574                    kind: Kind::undefined(),
575                    expected: Kind::object(BTreeMap::from([("x".into(), Kind::null())])),
576                },
577            ),
578            (
579                "array insert into any",
580                TestCase {
581                    this: Kind::any(),
582                    path: owned_value_path!(2),
583                    kind: Kind::bytes(),
584                    expected: Kind::array(
585                        Collection::from(BTreeMap::from([
586                            (0.into(), Kind::any().without_undefined()),
587                            (1.into(), Kind::any().without_undefined()),
588                            (2.into(), Kind::bytes()),
589                        ]))
590                        .with_unknown(Kind::any()),
591                    ),
592                },
593            ),
594            (
595                "object insert into any",
596                TestCase {
597                    this: Kind::any(),
598                    path: owned_value_path!("b"),
599                    kind: Kind::bytes(),
600                    expected: Kind::object(
601                        Collection::from(BTreeMap::from([("b".into(), Kind::bytes())]))
602                            .with_unknown(Kind::any()),
603                    ),
604                },
605            ),
606            (
607                "nested object/array insert into any",
608                TestCase {
609                    this: Kind::any(),
610                    path: owned_value_path!("x", 2),
611                    kind: Kind::bytes(),
612                    expected: Kind::object(
613                        Collection::from(BTreeMap::from([(
614                            "x".into(),
615                            Kind::array(
616                                Collection::from(BTreeMap::from([
617                                    (0.into(), Kind::any().without_undefined()),
618                                    (1.into(), Kind::any().without_undefined()),
619                                    (2.into(), Kind::bytes()),
620                                ]))
621                                .with_unknown(Kind::any()),
622                            ),
623                        )]))
624                        .with_unknown(Kind::any()),
625                    ),
626                },
627            ),
628            (
629                "nested array/array insert into any",
630                TestCase {
631                    this: Kind::any(),
632                    path: owned_value_path!(0, 0),
633                    kind: Kind::bytes(),
634                    expected: Kind::array(
635                        Collection::from(BTreeMap::from([(
636                            0.into(),
637                            Kind::array(
638                                Collection::from(BTreeMap::from([(0.into(), Kind::bytes())]))
639                                    .with_unknown(Kind::any()),
640                            ),
641                        )]))
642                        .with_unknown(Kind::any()),
643                    ),
644                },
645            ),
646        ] {
647            this.insert(&path, kind);
648            assert_eq!(this, expected, "{title}");
649        }
650    }
651}