Skip to main content

ploidy_pointer/
lib.rs

1use std::{
2    any::Any,
3    borrow::{Borrow, Cow},
4    collections::{BTreeMap, HashMap},
5    fmt::{Debug, Display},
6    hash::BuildHasher,
7    iter::FusedIterator,
8    ops::{Deref, Range},
9    rc::Rc,
10    str::Split,
11    sync::Arc,
12};
13
14use ref_cast::{RefCastCustom, ref_cast_custom};
15
16#[cfg(feature = "derive")]
17pub use ploidy_pointer_derive::{JsonPointee, JsonPointerTarget};
18
19/// A JSON Pointer.
20#[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd, RefCastCustom)]
21#[repr(transparent)]
22pub struct JsonPointer(str);
23
24impl JsonPointer {
25    #[ref_cast_custom]
26    fn new(raw: &str) -> &Self;
27
28    /// Parses a pointer from an RFC 6901 string.
29    ///
30    /// The empty string is the valid root pointer.
31    /// All other strings must start with `/`.
32    #[inline]
33    pub fn parse(s: &str) -> Result<&Self, JsonPointerSyntaxError> {
34        if s.is_empty() || s.starts_with('/') {
35            Ok(Self::new(s))
36        } else {
37            Err(JsonPointerSyntaxError)
38        }
39    }
40
41    /// Returns the empty root pointer.
42    #[inline]
43    pub fn empty() -> &'static Self {
44        JsonPointer::new("")
45    }
46
47    /// Returns `true` if this is the empty root pointer.
48    #[inline]
49    pub fn is_empty(&self) -> bool {
50        self.0.is_empty()
51    }
52
53    /// Returns the first segment, or `None` for the root pointer.
54    #[inline]
55    pub fn head(&self) -> Option<&JsonPointerSegment> {
56        let rest = self.0.strip_prefix('/')?;
57        let raw = rest.find('/').map(|index| &rest[..index]).unwrap_or(rest);
58        Some(JsonPointerSegment::new(raw))
59    }
60
61    /// Returns the pointer without its first segment.
62    ///
63    /// For the root pointer, returns the root pointer.
64    #[inline]
65    pub fn tail(&self) -> &JsonPointer {
66        self.0
67            .strip_prefix('/')
68            .and_then(|rest| rest.find('/').map(|index| &rest[index..]))
69            .map(JsonPointer::new)
70            .unwrap_or_else(|| JsonPointer::empty())
71    }
72
73    /// Returns a borrowing iterator over the segments.
74    #[inline]
75    pub fn segments(&self) -> JsonPointerSegments<'_> {
76        JsonPointerSegments(self.0.strip_prefix('/').map(|raw| raw.split('/')))
77    }
78
79    /// Follows this pointer through `root` and extracts the result as `T`.
80    #[inline]
81    pub fn follow<'a, T: JsonPointerTarget<'a>>(
82        &self,
83        root: &'a (impl JsonPointee + ?Sized),
84    ) -> Result<T, JsonPointerError> {
85        T::from_pointee(root.resolve(self)?).map_err(|err| JsonPointerError::Type {
86            pointer: self.to_owned(),
87            source: err,
88        })
89    }
90}
91
92impl Display for JsonPointer {
93    #[inline]
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        f.write_str(&self.0)
96    }
97}
98
99impl<'a> From<&'a JsonPointer> for Cow<'a, JsonPointer> {
100    #[inline]
101    fn from(value: &'a JsonPointer) -> Self {
102        Cow::Borrowed(value)
103    }
104}
105
106impl ToOwned for JsonPointer {
107    type Owned = JsonPointerBuf;
108
109    #[inline]
110    fn to_owned(&self) -> Self::Owned {
111        JsonPointerBuf(self.0.to_owned())
112    }
113}
114
115/// An owned JSON Pointer.
116#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
117pub struct JsonPointerBuf(String);
118
119impl JsonPointerBuf {
120    /// Parses an owned pointer from an RFC 6901 string.
121    ///
122    /// The empty string is the valid root pointer.
123    /// All other strings must start with `/`.
124    #[inline]
125    pub fn parse(s: String) -> Result<Self, JsonPointerSyntaxError> {
126        if s.is_empty() || s.starts_with('/') {
127            Ok(Self(s))
128        } else {
129            Err(JsonPointerSyntaxError)
130        }
131    }
132}
133
134impl AsRef<JsonPointer> for JsonPointerBuf {
135    #[inline]
136    fn as_ref(&self) -> &JsonPointer {
137        self
138    }
139}
140
141impl Borrow<JsonPointer> for JsonPointerBuf {
142    #[inline]
143    fn borrow(&self) -> &JsonPointer {
144        self
145    }
146}
147
148impl Deref for JsonPointerBuf {
149    type Target = JsonPointer;
150
151    #[inline]
152    fn deref(&self) -> &Self::Target {
153        JsonPointer::new(&self.0)
154    }
155}
156
157impl Display for JsonPointerBuf {
158    #[inline]
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        <JsonPointer as Display>::fmt(self, f)
161    }
162}
163
164impl From<JsonPointerBuf> for Cow<'_, JsonPointer> {
165    #[inline]
166    fn from(value: JsonPointerBuf) -> Self {
167        Cow::Owned(value)
168    }
169}
170
171impl From<&JsonPointer> for JsonPointerBuf {
172    #[inline]
173    fn from(value: &JsonPointer) -> Self {
174        value.to_owned()
175    }
176}
177
178/// A value that a [`JsonPointer`] points to.
179pub trait JsonPointee: Any {
180    /// Resolves a [`JsonPointer`] against this value.
181    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError>;
182
183    /// Returns the concrete type name of this value.
184    #[inline]
185    fn name(&self) -> &'static str {
186        std::any::type_name::<Self>()
187    }
188}
189
190/// Extracts a typed value from a [`JsonPointee`].
191pub trait JsonPointerTarget<'a>: Sized {
192    /// Tries to extract `Self` from a resolved pointee.
193    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError>;
194}
195
196/// Convenience methods for [`JsonPointee`] types.
197pub trait JsonPointeeExt: JsonPointee {
198    /// Parses a JSON pointer string, resolves it against this value,
199    /// and extracts the result as `T`.
200    #[inline]
201    fn pointer<'a, T: JsonPointerTarget<'a>>(&'a self, path: &str) -> Result<T, JsonPointerError> {
202        JsonPointer::parse(path)?.follow(self)
203    }
204}
205
206impl<P: JsonPointee + ?Sized> JsonPointeeExt for P {}
207
208/// A single segment of a [`JsonPointer`].
209#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, RefCastCustom)]
210#[repr(transparent)]
211pub struct JsonPointerSegment(str);
212
213impl JsonPointerSegment {
214    #[ref_cast_custom]
215    fn new(raw: &str) -> &Self;
216
217    /// Returns the value of this segment as a string.
218    #[inline]
219    pub fn to_str(&self) -> Cow<'_, str> {
220        if self.0.contains('~') {
221            self.0.replace("~1", "/").replace("~0", "~").into()
222        } else {
223            Cow::Borrowed(&self.0)
224        }
225    }
226
227    /// Returns the value of this segment as an array index,
228    /// or `None` if this segment can't be used as an index.
229    #[inline]
230    pub fn to_index(&self) -> Option<usize> {
231        match self.0.as_bytes() {
232            [b'0'] => Some(0),
233            [b'1'..=b'9', rest @ ..] if rest.iter().all(u8::is_ascii_digit) => {
234                // `usize::from_str` allows a leading `+`, and
235                // ignores leading zeros; RFC 6901 forbids both.
236                self.0.parse().ok()
237            }
238            _ => None,
239        }
240    }
241}
242
243impl PartialEq<str> for JsonPointerSegment {
244    #[inline]
245    fn eq(&self, other: &str) -> bool {
246        self.to_str() == other
247    }
248}
249
250impl PartialEq<JsonPointerSegment> for str {
251    #[inline]
252    fn eq(&self, other: &JsonPointerSegment) -> bool {
253        other == self
254    }
255}
256
257impl Display for JsonPointerSegment {
258    #[inline]
259    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260        f.write_str(&self.to_str())
261    }
262}
263
264/// A borrowing iterator over the segments of a [`JsonPointer`].
265#[derive(Clone, Debug)]
266pub struct JsonPointerSegments<'a>(Option<Split<'a, char>>);
267
268impl<'a> Iterator for JsonPointerSegments<'a> {
269    type Item = &'a JsonPointerSegment;
270
271    #[inline]
272    fn next(&mut self) -> Option<Self::Item> {
273        self.0
274            .as_mut()
275            .and_then(|iter| iter.next())
276            .map(JsonPointerSegment::new)
277    }
278}
279
280impl<'a> DoubleEndedIterator for JsonPointerSegments<'a> {
281    #[inline]
282    fn next_back(&mut self) -> Option<Self::Item> {
283        self.0
284            .as_mut()
285            .and_then(|iter| iter.next_back())
286            .map(JsonPointerSegment::new)
287    }
288}
289
290impl FusedIterator for JsonPointerSegments<'_> {}
291
292macro_rules! impl_pointee_for {
293    () => {};
294    (#[$($attrs:tt)+] $ty:ty $(, $($rest:tt)*)?) => {
295        #[$($attrs)*]
296        impl_pointee_for!($ty);
297        $(impl_pointee_for!($($rest)*);)?
298    };
299    ($ty:ty $(, $($rest:tt)*)?) => {
300        impl JsonPointee for $ty {
301            fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
302                if pointer.is_empty() {
303                    Ok(self)
304                } else {
305                    Err({
306                        #[cfg(feature = "did-you-mean")]
307                        let err = JsonPointerTypeError::with_ty(
308                            pointer,
309                            JsonPointeeType::Named(stringify!($ty)),
310                        );
311                        #[cfg(not(feature = "did-you-mean"))]
312                        let err = JsonPointerTypeError::new(pointer);
313                        err
314                    })?
315                }
316            }
317        }
318        $(impl_pointee_for!($($rest)*);)?
319    };
320}
321
322impl_pointee_for!(
323    i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize, f32, f64, bool, String, &'static str,
324    #[cfg(feature = "chrono")] chrono::DateTime<chrono::Utc>,
325    #[cfg(feature = "chrono")] chrono::NaiveDate,
326    #[cfg(feature = "url")] url::Url,
327    #[cfg(feature = "serde_bytes")] serde_bytes::ByteBuf,
328);
329
330macro_rules! impl_copied_pointer_target_for {
331    () => {};
332    (#[$($attrs:tt)+] $ty:ty $(, $($rest:tt)*)?) => {
333        #[$($attrs)*]
334        impl_copied_pointer_target_for!($ty);
335        $(impl_copied_pointer_target_for!($($rest)*);)?
336    };
337    ($ty:ty $(, $($rest:tt)*)?) => {
338        impl<'a> JsonPointerTarget<'a> for $ty {
339            #[inline]
340            fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
341                let any: &dyn Any = pointee;
342                any.downcast_ref::<$ty>().copied().ok_or_else(|| JsonPointerTargetError {
343                    expected: ::std::any::type_name::<$ty>(),
344                    actual: pointee.name(),
345                })
346            }
347        }
348        $(impl_copied_pointer_target_for!($($rest)*);)?
349    };
350}
351
352impl_copied_pointer_target_for!(
353    i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize, f32, f64, bool,
354    #[cfg(feature = "chrono")] chrono::DateTime<chrono::Utc>,
355    #[cfg(feature = "chrono")] chrono::NaiveDate,
356);
357
358#[cfg(feature = "serde_bytes")]
359impl<'a> JsonPointerTarget<'a> for &'a serde_bytes::ByteBuf {
360    #[inline]
361    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
362        let any: &dyn Any = pointee;
363        any.downcast_ref::<serde_bytes::ByteBuf>()
364            .ok_or_else(|| JsonPointerTargetError {
365                expected: ::std::any::type_name::<serde_bytes::ByteBuf>(),
366                actual: pointee.name(),
367            })
368    }
369}
370
371impl<'a> JsonPointerTarget<'a> for &'a str {
372    #[inline]
373    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
374        let any: &dyn Any = pointee;
375        if let Some(s) = any.downcast_ref::<String>() {
376            Ok(s.as_str())
377        } else if let Some(&s) = any.downcast_ref::<&str>() {
378            Ok(s)
379        } else {
380            Err(JsonPointerTargetError {
381                expected: "str",
382                actual: pointee.name(),
383            })
384        }
385    }
386}
387
388impl<'a, T: JsonPointee> JsonPointerTarget<'a> for &'a [T] {
389    #[inline]
390    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
391        let any: &dyn Any = pointee;
392        if let Some(v) = any.downcast_ref::<Vec<T>>() {
393            Ok(v.as_slice())
394        } else if let Some(v) = any.downcast_ref::<&[T]>() {
395            Ok(v)
396        } else {
397            Err(JsonPointerTargetError {
398                expected: ::std::any::type_name::<[T]>(),
399                actual: pointee.name(),
400            })
401        }
402    }
403}
404
405#[cfg(feature = "url")]
406impl<'a> JsonPointerTarget<'a> for &'a url::Url {
407    #[inline]
408    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
409        let any: &dyn Any = pointee;
410        any.downcast_ref::<url::Url>()
411            .ok_or_else(|| JsonPointerTargetError {
412                expected: ::std::any::type_name::<url::Url>(),
413                actual: pointee.name(),
414            })
415    }
416}
417
418impl<'a, T: JsonPointee, H: BuildHasher + 'static> JsonPointerTarget<'a>
419    for &'a HashMap<String, T, H>
420{
421    #[inline]
422    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
423        let any: &dyn Any = pointee;
424        any.downcast_ref::<HashMap<String, T, H>>()
425            .ok_or_else(|| JsonPointerTargetError {
426                expected: ::std::any::type_name::<HashMap<String, T, H>>(),
427                actual: pointee.name(),
428            })
429    }
430}
431
432impl<'a, T: JsonPointee> JsonPointerTarget<'a> for &'a BTreeMap<String, T> {
433    #[inline]
434    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
435        let any: &dyn Any = pointee;
436        any.downcast_ref::<BTreeMap<String, T>>()
437            .ok_or_else(|| JsonPointerTargetError {
438                expected: ::std::any::type_name::<BTreeMap<String, T>>(),
439                actual: pointee.name(),
440            })
441    }
442}
443
444#[cfg(feature = "indexmap")]
445impl<'a, T: JsonPointee, H: BuildHasher + 'static> JsonPointerTarget<'a>
446    for &'a indexmap::IndexMap<String, T, H>
447{
448    #[inline]
449    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
450        let any: &dyn Any = pointee;
451        any.downcast_ref::<indexmap::IndexMap<String, T, H>>()
452            .ok_or_else(|| JsonPointerTargetError {
453                expected: ::std::any::type_name::<indexmap::IndexMap<String, T, H>>(),
454                actual: pointee.name(),
455            })
456    }
457}
458
459#[cfg(feature = "serde")]
460impl<'a> JsonPointerTarget<'a> for &'a serde::de::IgnoredAny {
461    #[inline]
462    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
463        Err(JsonPointerTargetError {
464            expected: ::std::any::type_name::<serde::de::IgnoredAny>(),
465            actual: pointee.name(),
466        })
467    }
468}
469
470#[cfg(feature = "serde_json")]
471impl<'a> JsonPointerTarget<'a> for &'a serde_json::Value {
472    #[inline]
473    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
474        let any: &dyn Any = pointee;
475        any.downcast_ref::<serde_json::Value>()
476            .ok_or_else(|| JsonPointerTargetError {
477                expected: ::std::any::type_name::<serde_json::Value>(),
478                actual: pointee.name(),
479            })
480    }
481}
482
483/// Transparently resolves a [`JsonPointer`] against the contained value
484/// if [`Some`], or returns an error if [`None`].
485impl<T: JsonPointee> JsonPointee for Option<T> {
486    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
487        match self {
488            Some(value) => value.resolve(pointer),
489            None => Err({
490                #[cfg(feature = "did-you-mean")]
491                let err = JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(self));
492                #[cfg(not(feature = "did-you-mean"))]
493                let err = JsonPointerTypeError::new(pointer);
494                err
495            })?,
496        }
497    }
498}
499
500impl<T: JsonPointee> JsonPointee for Box<T> {
501    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
502        (**self).resolve(pointer)
503    }
504}
505
506impl<T: JsonPointee> JsonPointee for Arc<T> {
507    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
508        (**self).resolve(pointer)
509    }
510}
511
512impl<T: JsonPointee> JsonPointee for Rc<T> {
513    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
514        (**self).resolve(pointer)
515    }
516}
517
518impl<T: JsonPointee> JsonPointee for Vec<T> {
519    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
520        let Some(key) = pointer.head() else {
521            return Ok(self);
522        };
523        if let Some(index) = key.to_index() {
524            if let Some(item) = self.get(index) {
525                item.resolve(pointer.tail())
526            } else {
527                Err(JsonPointeeError::Index(index, 0..self.len()))
528            }
529        } else {
530            Err({
531                #[cfg(feature = "did-you-mean")]
532                let err = JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(self));
533                #[cfg(not(feature = "did-you-mean"))]
534                let err = JsonPointerTypeError::new(pointer);
535                err
536            })?
537        }
538    }
539}
540
541impl<T, H> JsonPointee for HashMap<String, T, H>
542where
543    T: JsonPointee,
544    H: BuildHasher + 'static,
545{
546    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
547        let Some(key) = pointer.head() else {
548            return Ok(self);
549        };
550        if let Some(value) = self.get(&*key.to_str()) {
551            value.resolve(pointer.tail())
552        } else {
553            Err({
554                #[cfg(feature = "did-you-mean")]
555                let err = JsonPointerKeyError::with_suggestions(
556                    key,
557                    JsonPointeeType::name_of(self),
558                    self.keys().map(|key| key.as_str()),
559                );
560                #[cfg(not(feature = "did-you-mean"))]
561                let err = JsonPointerKeyError::new(key);
562                err
563            })?
564        }
565    }
566}
567
568impl<T: JsonPointee> JsonPointee for BTreeMap<String, T> {
569    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
570        let Some(key) = pointer.head() else {
571            return Ok(self);
572        };
573        if let Some(value) = self.get(&*key.to_str()) {
574            value.resolve(pointer.tail())
575        } else {
576            Err({
577                #[cfg(feature = "did-you-mean")]
578                let err = JsonPointerKeyError::with_suggestions(
579                    key,
580                    JsonPointeeType::name_of(self),
581                    self.keys().map(|key| key.as_str()),
582                );
583                #[cfg(not(feature = "did-you-mean"))]
584                let err = JsonPointerKeyError::new(key);
585                err
586            })?
587        }
588    }
589}
590
591#[cfg(feature = "indexmap")]
592impl<T, H> JsonPointee for indexmap::IndexMap<String, T, H>
593where
594    T: JsonPointee,
595    H: BuildHasher + 'static,
596{
597    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
598        let Some(key) = pointer.head() else {
599            return Ok(self);
600        };
601        if let Some(value) = self.get(&*key.to_str()) {
602            value.resolve(pointer.tail())
603        } else {
604            Err({
605                #[cfg(feature = "did-you-mean")]
606                let err = JsonPointerKeyError::with_suggestions(
607                    key,
608                    JsonPointeeType::name_of(self),
609                    self.keys().map(|key| key.as_str()),
610                );
611                #[cfg(not(feature = "did-you-mean"))]
612                let err = JsonPointerKeyError::new(key);
613                err
614            })?
615        }
616    }
617}
618
619#[cfg(feature = "serde")]
620impl JsonPointee for serde::de::IgnoredAny {
621    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
622        Err({
623            #[cfg(feature = "did-you-mean")]
624            let err = JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(self));
625            #[cfg(not(feature = "did-you-mean"))]
626            let err = JsonPointerTypeError::new(pointer);
627            err
628        })?
629    }
630}
631
632#[cfg(feature = "serde_json")]
633impl JsonPointee for serde_json::Value {
634    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
635        let Some(key) = pointer.head() else {
636            return Ok(self);
637        };
638        match self {
639            serde_json::Value::Object(map) => {
640                if let Some(value) = map.get(&*key.to_str()) {
641                    value.resolve(pointer.tail())
642                } else {
643                    Err({
644                        #[cfg(feature = "did-you-mean")]
645                        let err = JsonPointerKeyError::with_suggestions(
646                            key,
647                            JsonPointeeType::name_of(map),
648                            map.keys().map(|key| key.as_str()),
649                        );
650                        #[cfg(not(feature = "did-you-mean"))]
651                        let err = JsonPointerKeyError::new(key);
652                        err
653                    })?
654                }
655            }
656            serde_json::Value::Array(array) => {
657                let Some(index) = key.to_index() else {
658                    return Err({
659                        #[cfg(feature = "did-you-mean")]
660                        let err =
661                            JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(array));
662                        #[cfg(not(feature = "did-you-mean"))]
663                        let err = JsonPointerTypeError::new(pointer);
664                        err
665                    })?;
666                };
667                if let Some(item) = array.get(index) {
668                    item.resolve(pointer.tail())
669                } else {
670                    Err(JsonPointeeError::Index(index, 0..array.len()))
671                }
672            }
673            serde_json::Value::Null => Err({
674                #[cfg(feature = "did-you-mean")]
675                let err = JsonPointerKeyError::with_ty(key, JsonPointeeType::name_of(self));
676                #[cfg(not(feature = "did-you-mean"))]
677                let err = JsonPointerKeyError::new(key);
678                err
679            })?,
680            _ => Err({
681                #[cfg(feature = "did-you-mean")]
682                let err = JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(self));
683                #[cfg(not(feature = "did-you-mean"))]
684                let err = JsonPointerTypeError::new(pointer);
685                err
686            })?,
687        }
688    }
689}
690
691/// An error that occurs during pointer resolution.
692#[derive(Debug, thiserror::Error)]
693pub enum JsonPointerError {
694    #[error(transparent)]
695    Syntax(#[from] JsonPointerSyntaxError),
696    #[error(transparent)]
697    Resolve(#[from] JsonPointeeError),
698    #[error("at `{pointer}`: {source}")]
699    Type {
700        pointer: JsonPointerBuf,
701        #[source]
702        source: JsonPointerTargetError,
703    },
704}
705
706/// An error that occurs during parsing.
707#[derive(Debug, thiserror::Error)]
708#[error("JSON Pointer must start with `/`")]
709pub struct JsonPointerSyntaxError;
710
711/// An error returned when a [`JsonPointerTarget`] can't extract a typed value
712/// from a type-erased [`JsonPointee`] because the pointee's type doesn't match
713/// the target type.
714#[derive(Debug, thiserror::Error)]
715#[error("expected type `{expected}`; got `{actual}`")]
716pub struct JsonPointerTargetError {
717    /// The expected type name, from [`std::any::type_name`].
718    pub expected: &'static str,
719    /// The actual type name, from [`JsonPointee::name`].
720    pub actual: &'static str,
721}
722
723/// An error that occurs during traversal.
724#[derive(Debug, thiserror::Error)]
725pub enum JsonPointeeError {
726    #[error(transparent)]
727    Key(#[from] JsonPointerKeyError),
728    #[error("index {} out of range {}..{}", .0, .1.start, .1.end)]
729    Index(usize, Range<usize>),
730    #[error(transparent)]
731    Ty(#[from] JsonPointerTypeError),
732}
733
734/// An error that occurs when a pointed-to value doesn't have a key
735/// that the pointer references, with an optional suggestion
736/// for the correct key.
737#[derive(Debug)]
738pub struct JsonPointerKeyError {
739    pub key: String,
740    pub context: Option<JsonPointerKeyErrorContext>,
741}
742
743impl JsonPointerKeyError {
744    pub fn new(key: &JsonPointerSegment) -> Self {
745        Self {
746            key: key.to_str().into_owned(),
747            context: None,
748        }
749    }
750
751    #[cfg(feature = "did-you-mean")]
752    pub fn with_ty(key: &JsonPointerSegment, ty: JsonPointeeType) -> Self {
753        Self {
754            key: key.to_str().into_owned(),
755            context: Some(JsonPointerKeyErrorContext {
756                ty,
757                suggestion: None,
758            }),
759        }
760    }
761
762    #[cfg(feature = "did-you-mean")]
763    #[cold]
764    pub fn with_suggestions<'a>(
765        key: &JsonPointerSegment,
766        ty: JsonPointeeType,
767        suggestions: impl IntoIterator<Item = &'a str>,
768    ) -> Self {
769        let key = key.to_str();
770        let suggestion = suggestions
771            .into_iter()
772            .map(|suggestion| (suggestion, strsim::jaro_winkler(&key, suggestion)))
773            .max_by(|&(_, a), &(_, b)| {
774                // `strsim::jaro_winkler` returns the Jaro-Winkler _similarity_,
775                // not distance; so higher values mean the strings are closer.
776                a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)
777            })
778            .map(|(suggestion, _)| suggestion.to_owned());
779        Self {
780            key: key.into_owned(),
781            context: Some(JsonPointerKeyErrorContext { ty, suggestion }),
782        }
783    }
784}
785
786impl std::error::Error for JsonPointerKeyError {}
787
788impl Display for JsonPointerKeyError {
789    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
790        match &self.context {
791            Some(JsonPointerKeyErrorContext {
792                ty,
793                suggestion: Some(suggestion),
794            }) => write!(
795                f,
796                "unknown key {:?} for value of {ty}; did you mean {suggestion:?}?",
797                self.key
798            ),
799            Some(JsonPointerKeyErrorContext {
800                ty,
801                suggestion: None,
802            }) => write!(f, "unknown key {:?} for value of {ty}", self.key),
803            None => write!(f, "unknown key {:?}", self.key),
804        }
805    }
806}
807
808#[derive(Debug)]
809pub struct JsonPointerKeyErrorContext {
810    pub ty: JsonPointeeType,
811    pub suggestion: Option<String>,
812}
813
814/// An error that occurs when a pointer can't be resolved
815/// against a value of the given type.
816#[derive(Debug)]
817pub struct JsonPointerTypeError {
818    pub pointer: String,
819    pub ty: Option<JsonPointeeType>,
820}
821
822impl JsonPointerTypeError {
823    pub fn new(pointer: &JsonPointer) -> Self {
824        Self {
825            pointer: pointer.to_string(),
826            ty: None,
827        }
828    }
829
830    #[cfg(feature = "did-you-mean")]
831    pub fn with_ty(pointer: &JsonPointer, ty: JsonPointeeType) -> Self {
832        Self {
833            pointer: pointer.to_string(),
834            ty: Some(ty),
835        }
836    }
837}
838
839impl std::error::Error for JsonPointerTypeError {}
840
841impl Display for JsonPointerTypeError {
842    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
843        match self.ty {
844            Some(ty) => write!(f, "can't resolve {:?} against value of {ty}", self.pointer),
845            None => write!(f, "can't resolve {:?}", self.pointer),
846        }
847    }
848}
849
850/// The name of a pointed-to type, for reporting traversal errors.
851#[derive(Clone, Copy, Debug, Eq, PartialEq)]
852pub enum JsonPointeeType {
853    Struct(JsonPointeeStructTy),
854    Variant(&'static str, JsonPointeeStructTy),
855    Named(&'static str),
856}
857
858impl JsonPointeeType {
859    #[inline]
860    pub fn struct_named(ty: &'static str) -> Self {
861        Self::Struct(JsonPointeeStructTy::Named(ty))
862    }
863
864    #[inline]
865    pub fn tuple_struct_named(ty: &'static str) -> Self {
866        Self::Struct(JsonPointeeStructTy::Tuple(ty))
867    }
868
869    #[inline]
870    pub fn unit_struct_named(ty: &'static str) -> Self {
871        Self::Struct(JsonPointeeStructTy::Unit(ty))
872    }
873
874    #[inline]
875    pub fn struct_variant_named(ty: &'static str, variant: &'static str) -> Self {
876        Self::Variant(ty, JsonPointeeStructTy::Named(variant))
877    }
878
879    #[inline]
880    pub fn tuple_variant_named(ty: &'static str, variant: &'static str) -> Self {
881        Self::Variant(ty, JsonPointeeStructTy::Tuple(variant))
882    }
883
884    #[inline]
885    pub fn unit_variant_named(ty: &'static str, variant: &'static str) -> Self {
886        Self::Variant(ty, JsonPointeeStructTy::Unit(variant))
887    }
888
889    #[inline]
890    pub fn named<T: ?Sized>() -> Self {
891        Self::Named(std::any::type_name::<T>())
892    }
893
894    #[inline]
895    pub fn name_of<T: ?Sized>(value: &T) -> Self {
896        Self::Named(std::any::type_name_of_val(value))
897    }
898}
899
900impl Display for JsonPointeeType {
901    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
902        match self {
903            Self::Struct(JsonPointeeStructTy::Named(ty)) => write!(f, "struct `{ty}`"),
904            Self::Struct(JsonPointeeStructTy::Tuple(ty)) => write!(f, "tuple struct `{ty}`"),
905            Self::Struct(JsonPointeeStructTy::Unit(ty)) => write!(f, "unit struct `{ty}`"),
906            Self::Variant(ty, JsonPointeeStructTy::Named(variant)) => {
907                write!(f, "variant `{variant}` of `{ty}`")
908            }
909            Self::Variant(ty, JsonPointeeStructTy::Tuple(variant)) => {
910                write!(f, "tuple variant `{variant}` of `{ty}`")
911            }
912            Self::Variant(ty, JsonPointeeStructTy::Unit(variant)) => {
913                write!(f, "unit variant `{variant}` of `{ty}`")
914            }
915            Self::Named(ty) => write!(f, "type `{ty}`"),
916        }
917    }
918}
919
920/// The name of a pointed-to struct type or enum variant,
921/// for reporting traversal errors.
922#[derive(Clone, Copy, Debug, Eq, PartialEq)]
923pub enum JsonPointeeStructTy {
924    Named(&'static str),
925    Tuple(&'static str),
926    Unit(&'static str),
927}
928
929#[cfg(test)]
930mod tests {
931    use super::*;
932
933    #[test]
934    fn test_segments() {
935        let pointer = JsonPointer::parse("/foo/bar/0").unwrap();
936        let mut segments = pointer.segments();
937        assert_eq!(segments.next().unwrap(), "foo");
938        assert_eq!(segments.next().unwrap(), "bar");
939        // `"0"` is parsed as a string segment, but implementations for `Vec`
940        // and tuple structs will parse it as an index.
941        assert_eq!(segments.next().unwrap(), "0");
942        assert_eq!(segments.next(), None);
943    }
944
945    #[test]
946    fn test_escaped_segments() {
947        let pointer = JsonPointer::parse("/foo~1bar/baz~0qux").unwrap();
948        let mut segments = pointer.segments();
949        // `~1` unescapes to `/`, `~0` unescapes to `~`.
950        assert_eq!(segments.next().unwrap(), "foo/bar");
951        assert_eq!(segments.next().unwrap(), "baz~qux");
952        assert_eq!(segments.next(), None);
953    }
954
955    #[test]
956    fn test_segment_display() {
957        let pointer = JsonPointer::parse("/foo~1bar").unwrap();
958        let segment = pointer.head().unwrap();
959        assert_eq!(segment.to_string(), "foo/bar");
960    }
961
962    #[test]
963    fn test_pointer_display() {
964        let input = "/foo/bar~1baz/0";
965        let pointer = JsonPointer::parse(input).unwrap();
966        assert_eq!(pointer.to_string(), input);
967    }
968
969    #[test]
970    fn test_pointer_buf() {
971        let pointer: Cow<'_, JsonPointer> = JsonPointer::parse("/foo/bar~0baz").unwrap().into();
972        let owned = pointer.into_owned();
973        let mut segments = owned.segments();
974        assert_eq!(segments.next().unwrap(), "foo");
975        assert_eq!(segments.next().unwrap(), "bar~baz");
976        assert_eq!(owned.to_string(), "/foo/bar~0baz");
977    }
978
979    #[test]
980    fn test_head_tail_single_segment() {
981        let pointer = JsonPointer::parse("/foo").unwrap();
982        assert_eq!(pointer.head().unwrap(), "foo");
983        assert!(pointer.tail().is_empty());
984    }
985
986    #[test]
987    fn test_tail_root_idempotent() {
988        let root = JsonPointer::empty();
989        assert!(root.tail().is_empty());
990        assert!(root.tail().tail().is_empty());
991    }
992
993    #[test]
994    fn test_trailing_slash_produces_empty_segment() {
995        let pointer = JsonPointer::parse("/foo/").unwrap();
996        let mut segments = pointer.segments();
997        assert_eq!(segments.next().unwrap(), "foo");
998        assert_eq!(segments.next().unwrap(), "");
999        assert_eq!(segments.next(), None);
1000
1001        // `head()` returns the first segment; `tail()` preserves the
1002        // trailing slash as a pointer with one empty segment.
1003        assert_eq!(pointer.head().unwrap(), "foo");
1004        let tail = pointer.tail();
1005        assert_eq!(tail.head().unwrap(), "");
1006        assert!(tail.tail().is_empty());
1007    }
1008
1009    #[test]
1010    fn test_consecutive_slashes() {
1011        let pointer = JsonPointer::parse("//").unwrap();
1012        let mut segments = pointer.segments();
1013        assert_eq!(segments.next().unwrap(), "");
1014        assert_eq!(segments.next().unwrap(), "");
1015        assert_eq!(segments.next(), None);
1016
1017        assert_eq!(pointer.head().unwrap(), "");
1018        let tail = pointer.tail();
1019        assert_eq!(tail.head().unwrap(), "");
1020        assert!(tail.tail().is_empty());
1021    }
1022
1023    #[test]
1024    fn test_parse_missing_leading_slash() {
1025        assert!(JsonPointer::parse("foo").is_err());
1026        assert!(JsonPointerBuf::parse("foo".to_owned()).is_err());
1027    }
1028
1029    #[test]
1030    fn test_resolve_vec() {
1031        let data = vec![1, 2, 3];
1032        let pointer = JsonPointer::parse("/1").unwrap();
1033        let result = data.resolve(pointer).unwrap() as &dyn Any;
1034        assert_eq!(result.downcast_ref::<i32>(), Some(&2));
1035    }
1036
1037    #[test]
1038    fn test_resolve_hashmap() {
1039        let mut data = HashMap::new();
1040        data.insert("foo".to_string(), 42);
1041
1042        let pointer = JsonPointer::parse("/foo").unwrap();
1043        let result = data.resolve(pointer).unwrap() as &dyn Any;
1044        assert_eq!(result.downcast_ref::<i32>(), Some(&42));
1045    }
1046
1047    #[test]
1048    fn test_resolve_option() {
1049        let data = Some(42);
1050        let pointer = JsonPointer::parse("").unwrap();
1051        let result = data.resolve(pointer).unwrap() as &dyn Any;
1052        assert_eq!(result.downcast_ref::<i32>(), Some(&42));
1053    }
1054
1055    #[test]
1056    fn test_primitive_empty_path() {
1057        let data = 42;
1058        let pointer = JsonPointer::parse("").unwrap();
1059        let result = data.resolve(pointer).unwrap() as &dyn Any;
1060        assert_eq!(result.downcast_ref::<i32>(), Some(&42));
1061    }
1062
1063    #[test]
1064    fn test_primitive_non_empty_path() {
1065        let data = 42;
1066        let pointer = JsonPointer::parse("/foo").unwrap();
1067        assert!(data.resolve(pointer).is_err());
1068    }
1069
1070    #[test]
1071    fn test_pointer_vec_element() {
1072        let data = vec![10, 20, 30];
1073        let result: i32 = data.pointer("/1").unwrap();
1074        assert_eq!(result, 20);
1075    }
1076
1077    #[test]
1078    fn test_pointer_hashmap_value() {
1079        let mut data = HashMap::new();
1080        data.insert("foo".to_owned(), 42);
1081        let result: i32 = data.pointer("/foo").unwrap();
1082        assert_eq!(result, 42);
1083    }
1084
1085    #[test]
1086    fn test_pointer_root() {
1087        let data = 42;
1088        let result: i32 = data.pointer("").unwrap();
1089        assert_eq!(result, 42);
1090    }
1091
1092    #[test]
1093    fn test_pointer_syntax_error() {
1094        let data = 42;
1095        assert!(matches!(
1096            data.pointer::<i32>("no-slash"),
1097            Err(JsonPointerError::Syntax(_))
1098        ));
1099    }
1100
1101    #[test]
1102    fn test_pointer_resolve_error() {
1103        let data = 42;
1104        assert!(matches!(
1105            data.pointer::<i32>("/foo"),
1106            Err(JsonPointerError::Resolve(_))
1107        ));
1108    }
1109
1110    #[test]
1111    fn test_pointer_cast_error() {
1112        let data = vec![42];
1113        let err = data.pointer::<&str>("/0").unwrap_err();
1114        assert!(matches!(err, JsonPointerError::Type { .. }));
1115    }
1116
1117    #[test]
1118    fn test_from_pointee_i32() {
1119        let data = 42i32;
1120        let pointee: &dyn JsonPointee = &data;
1121        let result = i32::from_pointee(pointee).unwrap();
1122        assert_eq!(result, 42);
1123    }
1124
1125    #[test]
1126    fn test_from_pointee_bool() {
1127        let data = true;
1128        let pointee: &dyn JsonPointee = &data;
1129        let result = bool::from_pointee(pointee).unwrap();
1130        assert!(result);
1131    }
1132
1133    #[test]
1134    fn test_from_pointee_wrong_type() {
1135        let data = 42i32;
1136        let pointee: &dyn JsonPointee = &data;
1137        let result = bool::from_pointee(pointee);
1138        assert!(result.is_err());
1139    }
1140}