Skip to main content

json_syntax/
tracing.rs

1//! Allocation-free source-location tracking ("backtraces") for [`JsonValue`].
2//!
3//! Pairs a value or error with a precise position in a JSON document, or
4//! across several documents, without allocating until a location is
5//! actually needed:
6//!
7//! - [`JsonBacktraceBuilder`] incrementally builds a location as a
8//!   borrow-chain stack while walking a document; call
9//!   [`JsonBacktraceBuilder::build`] to turn it into an owned
10//!   [`JsonBacktraceBuf`].
11//! - A [`JsonBacktraceBuf`]/[`JsonBacktrace`] is a sequence of
12//!   [`JsonBacktraceFrame`]s, one per file crossed, each holding a
13//!   [`JsonFragmentPathBuf`]/[`JsonFragmentPath`] of
14//!   [`JsonFragmentPathSegment`]/[`JsonFragmentPathItemRef`] navigation
15//!   steps within that file.
16//! - [`JsonLocated`] (and the [`JsonAt`]/[`JsonErrorAt`] helper traits)
17//!   pairs a value or error with its [`JsonBacktraceBuf`].
18//! - [`JsonValue::locate_fragment`] resolves a path back to a byte offset,
19//!   given the [`JsonCodeMap`] produced when the document was parsed.
20
21use core::fmt;
22use std::error::Error;
23
24use into_owned_trait::IntoOwned;
25
26use crate::{JsonCodeMap, JsonValue, array::JsonArrayExt, code_map::JsonMapped};
27
28impl JsonValue {
29	/// Returns the [`JsonCodeMap`] index of the fragment described by `path`.
30	///
31	/// The returned index can be used to look up the byte span of the fragment
32	/// in the `code_map` produced by parsing the same document.
33	///
34	/// Returns `None` if the path does not exist in this value (e.g., the key
35	/// is missing, the array index is out of range, or a path segment tries to
36	/// navigate into a fragment that has no sub-fragments).
37	///
38	/// # Segment semantics
39	///
40	/// - [`JsonFragmentPathSegment::ArrayIndex`]: navigates into the *i*-th
41	///   element of an array.
42	/// - [`JsonFragmentPathSegment::ObjectKey`]: navigates to the **key** string
43	///   fragment of the *n*-th occurrence of the named key.  This is a terminal
44	///   step; further path segments after this will return `None`.
45	/// - [`JsonFragmentPathSegment::ObjectValue`]: navigates to the **value**
46	///   of the *n*-th occurrence of the named key.
47	pub fn locate_fragment(
48		&self,
49		code_map: &JsonCodeMap,
50		path: &JsonFragmentPath,
51	) -> Option<usize> {
52		self.locate_fragment_at(code_map, path, 0)
53	}
54
55	fn locate_fragment_at(
56		&self,
57		code_map: &JsonCodeMap,
58		path: &JsonFragmentPath,
59		offset: usize,
60	) -> Option<usize> {
61		match path.split_first() {
62			Some((segment, rest)) => match segment {
63				JsonFragmentPathSegment::ArrayItem(i) => {
64					let array = self.as_array()?;
65					for (j, JsonMapped(item, item_offset)) in
66						array.iter_mapped(code_map, offset).enumerate()
67					{
68						if j == *i {
69							return item.locate_fragment_at(code_map, rest, item_offset);
70						}
71					}
72
73					None
74				}
75				JsonFragmentPathSegment::ObjectEntry(k, occurrence, part) => {
76					let object = self.as_object()?;
77					let mut occ_count = 0usize;
78					for JsonMapped(
79						(JsonMapped(key, key_offset), JsonMapped(value, value_offset)),
80						entry_offset,
81					) in object.iter_mapped(code_map, offset)
82					{
83						if k.as_str() == key.as_str() {
84							if occ_count == *occurrence {
85								return match *part {
86									JsonObjectEntryPart::All => {
87										if rest.is_empty() {
88											Some(entry_offset)
89										} else {
90											None
91										}
92									}
93									JsonObjectEntryPart::Key => {
94										if rest.is_empty() {
95											Some(key_offset)
96										} else {
97											None
98										}
99									}
100									JsonObjectEntryPart::Value => {
101										value.locate_fragment_at(code_map, rest, value_offset)
102									}
103								};
104							}
105
106							occ_count += 1;
107						}
108					}
109
110					None
111				}
112			},
113			None => Some(offset),
114		}
115	}
116}
117
118impl JsonValue {
119	/// Recursively searches this value, including nested arrays and objects,
120	/// for the first object containing a duplicate key.
121	///
122	/// Many JSON-based formats (such as JSON-LD) require object keys to be
123	/// unique throughout a document, even though JSON itself only recommends
124	/// it; this can be used to validate a whole document in one pass before
125	/// processing it, rather than checking every object individually as it is
126	/// encountered.
127	///
128	/// Returns the path to the first object found with a duplicate key
129	/// (relative to `self`), together with the duplicated key itself, or
130	/// `None` if no object anywhere in this value has a duplicate key. The
131	/// returned path can be passed to [`JsonValue::locate_fragment`] (with an
132	/// empty final segment, or combined with
133	/// [`JsonBacktraceBuilder`](crate::tracing::JsonBacktraceBuilder) methods)
134	/// to locate the offending object in a parsed document.
135	pub fn find_duplicate_key_recursive(&self) -> Option<(JsonFragmentPathBuf, &str)> {
136		let (mut path, key) = self.find_duplicate_key_recursive_reversed()?;
137		path.reverse();
138		Some((path, key))
139	}
140
141	/// Helper for [`Self::find_duplicate_key_recursive`].
142	///
143	/// Builds the path from the duplicate-key object back up to `self`,
144	/// i.e. in reverse order, since appending a segment as each stack frame
145	/// unwinds is cheaper than repeatedly inserting one at the front. The
146	/// public method reverses the result before returning it.
147	fn find_duplicate_key_recursive_reversed(&self) -> Option<(JsonFragmentPathBuf, &str)> {
148		match self {
149			Self::Array(items) => {
150				for (i, item) in items.iter().enumerate() {
151					if let Some((mut path, key)) = item.find_duplicate_key_recursive_reversed() {
152						path.push(JsonFragmentPathSegment::ArrayItem(i));
153						return Some((path, key));
154					}
155				}
156
157				None
158			}
159			Self::Object(object) => {
160				// This object's own entries take priority: if it has a
161				// duplicate key itself, there's no need to look deeper.
162				if let Some(key) = object.find_duplicate_key() {
163					return Some((Vec::new(), key));
164				}
165
166				// No duplicates among this object's own keys, so every key
167				// here maps to exactly one entry: occurrence `0` is always
168				// correct when descending into any of them.
169				for (key, value) in object.iter() {
170					if let Some((mut path, duplicate_key)) =
171						value.find_duplicate_key_recursive_reversed()
172					{
173						path.push(JsonFragmentPathSegment::ObjectEntry(
174							key.to_string(),
175							0,
176							JsonObjectEntryPart::Value,
177						));
178						return Some((path, duplicate_key));
179					}
180				}
181
182				None
183			}
184			_ => None,
185		}
186	}
187}
188
189/// Describes the exact location of a JSON fragment within a document, or
190/// across several documents.
191///
192/// A backtrace is a sequence of [`JsonBacktraceFrame`]s, ordered from the
193/// outermost file to the innermost. A single frame is enough to describe a
194/// fragment's position within one file (an object key/value, an array
195/// item, etc., accumulated as a [`JsonFragmentPathBuf`]). Multiple frames
196/// are needed when resolving that fragment required crossing into another
197/// file along the way.
198///
199/// A `JsonBacktraceBuf` is normally not built by hand: use
200/// [`JsonBacktraceBuilder`] to accumulate navigation steps cheaply while
201/// walking a document, then call [`JsonBacktraceBuilder::build`] to
202/// produce the final, owned value. Pair it with a value or error using
203/// [`JsonLocated`] (or the [`JsonAt`]/[`JsonErrorAt`] helper traits).
204pub type JsonBacktraceBuf<T> = Vec<JsonBacktraceFrame<T>>;
205
206/// Borrowed form of a [`JsonBacktraceBuf`].
207pub type JsonBacktrace<T> = [JsonBacktraceFrame<T>];
208
209/// A single frame of a [`JsonBacktraceBuf`]: the file a fragment was found
210/// in, and the path to that fragment within it.
211#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
212pub struct JsonBacktraceFrame<T> {
213	/// File in which the error occurred.
214	pub file: T,
215
216	/// Path to the exact fragment that caused the error.
217	pub fragment: JsonFragmentPathBuf,
218}
219
220impl<T> JsonBacktraceFrame<T> {
221	/// Creates a new frame for `file`, with an empty fragment path.
222	pub fn new(file: T) -> Self {
223		Self {
224			file,
225			fragment: Vec::new(),
226		}
227	}
228}
229
230/// Incrementally builds a [`JsonBacktraceBuf`] as a borrow-chain stack,
231/// without allocating until [`Self::build`] is called.
232///
233/// Each variant borrows the previous step (`&'a Self`), so a complete chain
234/// lives on the stack across nested calls; use [`Self::build`] to turn it
235/// into an owned [`JsonBacktraceBuf`].
236#[derive(Debug)]
237pub enum JsonBacktraceBuilder<'a, T: IntoOwned, O = <T as IntoOwned>::Owned> {
238	/// Empty backtrace with no frames.
239	Root,
240
241	/// Starts a new file frame, extending the previous chain.
242	File(&'a Self, T),
243
244	/// Pushes one navigation step onto the last file frame's fragment path.
245	Item(&'a Self, JsonFragmentPathItemRef<'a>),
246
247	/// Pushes every segment of a path onto the last file frame's fragment
248	/// path, in a single step.
249	Path(&'a Self, &'a JsonFragmentPathBuf),
250
251	/// Appends the frames of a previously-built backtrace onto the chain.
252	Backtrace(&'a Self, &'a JsonBacktrace<O>),
253}
254
255impl<T: IntoOwned + Clone> Clone for JsonBacktraceBuilder<'_, T> {
256	fn clone(&self) -> Self {
257		match *self {
258			Self::Root => Self::Root,
259			Self::File(parent, ref file) => Self::File(parent, file.clone()),
260			Self::Item(parent, item) => Self::Item(parent, item),
261			Self::Path(parent, path) => Self::Path(parent, path),
262			Self::Backtrace(parent, b) => Self::Backtrace(parent, b),
263		}
264	}
265}
266
267impl<T: IntoOwned + Copy> Copy for JsonBacktraceBuilder<'_, T> {}
268
269impl<T: IntoOwned> JsonBacktraceBuilder<'_, T> {
270	/// Creates an empty stack with no items.
271	pub fn new() -> &'static Self {
272		&Self::Root
273	}
274
275	/// Starts a new file frame, extending this chain.
276	pub fn file<'a>(&'a self, file: T) -> JsonBacktraceBuilder<'a, T> {
277		JsonBacktraceBuilder::File(self, file)
278	}
279
280	/// Pushes a single navigation step onto this chain.
281	pub fn push<'a>(&'a self, item: JsonFragmentPathItemRef<'a>) -> JsonBacktraceBuilder<'a, T> {
282		JsonBacktraceBuilder::Item(self, item)
283	}
284
285	/// Pushes every segment of `path`, in order, in a single step.
286	///
287	/// Equivalent to calling [`Self::push`] once per segment of `path`, but
288	/// without building an intermediate chain node for each one.
289	pub fn push_path<'a>(&'a self, path: &'a JsonFragmentPathBuf) -> JsonBacktraceBuilder<'a, T> {
290		JsonBacktraceBuilder::Path(self, path)
291	}
292
293	/// Appends the frames of a previously-built backtrace onto this chain,
294	/// in a single step.
295	///
296	/// Useful to "jump back" to a location recorded earlier (e.g. where a
297	/// value was originally defined), while still preserving how the
298	/// current chain got there.
299	pub fn push_backtrace<'a>(
300		&'a self,
301		backtrace: &'a JsonBacktrace<T::Owned>,
302	) -> JsonBacktraceBuilder<'a, T> {
303		JsonBacktraceBuilder::Backtrace(self, backtrace)
304	}
305
306	/// Push a navigation step to the `index`-th element of an array.
307	pub fn array_index(&self, index: usize) -> JsonBacktraceBuilder<'_, T> {
308		self.push(JsonFragmentPathItemRef::ArrayItem(index))
309	}
310
311	/// Push a navigation step to the whole entry (key + value) for the first occurrence of `key`.
312	///
313	/// Equivalent to `object_entry_at(key, 0)`. For objects with duplicate keys, use
314	/// [`object_entry_at`](Self::object_entry_at) to specify the occurrence index.
315	pub fn object_entry<'b>(&'b self, key: impl Into<&'b str>) -> JsonBacktraceBuilder<'b, T> {
316		self.push(JsonFragmentPathItemRef::ObjectEntry(
317			key.into(),
318			0,
319			JsonObjectEntryPart::All,
320		))
321	}
322
323	/// Push a navigation step to the whole entry (key + value) for the `occurrence`-th occurrence of `key`.
324	///
325	/// `0` selects the first occurrence, `1` the second, etc.
326	pub fn object_entry_at<'b>(
327		&'b self,
328		key: impl Into<&'b str>,
329		occurrence: usize,
330	) -> JsonBacktraceBuilder<'b, T> {
331		self.push(JsonFragmentPathItemRef::ObjectEntry(
332			key.into(),
333			occurrence,
334			JsonObjectEntryPart::All,
335		))
336	}
337
338	/// Push a navigation step to the key string of the first occurrence of `key`.
339	///
340	/// Equivalent to `object_key_at(key, 0)`. For objects with duplicate keys, use
341	/// [`object_key_at`](Self::object_key_at) to specify the occurrence index.
342	pub fn object_key<'b>(&'b self, key: impl Into<&'b str>) -> JsonBacktraceBuilder<'b, T> {
343		self.push(JsonFragmentPathItemRef::ObjectEntry(
344			key.into(),
345			0,
346			JsonObjectEntryPart::Key,
347		))
348	}
349
350	/// Push a navigation step to the key string of the `occurrence`-th occurrence of `key`.
351	///
352	/// `0` selects the first occurrence, `1` the second, etc.
353	pub fn object_key_at<'b>(
354		&'b self,
355		key: impl Into<&'b str>,
356		occurrence: usize,
357	) -> JsonBacktraceBuilder<'b, T> {
358		self.push(JsonFragmentPathItemRef::ObjectEntry(
359			key.into(),
360			occurrence,
361			JsonObjectEntryPart::Key,
362		))
363	}
364
365	/// Push a navigation step to the value of the first occurrence of `key`.
366	///
367	/// Equivalent to `object_value_at(key, 0)`. For objects with duplicate keys, use
368	/// [`object_value_at`](Self::object_value_at) to specify the occurrence index.
369	pub fn object_value<'b>(&'b self, key: impl Into<&'b str>) -> JsonBacktraceBuilder<'b, T> {
370		self.push(JsonFragmentPathItemRef::ObjectEntry(
371			key.into(),
372			0,
373			JsonObjectEntryPart::Value,
374		))
375	}
376
377	/// Push a navigation step to the value of the `occurrence`-th occurrence of `key`.
378	///
379	/// `0` selects the first occurrence, `1` the second, etc.
380	pub fn object_value_at<'b>(
381		&'b self,
382		key: impl Into<&'b str>,
383		occurrence: usize,
384	) -> JsonBacktraceBuilder<'b, T> {
385		self.push(JsonFragmentPathItemRef::ObjectEntry(
386			key.into(),
387			occurrence,
388			JsonObjectEntryPart::Value,
389		))
390	}
391
392	/// Consumes this chain and produces the equivalent owned
393	/// [`JsonBacktraceBuf`].
394	pub fn build(self) -> JsonBacktraceBuf<T::Owned>
395	where
396		T: Clone,
397		T::Owned: Clone,
398	{
399		match self {
400			Self::Root => JsonBacktraceBuf::new(),
401			Self::File(parent, file) => {
402				let mut backtrace = parent.clone().build();
403				backtrace.push(JsonBacktraceFrame::new(file.into_owned()));
404				backtrace
405			}
406			Self::Item(parent, item) => {
407				let mut backtrace = parent.clone().build();
408
409				if let Some(last) = backtrace.last_mut() {
410					last.fragment.push(item.into_owned());
411				}
412
413				backtrace
414			}
415			Self::Path(parent, path) => {
416				let mut backtrace = parent.clone().build();
417
418				if let Some(last) = backtrace.last_mut() {
419					last.fragment.extend(path.iter().cloned());
420				}
421
422				backtrace
423			}
424			Self::Backtrace(parent, b) => {
425				let mut backtrace = parent.clone().build();
426				backtrace.extend(b.iter().cloned());
427				backtrace
428			}
429		}
430	}
431}
432
433impl<T: Clone + IntoOwned> From<JsonBacktraceBuilder<'_, T>> for JsonBacktraceBuf<T::Owned>
434where
435	T::Owned: Clone,
436{
437	fn from(value: JsonBacktraceBuilder<'_, T>) -> Self {
438		value.build()
439	}
440}
441
442/// A value paired with a source location.
443#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
444pub struct JsonLocated<T, F> {
445	/// The located value.
446	pub value: T,
447
448	/// Backtrace describing where `value` was found.
449	pub location: JsonBacktraceBuf<F>,
450}
451
452impl<T, F> JsonLocated<T, F> {
453	/// Pairs `value` with its `location`.
454	pub fn new(value: T, location: JsonBacktraceBuf<F>) -> Self {
455		Self { value, location }
456	}
457
458	/// Maps the located value, keeping its location unchanged.
459	pub fn map<U>(self, f: impl FnOnce(T) -> U) -> JsonLocated<U, F> {
460		JsonLocated {
461			value: f(self.value),
462			location: self.location,
463		}
464	}
465
466	/// Converts the located value into another type, keeping its location
467	/// unchanged.
468	pub fn cast<U>(self) -> JsonLocated<U, F>
469	where
470		T: Into<U>,
471	{
472		self.map(Into::into)
473	}
474}
475
476impl<T: fmt::Display, F> fmt::Display for JsonLocated<T, F> {
477	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
478		self.value.fmt(f)
479	}
480}
481
482impl<T: 'static + Error, F: fmt::Debug> Error for JsonLocated<T, F> {
483	fn source(&self) -> Option<&(dyn Error + 'static)> {
484		Some(&self.value)
485	}
486}
487
488/// Owned form of a single path navigation step, as recorded in a
489/// [`JsonBacktraceFrame`].
490#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
491pub enum JsonFragmentPathSegment {
492	/// Navigates into the element at this index of an array.
493	ArrayItem(usize),
494
495	/// Navigates into the `occurrence`-th entry with this key in an
496	/// object, targeting the part given by [`JsonObjectEntryPart`].
497	ObjectEntry(String, usize, JsonObjectEntryPart),
498}
499
500/// Which part of an object entry a [`JsonFragmentPathSegment::ObjectEntry`]
501/// (or [`JsonFragmentPathItemRef::ObjectEntry`]) targets.
502#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
503pub enum JsonObjectEntryPart {
504	/// The whole entry (key and value).
505	All,
506
507	/// Just the key string.
508	Key,
509
510	/// Just the value.
511	Value,
512}
513
514/// Borrowed form of [`JsonFragmentPathSegment`], used while building a
515/// backtrace to avoid allocating until [`JsonBacktraceBuilder::build`] is
516/// called.
517#[derive(Debug, Clone, Copy)]
518pub enum JsonFragmentPathItemRef<'a> {
519	/// See [`JsonFragmentPathSegment::ArrayItem`].
520	ArrayItem(usize),
521
522	/// See [`JsonFragmentPathSegment::ObjectEntry`].
523	ObjectEntry(&'a str, usize, JsonObjectEntryPart),
524}
525
526impl JsonFragmentPathItemRef<'_> {
527	/// Converts this borrowed path segment into its owned
528	/// [`JsonFragmentPathSegment`] form.
529	pub fn into_owned(self) -> JsonFragmentPathSegment {
530		match self {
531			Self::ArrayItem(i) => JsonFragmentPathSegment::ArrayItem(i),
532			Self::ObjectEntry(k, n, part) => {
533				JsonFragmentPathSegment::ObjectEntry(k.to_owned(), n, part)
534			}
535		}
536	}
537}
538
539/// Owned sequence of path segments, from outermost to innermost.
540pub type JsonFragmentPathBuf = Vec<JsonFragmentPathSegment>;
541
542/// Borrowed form of a [`JsonFragmentPathBuf`].
543pub type JsonFragmentPath = [JsonFragmentPathSegment];
544
545/// Pairs any value with a location, producing a [`JsonLocated`].
546pub trait JsonAt: Sized {
547	/// Pairs `self` with `location`.
548	fn json_at<F>(self, location: impl Into<JsonBacktraceBuf<F>>) -> JsonLocated<Self, F>;
549}
550
551impl<T> JsonAt for T {
552	fn json_at<F>(self, location: impl Into<JsonBacktraceBuf<F>>) -> JsonLocated<Self, F> {
553		JsonLocated::new(self, location.into())
554	}
555}
556
557/// Pairs the error of a `Result`, if any, with a location.
558pub trait JsonErrorAt<T, E> {
559	/// Pairs this result's error, if any, with `location`.
560	fn json_err_at<F>(
561		self,
562		location: impl Into<JsonBacktraceBuf<F>>,
563	) -> Result<T, JsonLocated<E, F>>;
564}
565
566impl<T, E> JsonErrorAt<T, E> for Result<T, E> {
567	fn json_err_at<F>(
568		self,
569		location: impl Into<JsonBacktraceBuf<F>>,
570	) -> Result<T, JsonLocated<E, F>> {
571		self.map_err(|e| e.json_at(location))
572	}
573}
574
575#[cfg(test)]
576mod tests {
577	use crate::{JsonParse, JsonValue};
578
579	use super::*;
580
581	#[test]
582	fn locate_01() {
583		// { "a": 0, "b": [1, 2] }
584		// index 0: object          (volume 9)
585		// index 1: entry "a": 0   (volume 3)
586		// index 2: key "a"         (volume 1)
587		// index 3: value 0         (volume 1)
588		// index 4: entry "b": [...](volume 5)
589		// index 5: key "b"         (volume 1)
590		// index 6: array [1, 2]    (volume 3)
591		// index 7: value 1         (volume 1)
592		// index 8: value 2         (volume 1)
593		let (value, code_map) = JsonValue::parse_str(r#"{ "a": 0, "b": [1, 2] }"#).unwrap();
594
595		// empty path → root (the object itself)
596		assert_eq!(value.locate_fragment(&code_map, &[]), Some(0));
597
598		// entry fragments (All)
599		assert_eq!(
600			value.locate_fragment(
601				&code_map,
602				&[JsonFragmentPathSegment::ObjectEntry(
603					"a".into(),
604					0,
605					JsonObjectEntryPart::All
606				)]
607			),
608			Some(1)
609		);
610		assert_eq!(
611			value.locate_fragment(
612				&code_map,
613				&[JsonFragmentPathSegment::ObjectEntry(
614					"b".into(),
615					0,
616					JsonObjectEntryPart::All
617				)]
618			),
619			Some(4)
620		);
621
622		// key fragments
623		assert_eq!(
624			value.locate_fragment(
625				&code_map,
626				&[JsonFragmentPathSegment::ObjectEntry(
627					"a".into(),
628					0,
629					JsonObjectEntryPart::Key
630				)]
631			),
632			Some(2)
633		);
634		assert_eq!(
635			value.locate_fragment(
636				&code_map,
637				&[JsonFragmentPathSegment::ObjectEntry(
638					"b".into(),
639					0,
640					JsonObjectEntryPart::Key
641				)]
642			),
643			Some(5)
644		);
645
646		// value fragments
647		assert_eq!(
648			value.locate_fragment(
649				&code_map,
650				&[JsonFragmentPathSegment::ObjectEntry(
651					"a".into(),
652					0,
653					JsonObjectEntryPart::Value
654				)]
655			),
656			Some(3)
657		);
658		assert_eq!(
659			value.locate_fragment(
660				&code_map,
661				&[JsonFragmentPathSegment::ObjectEntry(
662					"b".into(),
663					0,
664					JsonObjectEntryPart::Value
665				)]
666			),
667			Some(6)
668		);
669
670		// array elements inside "b"
671		assert_eq!(
672			value.locate_fragment(
673				&code_map,
674				&[
675					JsonFragmentPathSegment::ObjectEntry("b".into(), 0, JsonObjectEntryPart::Value),
676					JsonFragmentPathSegment::ArrayItem(0),
677				]
678			),
679			Some(7)
680		);
681		assert_eq!(
682			value.locate_fragment(
683				&code_map,
684				&[
685					JsonFragmentPathSegment::ObjectEntry("b".into(), 0, JsonObjectEntryPart::Value),
686					JsonFragmentPathSegment::ArrayItem(1),
687				]
688			),
689			Some(8)
690		);
691
692		// missing key
693		assert_eq!(
694			value.locate_fragment(
695				&code_map,
696				&[JsonFragmentPathSegment::ObjectEntry(
697					"z".into(),
698					0,
699					JsonObjectEntryPart::Value
700				)]
701			),
702			None
703		);
704
705		// array index out of bounds
706		assert_eq!(
707			value.locate_fragment(
708				&code_map,
709				&[
710					JsonFragmentPathSegment::ObjectEntry("b".into(), 0, JsonObjectEntryPart::Value),
711					JsonFragmentPathSegment::ArrayItem(2),
712				]
713			),
714			None
715		);
716
717		// All and Key are terminal – further segments after them return None
718		assert_eq!(
719			value.locate_fragment(
720				&code_map,
721				&[
722					JsonFragmentPathSegment::ObjectEntry("b".into(), 0, JsonObjectEntryPart::All),
723					JsonFragmentPathSegment::ArrayItem(0),
724				]
725			),
726			None
727		);
728		assert_eq!(
729			value.locate_fragment(
730				&code_map,
731				&[
732					JsonFragmentPathSegment::ObjectEntry("b".into(), 0, JsonObjectEntryPart::Key),
733					JsonFragmentPathSegment::ArrayItem(0),
734				]
735			),
736			None
737		);
738	}
739
740	#[test]
741	fn locate_duplicate_keys() {
742		// { "a": 1, "a": 2 }
743		// index 0: object                        (volume 7)
744		// index 1: entry "a": 1, occurrence 0   (volume 3)
745		// index 2: key "a",   occurrence 0       (volume 1)
746		// index 3: value 1,   occurrence 0       (volume 1)
747		// index 4: entry "a": 2, occurrence 1   (volume 3)
748		// index 5: key "a",   occurrence 1       (volume 1)
749		// index 6: value 2,   occurrence 1       (volume 1)
750		let (value, code_map) = JsonValue::parse_str(r#"{ "a": 1, "a": 2 }"#).unwrap();
751
752		// first occurrence
753		assert_eq!(
754			value.locate_fragment(
755				&code_map,
756				&[JsonFragmentPathSegment::ObjectEntry(
757					"a".into(),
758					0,
759					JsonObjectEntryPart::All
760				)]
761			),
762			Some(1)
763		);
764		assert_eq!(
765			value.locate_fragment(
766				&code_map,
767				&[JsonFragmentPathSegment::ObjectEntry(
768					"a".into(),
769					0,
770					JsonObjectEntryPart::Key
771				)]
772			),
773			Some(2)
774		);
775		assert_eq!(
776			value.locate_fragment(
777				&code_map,
778				&[JsonFragmentPathSegment::ObjectEntry(
779					"a".into(),
780					0,
781					JsonObjectEntryPart::Value
782				)]
783			),
784			Some(3)
785		);
786
787		// second occurrence
788		assert_eq!(
789			value.locate_fragment(
790				&code_map,
791				&[JsonFragmentPathSegment::ObjectEntry(
792					"a".into(),
793					1,
794					JsonObjectEntryPart::All
795				)]
796			),
797			Some(4)
798		);
799		assert_eq!(
800			value.locate_fragment(
801				&code_map,
802				&[JsonFragmentPathSegment::ObjectEntry(
803					"a".into(),
804					1,
805					JsonObjectEntryPart::Key
806				)]
807			),
808			Some(5)
809		);
810		assert_eq!(
811			value.locate_fragment(
812				&code_map,
813				&[JsonFragmentPathSegment::ObjectEntry(
814					"a".into(),
815					1,
816					JsonObjectEntryPart::Value
817				)]
818			),
819			Some(6)
820		);
821
822		// non-existent third occurrence
823		assert_eq!(
824			value.locate_fragment(
825				&code_map,
826				&[JsonFragmentPathSegment::ObjectEntry(
827					"a".into(),
828					2,
829					JsonObjectEntryPart::Value
830				)]
831			),
832			None
833		);
834	}
835}