vrl/value/kind/crud/
get.rs

1//! All types related to finding a [`Kind`] nested into another one.
2
3use crate::path::{BorrowedSegment, ValuePath};
4use crate::value::Kind;
5use std::borrow::Cow;
6
7impl Kind {
8    /// Returns the type of a value that is retrieved from a certain path.
9    ///
10    /// This has the same behavior as `Value::get`, including
11    /// the implicit conversion of "undefined" to "null.
12    ///
13    /// If you want the type _without_ the implicit type conversion,
14    /// use `Kind::at_path` instead.
15    #[must_use]
16    #[allow(clippy::needless_pass_by_value)] // only references are implemented for `Path`
17    pub fn get<'a>(&self, path: impl ValuePath<'a>) -> Self {
18        self.at_path(path).upgrade_undefined()
19    }
20
21    /// This retrieves the `Kind` at a given path. There is a subtle difference
22    /// between this and `Kind::get` where this function does _not_ convert undefined to null.
23    /// It is viewing the type of a value in-place, before it is retrieved.
24    #[must_use]
25    #[allow(clippy::needless_pass_by_value)] // only references are implemented for `Path`
26    pub fn at_path<'a>(&self, path: impl ValuePath<'a>) -> Self {
27        self.get_recursive(path.segment_iter())
28    }
29
30    fn get_field(&self, field: Cow<'_, str>) -> Self {
31        self.as_object().map_or_else(Self::undefined, |object| {
32            let mut kind = object
33                .known()
34                .get(&field.into_owned().into())
35                .cloned()
36                .unwrap_or_else(|| object.unknown_kind());
37
38            if !self.is_exact() {
39                kind = kind.or_undefined();
40            }
41            kind
42        })
43    }
44
45    fn get_recursive<'a>(
46        &self,
47        mut iter: impl Iterator<Item = BorrowedSegment<'a>> + Clone,
48    ) -> Self {
49        if self.is_never() {
50            // a terminating expression by definition can "never" resolve to a value
51            return Self::never();
52        }
53
54        match iter.next() {
55            Some(BorrowedSegment::Field(field)) => self.get_field(field).get_recursive(iter),
56            Some(BorrowedSegment::Index(mut index)) => {
57                if let Some(array) = self.as_array() {
58                    if index < 0 {
59                        let largest_known_index = array.known().keys().map(|i| i.to_usize()).max();
60                        // The minimum size of the resulting array.
61                        let len_required = -index as usize;
62
63                        if array.unknown_kind().contains_any_defined() {
64                            // The exact length is not known. We can't know for sure if the index
65                            // will point to a known or unknown type, so the union of the unknown type
66                            // plus any possible known type must be taken. Just the unknown type alone is not sufficient.
67
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 = largest_known_index.map_or(0, |i| i + 1);
70
71                            // We can prove the positive index won't be less than "min_index".
72                            let min_index = (min_length as isize + index).max(0) as usize;
73                            let can_underflow = (min_length as isize + index) < 0;
74
75                            let mut kind = array.unknown_kind();
76
77                            // We can prove the index won't underflow, so it cannot be "undefined".
78                            // But only if the type can only be an array.
79                            if self.is_exact() && !can_underflow {
80                                kind.remove_undefined();
81                            }
82
83                            for (i, i_kind) in array.known() {
84                                if i.to_usize() >= min_index {
85                                    kind.merge_keep(i_kind.clone(), false);
86                                }
87                            }
88                            return kind.get_recursive(iter);
89                        }
90
91                        // There are no unknown indices, so we can determine the exact positive index.
92                        let exact_len = largest_known_index.map_or(0, |x| x + 1);
93                        if exact_len >= len_required {
94                            // Make the index positive, then continue below.
95                            index += exact_len as isize;
96                        } else {
97                            // Out of bounds index.
98                            return Self::undefined();
99                        }
100                    }
101
102                    debug_assert!(index >= 0, "negative indices already handled");
103
104                    let index = index as usize;
105                    let mut kind = array
106                        .known()
107                        .get(&index.into())
108                        .cloned()
109                        .unwrap_or_else(|| array.unknown_kind());
110
111                    if !self.is_exact() {
112                        kind = kind.or_undefined();
113                    }
114                    kind.get_recursive(iter)
115                } else {
116                    Self::undefined()
117                }
118            }
119            Some(BorrowedSegment::Invalid) => {
120                // Value::get returns `None` in this case, which means the value is not defined.
121                Self::undefined()
122            }
123            None => self.clone(),
124        }
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use crate::owned_value_path;
131    use crate::path::OwnedValuePath;
132    use std::collections::BTreeMap;
133
134    use super::*;
135    use crate::value::kind::Collection;
136    #[test]
137    #[allow(clippy::too_many_lines)]
138    fn test_at_path() {
139        struct TestCase {
140            kind: Kind,
141            path: OwnedValuePath,
142            want: Kind,
143        }
144
145        for (title, TestCase { kind, path, want }) in [
146            (
147                "get root",
148                TestCase {
149                    kind: Kind::bytes(),
150                    path: owned_value_path!(),
151                    want: Kind::bytes(),
152                },
153            ),
154            (
155                "get field from non-object",
156                TestCase {
157                    kind: Kind::bytes(),
158                    path: owned_value_path!("foo"),
159                    want: Kind::undefined(),
160                },
161            ),
162            (
163                "get field from object",
164                TestCase {
165                    kind: Kind::object(BTreeMap::from([("a".into(), Kind::integer())])),
166                    path: owned_value_path!("a"),
167                    want: Kind::integer(),
168                },
169            ),
170            (
171                "get field from maybe an object",
172                TestCase {
173                    kind: Kind::object(BTreeMap::from([("a".into(), Kind::integer())])).or_null(),
174                    path: owned_value_path!("a"),
175                    want: Kind::integer().or_undefined(),
176                },
177            ),
178            (
179                "get unknown from object with no unknown",
180                TestCase {
181                    kind: Kind::object(BTreeMap::from([("a".into(), Kind::integer())])),
182                    path: owned_value_path!("b"),
183                    want: Kind::undefined(),
184                },
185            ),
186            (
187                "get unknown from object with unknown",
188                TestCase {
189                    kind: Kind::object(
190                        Collection::from(BTreeMap::from([("a".into(), Kind::integer())]))
191                            .with_unknown(Kind::bytes()),
192                    ),
193                    path: owned_value_path!("b"),
194                    want: Kind::bytes().or_undefined(),
195                },
196            ),
197            (
198                "get unknown from object with null unknown",
199                TestCase {
200                    kind: Kind::object(
201                        Collection::from(BTreeMap::from([("a".into(), Kind::integer())]))
202                            .with_unknown(Kind::null()),
203                    ),
204                    path: owned_value_path!("b"),
205                    want: Kind::null().or_undefined(),
206                },
207            ),
208            (
209                "get nested field",
210                TestCase {
211                    kind: Kind::object(
212                        Collection::from(BTreeMap::from([(
213                            "a".into(),
214                            Kind::object(
215                                Collection::from(BTreeMap::from([("b".into(), Kind::integer())]))
216                                    .with_unknown(Kind::null()),
217                            ),
218                        )]))
219                        .with_unknown(Kind::null()),
220                    ),
221                    path: owned_value_path!("a", "b"),
222                    want: Kind::integer(),
223                },
224            ),
225            (
226                "get index from non-array",
227                TestCase {
228                    kind: Kind::bytes(),
229                    path: owned_value_path!(1),
230                    want: Kind::undefined(),
231                },
232            ),
233            (
234                "get index from array",
235                TestCase {
236                    kind: Kind::array(BTreeMap::from([(0.into(), Kind::integer())])),
237                    path: owned_value_path!(0),
238                    want: Kind::integer(),
239                },
240            ),
241            (
242                "get index from maybe array",
243                TestCase {
244                    kind: Kind::array(BTreeMap::from([(0.into(), Kind::integer())])).or_bytes(),
245                    path: owned_value_path!(0),
246                    want: Kind::integer().or_undefined(),
247                },
248            ),
249            (
250                "get unknown from array with no unknown",
251                TestCase {
252                    kind: Kind::array(BTreeMap::from([(0.into(), Kind::integer())])),
253                    path: owned_value_path!(1),
254                    want: Kind::undefined(),
255                },
256            ),
257            (
258                "get unknown from array with unknown",
259                TestCase {
260                    kind: Kind::array(
261                        Collection::from(BTreeMap::from([(0.into(), Kind::integer())]))
262                            .with_unknown(Kind::bytes()),
263                    ),
264                    path: owned_value_path!(1),
265                    want: Kind::bytes().or_undefined(),
266                },
267            ),
268            (
269                "get unknown from array with null unknown",
270                TestCase {
271                    kind: Kind::array(
272                        Collection::from(BTreeMap::from([(0.into(), Kind::integer())]))
273                            .with_unknown(Kind::null()),
274                    ),
275                    path: owned_value_path!(1),
276                    want: Kind::null().or_undefined(),
277                },
278            ),
279            (
280                "get nested index",
281                TestCase {
282                    kind: Kind::array(
283                        Collection::from(BTreeMap::from([(
284                            0.into(),
285                            Kind::array(
286                                Collection::from(BTreeMap::from([(0.into(), Kind::integer())]))
287                                    .with_unknown(Kind::null()),
288                            ),
289                        )]))
290                        .with_unknown(Kind::null()),
291                    ),
292                    path: owned_value_path!(0, 0),
293                    want: Kind::integer(),
294                },
295            ),
296            (
297                "out of bounds negative index",
298                TestCase {
299                    kind: Kind::array(Collection::from(BTreeMap::from([(
300                        0.into(),
301                        Kind::integer(),
302                    )]))),
303                    path: owned_value_path!(-2),
304                    want: Kind::undefined(),
305                },
306            ),
307            (
308                "negative index no unknown",
309                TestCase {
310                    kind: Kind::array(Collection::from(BTreeMap::from([(
311                        0.into(),
312                        Kind::integer(),
313                    )]))),
314                    path: owned_value_path!(-1),
315                    want: Kind::integer(),
316                },
317            ),
318            (
319                "negative index with unknown can't underflow",
320                TestCase {
321                    kind: Kind::array(
322                        Collection::from(BTreeMap::from([
323                            (0.into(), Kind::integer()),
324                            (1.into(), Kind::bytes()),
325                            (2.into(), Kind::float()),
326                        ]))
327                        .with_unknown(Kind::boolean()),
328                    ),
329                    path: owned_value_path!(-2),
330                    want: Kind::boolean().or_bytes().or_float(),
331                },
332            ),
333            (
334                "negative index with unknown can't underflow (maybe array)",
335                TestCase {
336                    kind: Kind::array(
337                        Collection::from(BTreeMap::from([
338                            (0.into(), Kind::integer()),
339                            (1.into(), Kind::bytes()),
340                            (2.into(), Kind::float()),
341                        ]))
342                        .with_unknown(Kind::boolean()),
343                    )
344                    .or_null(),
345                    path: owned_value_path!(-2),
346                    want: Kind::boolean().or_bytes().or_float().or_undefined(),
347                },
348            ),
349            (
350                "negative index with unknown can underflow",
351                TestCase {
352                    kind: Kind::array(
353                        Collection::from(BTreeMap::from([(0.into(), Kind::integer())]))
354                            .with_unknown(Kind::boolean()),
355                    ),
356                    path: owned_value_path!(-2),
357                    want: Kind::boolean().or_integer().or_undefined(),
358                },
359            ),
360            (
361                "negative index nested",
362                TestCase {
363                    kind: Kind::array(
364                        Collection::from(BTreeMap::from([(0.into(), Kind::integer())]))
365                            .with_unknown(Kind::object(BTreeMap::from([(
366                                "foo".into(),
367                                Kind::bytes(),
368                            )]))),
369                    ),
370                    path: owned_value_path!(-2, "foo"),
371                    want: Kind::bytes().or_undefined(),
372                },
373            ),
374            (
375                "nested terminating expression",
376                TestCase {
377                    kind: Kind::never(),
378                    path: owned_value_path!(".foo.bar"),
379                    want: Kind::never(),
380                },
381            ),
382        ] {
383            assert_eq!(kind.at_path(&path), want, "test: {title}");
384        }
385    }
386}