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);
328
329macro_rules! impl_copied_pointer_target_for {
330    () => {};
331    (#[$($attrs:tt)+] $ty:ty $(, $($rest:tt)*)?) => {
332        #[$($attrs)*]
333        impl_copied_pointer_target_for!($ty);
334        $(impl_copied_pointer_target_for!($($rest)*);)?
335    };
336    ($ty:ty $(, $($rest:tt)*)?) => {
337        impl<'a> JsonPointerTarget<'a> for $ty {
338            #[inline]
339            fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
340                let any: &dyn Any = pointee;
341                any.downcast_ref::<$ty>().copied().ok_or_else(|| JsonPointerTargetError {
342                    expected: ::std::any::type_name::<$ty>(),
343                    actual: pointee.name(),
344                })
345            }
346        }
347        $(impl_copied_pointer_target_for!($($rest)*);)?
348    };
349}
350
351impl_copied_pointer_target_for!(
352    i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize, f32, f64, bool,
353    #[cfg(feature = "chrono")] chrono::DateTime<chrono::Utc>,
354    #[cfg(feature = "chrono")] chrono::NaiveDate,
355);
356
357impl<'a> JsonPointerTarget<'a> for &'a str {
358    #[inline]
359    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
360        let any: &dyn Any = pointee;
361        if let Some(s) = any.downcast_ref::<String>() {
362            Ok(s.as_str())
363        } else if let Some(&s) = any.downcast_ref::<&str>() {
364            Ok(s)
365        } else {
366            Err(JsonPointerTargetError {
367                expected: "str",
368                actual: pointee.name(),
369            })
370        }
371    }
372}
373
374impl<'a, T: JsonPointee> JsonPointerTarget<'a> for &'a [T] {
375    #[inline]
376    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
377        let any: &dyn Any = pointee;
378        if let Some(v) = any.downcast_ref::<Vec<T>>() {
379            Ok(v.as_slice())
380        } else if let Some(v) = any.downcast_ref::<&[T]>() {
381            Ok(v)
382        } else {
383            Err(JsonPointerTargetError {
384                expected: ::std::any::type_name::<[T]>(),
385                actual: pointee.name(),
386            })
387        }
388    }
389}
390
391#[cfg(feature = "url")]
392impl<'a> JsonPointerTarget<'a> for &'a url::Url {
393    #[inline]
394    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
395        let any: &dyn Any = pointee;
396        any.downcast_ref::<url::Url>()
397            .ok_or_else(|| JsonPointerTargetError {
398                expected: ::std::any::type_name::<url::Url>(),
399                actual: pointee.name(),
400            })
401    }
402}
403
404impl<'a, T: JsonPointee, H: BuildHasher + 'static> JsonPointerTarget<'a>
405    for &'a HashMap<String, T, H>
406{
407    #[inline]
408    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
409        let any: &dyn Any = pointee;
410        any.downcast_ref::<HashMap<String, T, H>>()
411            .ok_or_else(|| JsonPointerTargetError {
412                expected: ::std::any::type_name::<HashMap<String, T, H>>(),
413                actual: pointee.name(),
414            })
415    }
416}
417
418impl<'a, T: JsonPointee> JsonPointerTarget<'a> for &'a BTreeMap<String, T> {
419    #[inline]
420    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
421        let any: &dyn Any = pointee;
422        any.downcast_ref::<BTreeMap<String, T>>()
423            .ok_or_else(|| JsonPointerTargetError {
424                expected: ::std::any::type_name::<BTreeMap<String, T>>(),
425                actual: pointee.name(),
426            })
427    }
428}
429
430#[cfg(feature = "indexmap")]
431impl<'a, T: JsonPointee, H: BuildHasher + 'static> JsonPointerTarget<'a>
432    for &'a indexmap::IndexMap<String, T, H>
433{
434    #[inline]
435    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
436        let any: &dyn Any = pointee;
437        any.downcast_ref::<indexmap::IndexMap<String, T, H>>()
438            .ok_or_else(|| JsonPointerTargetError {
439                expected: ::std::any::type_name::<indexmap::IndexMap<String, T, H>>(),
440                actual: pointee.name(),
441            })
442    }
443}
444
445#[cfg(feature = "serde_json")]
446impl<'a> JsonPointerTarget<'a> for &'a serde_json::Value {
447    #[inline]
448    fn from_pointee(pointee: &'a dyn JsonPointee) -> Result<Self, JsonPointerTargetError> {
449        let any: &dyn Any = pointee;
450        any.downcast_ref::<serde_json::Value>()
451            .ok_or_else(|| JsonPointerTargetError {
452                expected: ::std::any::type_name::<serde_json::Value>(),
453                actual: pointee.name(),
454            })
455    }
456}
457
458/// Transparently resolves a [`JsonPointer`] against the contained value
459/// if [`Some`], or returns an error if [`None`].
460impl<T: JsonPointee> JsonPointee for Option<T> {
461    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
462        match self {
463            Some(value) => value.resolve(pointer),
464            None => Err({
465                #[cfg(feature = "did-you-mean")]
466                let err = JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(self));
467                #[cfg(not(feature = "did-you-mean"))]
468                let err = JsonPointerTypeError::new(pointer);
469                err
470            })?,
471        }
472    }
473}
474
475impl<T: JsonPointee> JsonPointee for Box<T> {
476    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
477        (**self).resolve(pointer)
478    }
479}
480
481impl<T: JsonPointee> JsonPointee for Arc<T> {
482    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
483        (**self).resolve(pointer)
484    }
485}
486
487impl<T: JsonPointee> JsonPointee for Rc<T> {
488    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
489        (**self).resolve(pointer)
490    }
491}
492
493impl<T: JsonPointee> JsonPointee for Vec<T> {
494    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
495        let Some(key) = pointer.head() else {
496            return Ok(self);
497        };
498        if let Some(index) = key.to_index() {
499            if let Some(item) = self.get(index) {
500                item.resolve(pointer.tail())
501            } else {
502                Err(JsonPointeeError::Index(index, 0..self.len()))
503            }
504        } else {
505            Err({
506                #[cfg(feature = "did-you-mean")]
507                let err = JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(self));
508                #[cfg(not(feature = "did-you-mean"))]
509                let err = JsonPointerTypeError::new(pointer);
510                err
511            })?
512        }
513    }
514}
515
516impl<T, H> JsonPointee for HashMap<String, T, H>
517where
518    T: JsonPointee,
519    H: BuildHasher + 'static,
520{
521    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
522        let Some(key) = pointer.head() else {
523            return Ok(self);
524        };
525        if let Some(value) = self.get(&*key.to_str()) {
526            value.resolve(pointer.tail())
527        } else {
528            Err({
529                #[cfg(feature = "did-you-mean")]
530                let err = JsonPointerKeyError::with_suggestions(
531                    key,
532                    JsonPointeeType::name_of(self),
533                    self.keys().map(|key| key.as_str()),
534                );
535                #[cfg(not(feature = "did-you-mean"))]
536                let err = JsonPointerKeyError::new(key);
537                err
538            })?
539        }
540    }
541}
542
543impl<T: JsonPointee> JsonPointee for BTreeMap<String, T> {
544    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
545        let Some(key) = pointer.head() else {
546            return Ok(self);
547        };
548        if let Some(value) = self.get(&*key.to_str()) {
549            value.resolve(pointer.tail())
550        } else {
551            Err({
552                #[cfg(feature = "did-you-mean")]
553                let err = JsonPointerKeyError::with_suggestions(
554                    key,
555                    JsonPointeeType::name_of(self),
556                    self.keys().map(|key| key.as_str()),
557                );
558                #[cfg(not(feature = "did-you-mean"))]
559                let err = JsonPointerKeyError::new(key);
560                err
561            })?
562        }
563    }
564}
565
566#[cfg(feature = "indexmap")]
567impl<T, H> JsonPointee for indexmap::IndexMap<String, T, H>
568where
569    T: JsonPointee,
570    H: BuildHasher + 'static,
571{
572    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
573        let Some(key) = pointer.head() else {
574            return Ok(self);
575        };
576        if let Some(value) = self.get(&*key.to_str()) {
577            value.resolve(pointer.tail())
578        } else {
579            Err({
580                #[cfg(feature = "did-you-mean")]
581                let err = JsonPointerKeyError::with_suggestions(
582                    key,
583                    JsonPointeeType::name_of(self),
584                    self.keys().map(|key| key.as_str()),
585                );
586                #[cfg(not(feature = "did-you-mean"))]
587                let err = JsonPointerKeyError::new(key);
588                err
589            })?
590        }
591    }
592}
593
594#[cfg(feature = "serde_json")]
595impl JsonPointee for serde_json::Value {
596    fn resolve(&self, pointer: &JsonPointer) -> Result<&dyn JsonPointee, JsonPointeeError> {
597        let Some(key) = pointer.head() else {
598            return Ok(self);
599        };
600        match self {
601            serde_json::Value::Object(map) => {
602                if let Some(value) = map.get(&*key.to_str()) {
603                    value.resolve(pointer.tail())
604                } else {
605                    Err({
606                        #[cfg(feature = "did-you-mean")]
607                        let err = JsonPointerKeyError::with_suggestions(
608                            key,
609                            JsonPointeeType::name_of(map),
610                            map.keys().map(|key| key.as_str()),
611                        );
612                        #[cfg(not(feature = "did-you-mean"))]
613                        let err = JsonPointerKeyError::new(key);
614                        err
615                    })?
616                }
617            }
618            serde_json::Value::Array(array) => {
619                let Some(index) = key.to_index() else {
620                    return Err({
621                        #[cfg(feature = "did-you-mean")]
622                        let err =
623                            JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(array));
624                        #[cfg(not(feature = "did-you-mean"))]
625                        let err = JsonPointerTypeError::new(pointer);
626                        err
627                    })?;
628                };
629                if let Some(item) = array.get(index) {
630                    item.resolve(pointer.tail())
631                } else {
632                    Err(JsonPointeeError::Index(index, 0..array.len()))
633                }
634            }
635            serde_json::Value::Null => Err({
636                #[cfg(feature = "did-you-mean")]
637                let err = JsonPointerKeyError::with_ty(key, JsonPointeeType::name_of(self));
638                #[cfg(not(feature = "did-you-mean"))]
639                let err = JsonPointerKeyError::new(key);
640                err
641            })?,
642            _ => Err({
643                #[cfg(feature = "did-you-mean")]
644                let err = JsonPointerTypeError::with_ty(pointer, JsonPointeeType::name_of(self));
645                #[cfg(not(feature = "did-you-mean"))]
646                let err = JsonPointerTypeError::new(pointer);
647                err
648            })?,
649        }
650    }
651}
652
653/// An error that occurs during pointer resolution.
654#[derive(Debug, thiserror::Error)]
655pub enum JsonPointerError {
656    #[error(transparent)]
657    Syntax(#[from] JsonPointerSyntaxError),
658    #[error(transparent)]
659    Resolve(#[from] JsonPointeeError),
660    #[error("at `{pointer}`: {source}")]
661    Type {
662        pointer: JsonPointerBuf,
663        #[source]
664        source: JsonPointerTargetError,
665    },
666}
667
668/// An error that occurs during parsing.
669#[derive(Debug, thiserror::Error)]
670#[error("JSON Pointer must start with `/`")]
671pub struct JsonPointerSyntaxError;
672
673/// An error returned when a [`JsonPointerTarget`] can't extract a typed value
674/// from a type-erased [`JsonPointee`] because the pointee's type doesn't match
675/// the target type.
676#[derive(Debug, thiserror::Error)]
677#[error("expected type `{expected}`; got `{actual}`")]
678pub struct JsonPointerTargetError {
679    /// The expected type name, from [`std::any::type_name`].
680    pub expected: &'static str,
681    /// The actual type name, from [`JsonPointee::name`].
682    pub actual: &'static str,
683}
684
685/// An error that occurs during traversal.
686#[derive(Debug, thiserror::Error)]
687pub enum JsonPointeeError {
688    #[error(transparent)]
689    Key(#[from] JsonPointerKeyError),
690    #[error("index {} out of range {}..{}", .0, .1.start, .1.end)]
691    Index(usize, Range<usize>),
692    #[error(transparent)]
693    Ty(#[from] JsonPointerTypeError),
694}
695
696/// An error that occurs when a pointed-to value doesn't have a key
697/// that the pointer references, with an optional suggestion
698/// for the correct key.
699#[derive(Debug)]
700pub struct JsonPointerKeyError {
701    pub key: String,
702    pub context: Option<JsonPointerKeyErrorContext>,
703}
704
705impl JsonPointerKeyError {
706    pub fn new(key: &JsonPointerSegment) -> Self {
707        Self {
708            key: key.to_str().into_owned(),
709            context: None,
710        }
711    }
712
713    #[cfg(feature = "did-you-mean")]
714    pub fn with_ty(key: &JsonPointerSegment, ty: JsonPointeeType) -> Self {
715        Self {
716            key: key.to_str().into_owned(),
717            context: Some(JsonPointerKeyErrorContext {
718                ty,
719                suggestion: None,
720            }),
721        }
722    }
723
724    #[cfg(feature = "did-you-mean")]
725    #[cold]
726    pub fn with_suggestions<'a>(
727        key: &JsonPointerSegment,
728        ty: JsonPointeeType,
729        suggestions: impl IntoIterator<Item = &'a str>,
730    ) -> Self {
731        let key = key.to_str();
732        let suggestion = suggestions
733            .into_iter()
734            .map(|suggestion| (suggestion, strsim::jaro_winkler(&key, suggestion)))
735            .max_by(|&(_, a), &(_, b)| {
736                // `strsim::jaro_winkler` returns the Jaro-Winkler _similarity_,
737                // not distance; so higher values mean the strings are closer.
738                a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)
739            })
740            .map(|(suggestion, _)| suggestion.to_owned());
741        Self {
742            key: key.into_owned(),
743            context: Some(JsonPointerKeyErrorContext { ty, suggestion }),
744        }
745    }
746}
747
748impl std::error::Error for JsonPointerKeyError {}
749
750impl Display for JsonPointerKeyError {
751    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
752        match &self.context {
753            Some(JsonPointerKeyErrorContext {
754                ty,
755                suggestion: Some(suggestion),
756            }) => write!(
757                f,
758                "unknown key {:?} for value of {ty}; did you mean {suggestion:?}?",
759                self.key
760            ),
761            Some(JsonPointerKeyErrorContext {
762                ty,
763                suggestion: None,
764            }) => write!(f, "unknown key {:?} for value of {ty}", self.key),
765            None => write!(f, "unknown key {:?}", self.key),
766        }
767    }
768}
769
770#[derive(Debug)]
771pub struct JsonPointerKeyErrorContext {
772    pub ty: JsonPointeeType,
773    pub suggestion: Option<String>,
774}
775
776/// An error that occurs when a pointer can't be resolved
777/// against a value of the given type.
778#[derive(Debug)]
779pub struct JsonPointerTypeError {
780    pub pointer: String,
781    pub ty: Option<JsonPointeeType>,
782}
783
784impl JsonPointerTypeError {
785    pub fn new(pointer: &JsonPointer) -> Self {
786        Self {
787            pointer: pointer.to_string(),
788            ty: None,
789        }
790    }
791
792    #[cfg(feature = "did-you-mean")]
793    pub fn with_ty(pointer: &JsonPointer, ty: JsonPointeeType) -> Self {
794        Self {
795            pointer: pointer.to_string(),
796            ty: Some(ty),
797        }
798    }
799}
800
801impl std::error::Error for JsonPointerTypeError {}
802
803impl Display for JsonPointerTypeError {
804    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
805        match self.ty {
806            Some(ty) => write!(f, "can't resolve {:?} against value of {ty}", self.pointer),
807            None => write!(f, "can't resolve {:?}", self.pointer),
808        }
809    }
810}
811
812/// The name of a pointed-to type, for reporting traversal errors.
813#[derive(Clone, Copy, Debug, Eq, PartialEq)]
814pub enum JsonPointeeType {
815    Struct(JsonPointeeStructTy),
816    Variant(&'static str, JsonPointeeStructTy),
817    Named(&'static str),
818}
819
820impl JsonPointeeType {
821    #[inline]
822    pub fn struct_named(ty: &'static str) -> Self {
823        Self::Struct(JsonPointeeStructTy::Named(ty))
824    }
825
826    #[inline]
827    pub fn tuple_struct_named(ty: &'static str) -> Self {
828        Self::Struct(JsonPointeeStructTy::Tuple(ty))
829    }
830
831    #[inline]
832    pub fn unit_struct_named(ty: &'static str) -> Self {
833        Self::Struct(JsonPointeeStructTy::Unit(ty))
834    }
835
836    #[inline]
837    pub fn struct_variant_named(ty: &'static str, variant: &'static str) -> Self {
838        Self::Variant(ty, JsonPointeeStructTy::Named(variant))
839    }
840
841    #[inline]
842    pub fn tuple_variant_named(ty: &'static str, variant: &'static str) -> Self {
843        Self::Variant(ty, JsonPointeeStructTy::Tuple(variant))
844    }
845
846    #[inline]
847    pub fn unit_variant_named(ty: &'static str, variant: &'static str) -> Self {
848        Self::Variant(ty, JsonPointeeStructTy::Unit(variant))
849    }
850
851    #[inline]
852    pub fn named<T: ?Sized>() -> Self {
853        Self::Named(std::any::type_name::<T>())
854    }
855
856    #[inline]
857    pub fn name_of<T: ?Sized>(value: &T) -> Self {
858        Self::Named(std::any::type_name_of_val(value))
859    }
860}
861
862impl Display for JsonPointeeType {
863    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
864        match self {
865            Self::Struct(JsonPointeeStructTy::Named(ty)) => write!(f, "struct `{ty}`"),
866            Self::Struct(JsonPointeeStructTy::Tuple(ty)) => write!(f, "tuple struct `{ty}`"),
867            Self::Struct(JsonPointeeStructTy::Unit(ty)) => write!(f, "unit struct `{ty}`"),
868            Self::Variant(ty, JsonPointeeStructTy::Named(variant)) => {
869                write!(f, "variant `{variant}` of `{ty}`")
870            }
871            Self::Variant(ty, JsonPointeeStructTy::Tuple(variant)) => {
872                write!(f, "tuple variant `{variant}` of `{ty}`")
873            }
874            Self::Variant(ty, JsonPointeeStructTy::Unit(variant)) => {
875                write!(f, "unit variant `{variant}` of `{ty}`")
876            }
877            Self::Named(ty) => write!(f, "type `{ty}`"),
878        }
879    }
880}
881
882/// The name of a pointed-to struct type or enum variant,
883/// for reporting traversal errors.
884#[derive(Clone, Copy, Debug, Eq, PartialEq)]
885pub enum JsonPointeeStructTy {
886    Named(&'static str),
887    Tuple(&'static str),
888    Unit(&'static str),
889}
890
891#[cfg(test)]
892mod tests {
893    use super::*;
894
895    #[test]
896    fn test_segments() {
897        let pointer = JsonPointer::parse("/foo/bar/0").unwrap();
898        let mut segments = pointer.segments();
899        assert_eq!(segments.next().unwrap(), "foo");
900        assert_eq!(segments.next().unwrap(), "bar");
901        // `"0"` is parsed as a string segment, but implementations for `Vec`
902        // and tuple structs will parse it as an index.
903        assert_eq!(segments.next().unwrap(), "0");
904        assert_eq!(segments.next(), None);
905    }
906
907    #[test]
908    fn test_escaped_segments() {
909        let pointer = JsonPointer::parse("/foo~1bar/baz~0qux").unwrap();
910        let mut segments = pointer.segments();
911        // `~1` unescapes to `/`, `~0` unescapes to `~`.
912        assert_eq!(segments.next().unwrap(), "foo/bar");
913        assert_eq!(segments.next().unwrap(), "baz~qux");
914        assert_eq!(segments.next(), None);
915    }
916
917    #[test]
918    fn test_segment_display() {
919        let pointer = JsonPointer::parse("/foo~1bar").unwrap();
920        let segment = pointer.head().unwrap();
921        assert_eq!(segment.to_string(), "foo/bar");
922    }
923
924    #[test]
925    fn test_pointer_display() {
926        let input = "/foo/bar~1baz/0";
927        let pointer = JsonPointer::parse(input).unwrap();
928        assert_eq!(pointer.to_string(), input);
929    }
930
931    #[test]
932    fn test_pointer_buf() {
933        let pointer: Cow<'_, JsonPointer> = JsonPointer::parse("/foo/bar~0baz").unwrap().into();
934        let owned = pointer.into_owned();
935        let mut segments = owned.segments();
936        assert_eq!(segments.next().unwrap(), "foo");
937        assert_eq!(segments.next().unwrap(), "bar~baz");
938        assert_eq!(owned.to_string(), "/foo/bar~0baz");
939    }
940
941    #[test]
942    fn test_head_tail_single_segment() {
943        let pointer = JsonPointer::parse("/foo").unwrap();
944        assert_eq!(pointer.head().unwrap(), "foo");
945        assert!(pointer.tail().is_empty());
946    }
947
948    #[test]
949    fn test_tail_root_idempotent() {
950        let root = JsonPointer::empty();
951        assert!(root.tail().is_empty());
952        assert!(root.tail().tail().is_empty());
953    }
954
955    #[test]
956    fn test_trailing_slash_produces_empty_segment() {
957        let pointer = JsonPointer::parse("/foo/").unwrap();
958        let mut segments = pointer.segments();
959        assert_eq!(segments.next().unwrap(), "foo");
960        assert_eq!(segments.next().unwrap(), "");
961        assert_eq!(segments.next(), None);
962
963        // `head()` returns the first segment; `tail()` preserves the
964        // trailing slash as a pointer with one empty segment.
965        assert_eq!(pointer.head().unwrap(), "foo");
966        let tail = pointer.tail();
967        assert_eq!(tail.head().unwrap(), "");
968        assert!(tail.tail().is_empty());
969    }
970
971    #[test]
972    fn test_consecutive_slashes() {
973        let pointer = JsonPointer::parse("//").unwrap();
974        let mut segments = pointer.segments();
975        assert_eq!(segments.next().unwrap(), "");
976        assert_eq!(segments.next().unwrap(), "");
977        assert_eq!(segments.next(), None);
978
979        assert_eq!(pointer.head().unwrap(), "");
980        let tail = pointer.tail();
981        assert_eq!(tail.head().unwrap(), "");
982        assert!(tail.tail().is_empty());
983    }
984
985    #[test]
986    fn test_parse_missing_leading_slash() {
987        assert!(JsonPointer::parse("foo").is_err());
988        assert!(JsonPointerBuf::parse("foo".to_owned()).is_err());
989    }
990
991    #[test]
992    fn test_resolve_vec() {
993        let data = vec![1, 2, 3];
994        let pointer = JsonPointer::parse("/1").unwrap();
995        let result = data.resolve(pointer).unwrap() as &dyn Any;
996        assert_eq!(result.downcast_ref::<i32>(), Some(&2));
997    }
998
999    #[test]
1000    fn test_resolve_hashmap() {
1001        let mut data = HashMap::new();
1002        data.insert("foo".to_string(), 42);
1003
1004        let pointer = JsonPointer::parse("/foo").unwrap();
1005        let result = data.resolve(pointer).unwrap() as &dyn Any;
1006        assert_eq!(result.downcast_ref::<i32>(), Some(&42));
1007    }
1008
1009    #[test]
1010    fn test_resolve_option() {
1011        let data = Some(42);
1012        let pointer = JsonPointer::parse("").unwrap();
1013        let result = data.resolve(pointer).unwrap() as &dyn Any;
1014        assert_eq!(result.downcast_ref::<i32>(), Some(&42));
1015    }
1016
1017    #[test]
1018    fn test_primitive_empty_path() {
1019        let data = 42;
1020        let pointer = JsonPointer::parse("").unwrap();
1021        let result = data.resolve(pointer).unwrap() as &dyn Any;
1022        assert_eq!(result.downcast_ref::<i32>(), Some(&42));
1023    }
1024
1025    #[test]
1026    fn test_primitive_non_empty_path() {
1027        let data = 42;
1028        let pointer = JsonPointer::parse("/foo").unwrap();
1029        assert!(data.resolve(pointer).is_err());
1030    }
1031
1032    #[test]
1033    fn test_pointer_vec_element() {
1034        let data = vec![10, 20, 30];
1035        let result: i32 = data.pointer("/1").unwrap();
1036        assert_eq!(result, 20);
1037    }
1038
1039    #[test]
1040    fn test_pointer_hashmap_value() {
1041        let mut data = HashMap::new();
1042        data.insert("foo".to_owned(), 42);
1043        let result: i32 = data.pointer("/foo").unwrap();
1044        assert_eq!(result, 42);
1045    }
1046
1047    #[test]
1048    fn test_pointer_root() {
1049        let data = 42;
1050        let result: i32 = data.pointer("").unwrap();
1051        assert_eq!(result, 42);
1052    }
1053
1054    #[test]
1055    fn test_pointer_syntax_error() {
1056        let data = 42;
1057        assert!(matches!(
1058            data.pointer::<i32>("no-slash"),
1059            Err(JsonPointerError::Syntax(_))
1060        ));
1061    }
1062
1063    #[test]
1064    fn test_pointer_resolve_error() {
1065        let data = 42;
1066        assert!(matches!(
1067            data.pointer::<i32>("/foo"),
1068            Err(JsonPointerError::Resolve(_))
1069        ));
1070    }
1071
1072    #[test]
1073    fn test_pointer_cast_error() {
1074        let data = vec![42];
1075        let err = data.pointer::<&str>("/0").unwrap_err();
1076        assert!(matches!(err, JsonPointerError::Type { .. }));
1077    }
1078
1079    #[test]
1080    fn test_from_pointee_i32() {
1081        let data = 42i32;
1082        let pointee: &dyn JsonPointee = &data;
1083        let result = i32::from_pointee(pointee).unwrap();
1084        assert_eq!(result, 42);
1085    }
1086
1087    #[test]
1088    fn test_from_pointee_bool() {
1089        let data = true;
1090        let pointee: &dyn JsonPointee = &data;
1091        let result = bool::from_pointee(pointee).unwrap();
1092        assert!(result);
1093    }
1094
1095    #[test]
1096    fn test_from_pointee_wrong_type() {
1097        let data = 42i32;
1098        let pointee: &dyn JsonPointee = &data;
1099        let result = bool::from_pointee(pointee);
1100        assert!(result.is_err());
1101    }
1102}