1use crate::path::{BorrowedSegment, ValuePath};
4use crate::value::Kind;
5use std::borrow::Cow;
6
7impl Kind {
8 #[must_use]
16 #[allow(clippy::needless_pass_by_value)] pub fn get<'a>(&self, path: impl ValuePath<'a>) -> Self {
18 self.at_path(path).upgrade_undefined()
19 }
20
21 #[must_use]
25 #[allow(clippy::needless_pass_by_value)] 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 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 let len_required = -index as usize;
62
63 if array.unknown_kind().contains_any_defined() {
64 let min_length = largest_known_index.map_or(0, |i| i + 1);
70
71 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 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 let exact_len = largest_known_index.map_or(0, |x| x + 1);
93 if exact_len >= len_required {
94 index += exact_len as isize;
96 } else {
97 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 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}