From 45c4fdef8618d9f4dae3f23fe7b04fba4e76060a Mon Sep 17 00:00:00 2001 From: Chance Date: Mon, 21 Oct 2024 14:09:05 -0400 Subject: [PATCH 01/30] * Renames `crate::resolve::ResolveError` to `crate::resolve::Error` * Adds type alias `crate::resolve::ResolveError` for `crate::resolve::Error` * Renames `crate::assign::AssignError` to `crate::assign::Error` * Adds type alias `crate::assign::AssignError` for crate::assign::Error` * Adds `position` (token index) to variants of `assign::Error` & `resolve::Error` --- Cargo.toml | 2 +- src/assign.rs | 126 +++++++++++++------- src/resolve.rs | 304 +++++++++++++++++++++++++++---------------------- src/token.rs | 49 +------- 4 files changed, 255 insertions(+), 226 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e4857e2..ae3d3d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,7 @@ syn = { version = "1.0.109", optional = true } [features] assign = [] -default = ["std", "serde", "json", "resolve", "assign", "delete"] +default = ["std", "serde", "json", "toml", "resolve", "assign", "delete"] delete = ["resolve"] json = ["dep:serde_json", "serde"] resolve = [] diff --git a/src/assign.rs b/src/assign.rs index 3381b27..e8386bb 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -133,27 +133,41 @@ pub trait Assign { ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ ║ ║ -║ AssignError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +║ Error ║ +║ ¯¯¯¯¯¯¯ ║ ╚══════════════════════════════════════════════════════════════════════════════╝ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ +// TODO: should AssignError be deprecated? +/// Alias for [`Error`]. +/// +/// Possible error returned from [`Assign`] implementations for +/// [`serde_json::Value`] and +/// [`toml::Value`](https://docs.rs/toml/0.8.14/toml/index.html). +pub type AssignError = Error; + /// Possible error returned from [`Assign`] implementations for /// [`serde_json::Value`] and /// [`toml::Value`](https://docs.rs/toml/0.8.14/toml/index.html). #[derive(Debug, PartialEq, Eq)] -pub enum AssignError { - /// A `Token` within the `Pointer` failed to be parsed as an array index. +pub enum Error { + /// A [`Token`] within the [`Pointer`] failed to be parsed as an array index. FailedToParseIndex { + /// Position (index) of the token which failed to parse as an `Index` + position: usize, /// Offset of the partial pointer starting with the invalid index. offset: usize, /// The source [`ParseIndexError`] source: ParseIndexError, }, - /// target array. + /// A [`Token`] within the [`Pointer`] contains an [`Index`] which is out of bounds. + /// + /// The current or resulting array's length is less than the index. OutOfBounds { + /// Position (index) of the token which failed to parse as an `Index` + position: usize, /// Offset of the partial pointer starting with the invalid index. offset: usize, /// The source [`OutOfBoundsError`] @@ -161,19 +175,16 @@ pub enum AssignError { }, } -impl fmt::Display for AssignError { +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::FailedToParseIndex { offset, .. } => { - write!( - f, - "assignment failed due to an invalid index at offset {offset}" - ) + Self::FailedToParseIndex { .. } => { + write!(f, "assignment failed due to an invalid index") } - Self::OutOfBounds { offset, .. } => { + Self::OutOfBounds { .. } => { write!( f, - "assignment failed due to index at offset {offset} being out of bounds" + "assignment failed due to index an index being out of bounds" ) } } @@ -181,7 +192,7 @@ impl fmt::Display for AssignError { } #[cfg(feature = "std")] -impl std::error::Error for AssignError { +impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::FailedToParseIndex { source, .. } => Some(source), @@ -207,7 +218,7 @@ enum Assigned<'v, V> { #[cfg(feature = "json")] mod json { - use super::{Assign, AssignError, Assigned}; + use super::{Assign, Assigned, Error}; use crate::{Pointer, Token}; use alloc::{ string::{String, ToString}, @@ -235,7 +246,7 @@ mod json { } impl Assign for Value { type Value = Value; - type Error = AssignError; + type Error = Error; fn assign(&mut self, ptr: &Pointer, value: V) -> Result, Self::Error> where V: Into, @@ -248,14 +259,15 @@ mod json { mut ptr: &Pointer, mut dest: &mut Value, mut value: Value, - ) -> Result, AssignError> { + ) -> Result, Error> { let mut offset = 0; + let mut position = 0; while let Some((token, tail)) = ptr.split_front() { let tok_len = token.encoded().len(); let assigned = match dest { - Value::Array(array) => assign_array(token, tail, array, value, offset)?, + Value::Array(array) => assign_array(token, tail, array, value, position, offset)?, Value::Object(obj) => assign_object(token, tail, obj, value), _ => assign_scalar(ptr, dest, value), }; @@ -273,6 +285,7 @@ mod json { } } offset += 1 + tok_len; + position += 1; } // Pointer is root, we can replace `dest` directly @@ -285,14 +298,23 @@ mod json { remaining: &Pointer, array: &'v mut Vec, src: Value, + position: usize, offset: usize, - ) -> Result, AssignError> { + ) -> Result, Error> { // parsing the index let idx = token .to_index() - .map_err(|source| AssignError::FailedToParseIndex { offset, source })? + .map_err(|source| Error::FailedToParseIndex { + position, + offset, + source, + })? .for_len_incl(array.len()) - .map_err(|source| AssignError::OutOfBounds { offset, source })?; + .map_err(|source| Error::OutOfBounds { + position, + offset, + source, + })?; debug_assert!(idx <= array.len()); @@ -381,7 +403,7 @@ mod json { #[cfg(feature = "toml")] mod toml { - use super::{Assign, AssignError, Assigned}; + use super::{Assign, Assigned, Error}; use crate::{Pointer, Token}; use alloc::{string::String, vec, vec::Vec}; use core::mem; @@ -406,7 +428,7 @@ mod toml { impl Assign for Value { type Value = Value; - type Error = AssignError; + type Error = Error; fn assign(&mut self, ptr: &Pointer, value: V) -> Result, Self::Error> where V: Into, @@ -419,14 +441,15 @@ mod toml { mut ptr: &Pointer, mut dest: &mut Value, mut value: Value, - ) -> Result, AssignError> { + ) -> Result, Error> { let mut offset = 0; + let mut position = 0; while let Some((token, tail)) = ptr.split_front() { let tok_len = token.encoded().len(); let assigned = match dest { - Value::Array(array) => assign_array(token, tail, array, value, offset)?, + Value::Array(array) => assign_array(token, tail, array, value, position, offset)?, Value::Table(tbl) => assign_object(token, tail, tbl, value), _ => assign_scalar(ptr, dest, value), }; @@ -444,6 +467,7 @@ mod toml { } } offset += 1 + tok_len; + position += 1; } // Pointer is root, we can replace `dest` directly @@ -457,14 +481,23 @@ mod toml { remaining: &Pointer, array: &'v mut Vec, src: Value, + position: usize, offset: usize, - ) -> Result, AssignError> { + ) -> Result, Error> { // parsing the index let idx = token .to_index() - .map_err(|source| AssignError::FailedToParseIndex { offset, source })? + .map_err(|source| Error::FailedToParseIndex { + position, + offset, + source, + })? .for_len_incl(array.len()) - .map_err(|source| AssignError::OutOfBounds { offset, source })?; + .map_err(|source| Error::OutOfBounds { + position, + offset, + source, + })?; debug_assert!(idx <= array.len()); @@ -550,7 +583,7 @@ mod toml { #[cfg(test)] #[allow(clippy::too_many_lines)] mod tests { - use super::{Assign, AssignError}; + use super::{Assign, Error}; use crate::{ index::{OutOfBoundsError, ParseIndexError}, Pointer, @@ -574,9 +607,6 @@ mod tests { V::Error: Debug + PartialEq, Result, V::Error>: PartialEq, V::Error>>, { - fn all(tests: impl IntoIterator>) { - tests.into_iter().enumerate().for_each(|(i, t)| t.run(i)); - } fn run(self, i: usize) { let Test { ptr, @@ -607,7 +637,7 @@ mod tests { fn assign_json() { use alloc::vec; use serde_json::json; - Test::all([ + [ Test { ptr: "/foo", data: json!({}), @@ -731,7 +761,8 @@ mod tests { ptr: "/1", data: json!([]), assign: json!("foo"), - expected: Err(AssignError::OutOfBounds { + expected: Err(Error::OutOfBounds { + position: 0, offset: 0, source: OutOfBoundsError { index: 1, @@ -751,7 +782,8 @@ mod tests { ptr: "/a", data: json!([]), assign: json!("foo"), - expected: Err(AssignError::FailedToParseIndex { + expected: Err(Error::FailedToParseIndex { + position: 0, offset: 0, source: ParseIndexError::InvalidInteger(usize::from_str("foo").unwrap_err()), }), @@ -761,7 +793,8 @@ mod tests { ptr: "/002", data: json!([]), assign: json!("foo"), - expected: Err(AssignError::FailedToParseIndex { + expected: Err(Error::FailedToParseIndex { + position: 0, offset: 0, source: ParseIndexError::LeadingZeros, }), @@ -771,13 +804,17 @@ mod tests { ptr: "/+23", data: json!([]), assign: json!("foo"), - expected: Err(AssignError::FailedToParseIndex { + expected: Err(Error::FailedToParseIndex { + position: 0, offset: 0, source: ParseIndexError::InvalidCharacters("+".into()), }), expected_data: json!([]), }, - ]); + ] + .into_iter() + .enumerate() + .for_each(|(i, t)| t.run(i)); } /* @@ -791,7 +828,7 @@ mod tests { fn assign_toml() { use alloc::vec; use toml::{toml, Table, Value}; - Test::all([ + [ Test { data: Value::Table(toml::Table::new()), ptr: "/foo", @@ -910,7 +947,8 @@ mod tests { data: Value::Array(vec![]), ptr: "/1", assign: "foo".into(), - expected: Err(AssignError::OutOfBounds { + expected: Err(Error::OutOfBounds { + position: 0, offset: 0, source: OutOfBoundsError { index: 1, @@ -923,12 +961,16 @@ mod tests { data: Value::Array(vec![]), ptr: "/a", assign: "foo".into(), - expected: Err(AssignError::FailedToParseIndex { + expected: Err(Error::FailedToParseIndex { + position: 0, offset: 0, source: ParseIndexError::InvalidInteger(usize::from_str("foo").unwrap_err()), }), expected_data: Value::Array(vec![]), }, - ]); + ] + .into_iter() + .enumerate() + .for_each(|(i, t)| t.run(i)); } } diff --git a/src/resolve.rs b/src/resolve.rs index 1f981d1..82c23ff 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -97,15 +97,19 @@ pub trait ResolveMut { ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ ║ ║ -║ ResolveError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +║ Error ║ +║ ¯¯¯¯¯¯¯ ║ ╚══════════════════════════════════════════════════════════════════════════════╝ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ +// TODO: should ResolveError be deprecated? +/// Alias for [`Error`]. +pub type ResolveError = Error; + /// Indicates that the `Pointer` could not be resolved. #[derive(Debug, PartialEq, Eq)] -pub enum ResolveError { +pub enum Error { /// `Pointer` could not be resolved because a `Token` for an array index is /// not a valid integer or dash (`"-"`). /// @@ -118,14 +122,16 @@ pub enum ResolveError { /// assert!(ptr.resolve(&data).unwrap_err().is_failed_to_parse_index()); /// ``` FailedToParseIndex { + /// Position (index) of the token which failed to parse as an `Index` + position: usize, /// Offset of the partial pointer starting with the invalid index. offset: usize, /// The source `ParseIndexError` source: ParseIndexError, }, - /// `Pointer` could not be resolved due to an index being out of bounds - /// within an array. + /// A [`Token`] within the [`Pointer`] contains an [`Index`] which is out of + /// bounds. /// /// ## Example /// ```rust @@ -135,6 +141,8 @@ pub enum ResolveError { /// let ptr = Pointer::from_static("/foo/1"); /// assert!(ptr.resolve(&data).unwrap_err().is_out_of_bounds()); OutOfBounds { + /// Position (index) of the token which failed to parse as an `Index` + position: usize, /// Offset of the partial pointer starting with the invalid index. offset: usize, /// The source `OutOfBoundsError` @@ -152,6 +160,8 @@ pub enum ResolveError { /// assert!(ptr.resolve(&data).unwrap_err().is_not_found()); /// ``` NotFound { + /// Position (index) of the token which was not found. + position: usize, /// Offset of the pointer starting with the `Token` which was not found. offset: usize, }, @@ -169,12 +179,14 @@ pub enum ResolveError { /// assert!(err.is_unreachable()); /// ``` Unreachable { + /// Position (index) of the token which was unreachable. + position: usize, /// Offset of the pointer which was unreachable. offset: usize, }, } -impl ResolveError { +impl Error { /// Offset of the partial pointer starting with the token which caused the /// error. pub fn offset(&self) -> usize { @@ -186,6 +198,16 @@ impl ResolveError { } } + /// Position (index) of the token which caused the error. + pub fn position(&self) -> usize { + match self { + Self::FailedToParseIndex { position, .. } + | Self::OutOfBounds { position, .. } + | Self::NotFound { position, .. } + | Self::Unreachable { position, .. } => *position, + } + } + /// Returns `true` if this error is `FailedToParseIndex`; otherwise returns /// `false`. pub fn is_unreachable(&self) -> bool { @@ -211,7 +233,7 @@ impl ResolveError { } } -impl core::fmt::Display for ResolveError { +impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { Self::FailedToParseIndex { offset, .. } => { @@ -231,7 +253,7 @@ impl core::fmt::Display for ResolveError { } #[cfg(feature = "std")] -impl std::error::Error for ResolveError { +impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::FailedToParseIndex { source, .. } => Some(source), @@ -253,15 +275,16 @@ impl std::error::Error for ResolveError { #[cfg(feature = "json")] mod json { - use super::{parse_index, Pointer, Resolve, ResolveError, ResolveMut}; + use super::{parse_index, Error, Pointer, Resolve, ResolveMut}; use serde_json::Value; impl Resolve for Value { type Value = Value; - type Error = ResolveError; + type Error = Error; fn resolve(&self, mut ptr: &Pointer) -> Result<&Value, Self::Error> { let mut offset = 0; + let mut position = 0; let mut value = self; while let Some((token, rem)) = ptr.split_front() { let tok_len = token.encoded().len(); @@ -270,19 +293,28 @@ mod json { Value::Array(v) => { let idx = token .to_index() - .map_err(|source| ResolveError::FailedToParseIndex { offset, source })? + .map_err(|source| Error::FailedToParseIndex { + position, + offset, + source, + })? .for_len(v.len()) - .map_err(|source| ResolveError::OutOfBounds { offset, source })?; + .map_err(|source| Error::OutOfBounds { + position, + offset, + source, + })?; Ok(&v[idx]) } Value::Object(v) => v .get(token.decoded().as_ref()) - .ok_or(ResolveError::NotFound { offset }), + .ok_or(Error::NotFound { position, offset }), // found a leaf node but the pointer hasn't been exhausted - _ => Err(ResolveError::Unreachable { offset }), + _ => Err(Error::Unreachable { position, offset }), }?; offset += 1 + tok_len; + position += 1; } Ok(value) } @@ -290,37 +322,52 @@ mod json { impl ResolveMut for Value { type Value = Value; - type Error = ResolveError; + type Error = Error; - fn resolve_mut(&mut self, mut ptr: &Pointer) -> Result<&mut Value, ResolveError> { + fn resolve_mut(&mut self, mut ptr: &Pointer) -> Result<&mut Value, Error> { let mut offset = 0; + let mut position = 0; let mut value = self; while let Some((token, rem)) = ptr.split_front() { let tok_len = token.encoded().len(); ptr = rem; value = match value { Value::Array(array) => { - let idx = parse_index(token, array.len(), offset)?; + let idx = parse_index(token, array.len(), position, offset)?; Ok(&mut array[idx]) } Value::Object(v) => v .get_mut(token.decoded().as_ref()) - .ok_or(ResolveError::NotFound { offset }), + .ok_or(Error::NotFound { position, offset }), // found a leaf node but the pointer hasn't been exhausted - _ => Err(ResolveError::Unreachable { offset }), + _ => Err(Error::Unreachable { position, offset }), }?; offset += 1 + tok_len; + position += 1; } Ok(value) } } } -fn parse_index(token: Token, array_len: usize, offset: usize) -> Result { +fn parse_index( + token: Token, + array_len: usize, + position: usize, + offset: usize, +) -> Result { token .to_index() - .map_err(|source| ResolveError::FailedToParseIndex { offset, source })? + .map_err(|source| Error::FailedToParseIndex { + position, + offset, + source, + })? .for_len(array_len) - .map_err(|source| ResolveError::OutOfBounds { offset, source }) + .map_err(|source| Error::OutOfBounds { + position, + offset, + source, + }) } /* @@ -335,16 +382,17 @@ fn parse_index(token: Token, array_len: usize, offset: usize) -> Result Result<&Value, Self::Error> { let mut offset = 0; + let mut position = 0; let mut value = self; while let Some((token, rem)) = ptr.split_front() { let tok_len = token.encoded().len(); @@ -353,19 +401,28 @@ mod toml { Value::Array(v) => { let idx = token .to_index() - .map_err(|source| ResolveError::FailedToParseIndex { offset, source })? + .map_err(|source| Error::FailedToParseIndex { + position, + offset, + source, + })? .for_len(v.len()) - .map_err(|source| ResolveError::OutOfBounds { offset, source })?; + .map_err(|source| Error::OutOfBounds { + position, + offset, + source, + })?; Ok(&v[idx]) } Value::Table(v) => v .get(token.decoded().as_ref()) - .ok_or(ResolveError::NotFound { offset }), + .ok_or(Error::NotFound { position, offset }), // found a leaf node but the pointer hasn't been exhausted - _ => Err(ResolveError::Unreachable { offset }), + _ => Err(Error::Unreachable { position, offset }), }?; offset += 1 + tok_len; + position += 1; } Ok(value) } @@ -373,10 +430,12 @@ mod toml { impl ResolveMut for Value { type Value = Value; - type Error = ResolveError; + type Error = Error; - fn resolve_mut(&mut self, mut ptr: &Pointer) -> Result<&mut Value, ResolveError> { + fn resolve_mut(&mut self, mut ptr: &Pointer) -> Result<&mut Value, Error> { let mut offset = 0; + let mut position = 0; + let mut value = self; while let Some((token, rem)) = ptr.split_front() { let tok_len = token.encoded().len(); @@ -385,18 +444,27 @@ mod toml { Value::Array(array) => { let idx = token .to_index() - .map_err(|source| ResolveError::FailedToParseIndex { offset, source })? + .map_err(|source| Error::FailedToParseIndex { + position, + offset, + source, + })? .for_len(array.len()) - .map_err(|source| ResolveError::OutOfBounds { offset, source })?; + .map_err(|source| Error::OutOfBounds { + position, + offset, + source, + })?; Ok(&mut array[idx]) } Value::Table(v) => v .get_mut(token.decoded().as_ref()) - .ok_or(ResolveError::NotFound { offset }), + .ok_or(Error::NotFound { position, offset }), // found a leaf node but the pointer hasn't been exhausted - _ => Err(ResolveError::Unreachable { offset }), + _ => Err(Error::Unreachable { position, offset }), }?; offset += 1 + tok_len; + position += 1; } Ok(value) } @@ -415,124 +483,56 @@ mod toml { #[cfg(test)] mod tests { - use super::{Resolve, ResolveError, ResolveMut}; + use super::{Error, Resolve, ResolveMut}; use crate::{ index::{OutOfBoundsError, ParseIndexError}, Pointer, }; use core::fmt; - #[cfg(feature = "std")] - #[test] - fn resolve_error_source() { - use std::error::Error; - let err = ResolveError::FailedToParseIndex { - offset: 0, - source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()), - }; - assert!(err.source().is_some()); - - let err = ResolveError::OutOfBounds { - offset: 0, - source: OutOfBoundsError { - index: 1, - length: 0, - }, - }; - assert!(err.source().is_some()); - - let err = ResolveError::NotFound { offset: 0 }; - assert!(err.source().is_none()); - - let err = ResolveError::Unreachable { offset: 0 }; - assert!(err.source().is_none()); - } - #[test] - fn resolve_error_display() { - let err = ResolveError::FailedToParseIndex { - offset: 0, - source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()), - }; - assert_eq!(format!("{err}"), "failed to parse index at offset 0"); - - let err = ResolveError::OutOfBounds { - offset: 0, - source: OutOfBoundsError { - index: 1, - length: 0, - }, - }; - assert_eq!(format!("{err}"), "index at offset 0 out of bounds"); - - let err = ResolveError::NotFound { offset: 0 }; - - assert_eq!(format!("{err}"), "pointer starting at offset 0 not found"); - - let err = ResolveError::Unreachable { offset: 0 }; - assert_eq!( - format!("{err}"), - "pointer starting at offset 0 is unreachable" - ); - } - - #[test] - fn resolve_error_offset() { - let err = ResolveError::FailedToParseIndex { + fn resolve_error_is_unreachable() { + let err = Error::FailedToParseIndex { + position: 0, offset: 0, source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()), }; - assert_eq!(err.offset(), 0); + assert!(!err.is_unreachable()); - let err = ResolveError::OutOfBounds { + let err = Error::OutOfBounds { + position: 0, offset: 0, source: OutOfBoundsError { index: 1, length: 0, }, }; - assert_eq!(err.offset(), 0); - - let err = ResolveError::NotFound { offset: 0 }; - assert_eq!(err.offset(), 0); - - let err = ResolveError::Unreachable { offset: 0 }; - assert_eq!(err.offset(), 0); - } + assert!(!err.is_unreachable()); - #[test] - fn resolve_error_is_unreachable() { - let err = ResolveError::FailedToParseIndex { + let err = Error::NotFound { + position: 0, offset: 0, - source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()), }; assert!(!err.is_unreachable()); - let err = ResolveError::OutOfBounds { + let err = Error::Unreachable { + position: 0, offset: 0, - source: OutOfBoundsError { - index: 1, - length: 0, - }, }; - assert!(!err.is_unreachable()); - - let err = ResolveError::NotFound { offset: 0 }; - assert!(!err.is_unreachable()); - - let err = ResolveError::Unreachable { offset: 0 }; assert!(err.is_unreachable()); } #[test] fn resolve_error_is_not_found() { - let err = ResolveError::FailedToParseIndex { + let err = Error::FailedToParseIndex { + position: 0, offset: 0, source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()), }; assert!(!err.is_not_found()); - let err = ResolveError::OutOfBounds { + let err = Error::OutOfBounds { + position: 0, offset: 0, source: OutOfBoundsError { index: 1, @@ -541,22 +541,30 @@ mod tests { }; assert!(!err.is_not_found()); - let err = ResolveError::NotFound { offset: 0 }; + let err = Error::NotFound { + position: 0, + offset: 0, + }; assert!(err.is_not_found()); - let err = ResolveError::Unreachable { offset: 0 }; + let err = Error::Unreachable { + position: 0, + offset: 0, + }; assert!(!err.is_not_found()); } #[test] fn resolve_error_is_out_of_bounds() { - let err = ResolveError::FailedToParseIndex { + let err = Error::FailedToParseIndex { + position: 0, offset: 0, source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()), }; assert!(!err.is_out_of_bounds()); - let err = ResolveError::OutOfBounds { + let err = Error::OutOfBounds { + position: 0, offset: 0, source: OutOfBoundsError { index: 1, @@ -565,22 +573,30 @@ mod tests { }; assert!(err.is_out_of_bounds()); - let err = ResolveError::NotFound { offset: 0 }; + let err = Error::NotFound { + position: 0, + offset: 0, + }; assert!(!err.is_out_of_bounds()); - let err = ResolveError::Unreachable { offset: 0 }; + let err = Error::Unreachable { + position: 0, + offset: 0, + }; assert!(!err.is_out_of_bounds()); } #[test] fn resolve_error_is_failed_to_parse_index() { - let err = ResolveError::FailedToParseIndex { + let err = Error::FailedToParseIndex { + position: 0, offset: 0, source: ParseIndexError::InvalidInteger("invalid".parse::().unwrap_err()), }; assert!(err.is_failed_to_parse_index()); - let err = ResolveError::OutOfBounds { + let err = Error::OutOfBounds { + position: 0, offset: 0, source: OutOfBoundsError { index: 1, @@ -589,10 +605,16 @@ mod tests { }; assert!(!err.is_failed_to_parse_index()); - let err = ResolveError::NotFound { offset: 0 }; + let err = Error::NotFound { + position: 0, + offset: 0, + }; assert!(!err.is_failed_to_parse_index()); - let err = ResolveError::Unreachable { offset: 0 }; + let err = Error::Unreachable { + position: 0, + offset: 0, + }; assert!(!err.is_failed_to_parse_index()); } @@ -699,13 +721,19 @@ mod tests { Test { ptr: "/object/bool/unresolvable", data, - expected: Err(ResolveError::Unreachable { offset: 12 }), + expected: Err(Error::Unreachable { + position: 2, + offset: 12, + }), }, // 12 Test { ptr: "/object/not_found", data, - expected: Err(ResolveError::NotFound { offset: 7 }), + expected: Err(Error::NotFound { + position: 1, + offset: 7, + }), }, ]); } @@ -799,25 +827,31 @@ mod tests { Test { ptr: "/object/bool/unresolvable", data, - expected: Err(ResolveError::Unreachable { offset: 12 }), + expected: Err(Error::Unreachable { + position: 2, + offset: 12, + }), }, Test { ptr: "/object/not_found", data, - expected: Err(ResolveError::NotFound { offset: 7 }), + expected: Err(Error::NotFound { + position: 1, + offset: 7, + }), }, ]); } struct Test<'v, V> { ptr: &'static str, - expected: Result<&'v V, ResolveError>, + expected: Result<&'v V, Error>, data: &'v V, } impl<'v, V> Test<'v, V> where - V: Resolve - + ResolveMut + V: Resolve + + ResolveMut + Clone + PartialEq + fmt::Display diff --git a/src/token.rs b/src/token.rs index 0447d20..5f48a97 100644 --- a/src/token.rs +++ b/src/token.rs @@ -378,7 +378,7 @@ impl std::error::Error for InvalidEncodingError {} #[cfg(test)] mod tests { - use crate::{assign::AssignError, index::OutOfBoundsError, Pointer}; + use crate::Pointer; use super::*; use quickcheck_macros::quickcheck; @@ -408,53 +408,6 @@ mod tests { assert_eq!(Token::new("a/b").encoded(), "a~1b"); } - #[test] - fn assign_error_display() { - let err = AssignError::FailedToParseIndex { - offset: 3, - source: ParseIndexError::InvalidInteger("a".parse::().unwrap_err()), - }; - assert_eq!( - err.to_string(), - "assignment failed due to an invalid index at offset 3" - ); - - let err = AssignError::OutOfBounds { - offset: 3, - source: OutOfBoundsError { - index: 3, - length: 2, - }, - }; - - assert_eq!( - err.to_string(), - "assignment failed due to index at offset 3 being out of bounds" - ); - } - - #[test] - #[cfg(feature = "std")] - fn assign_error_source() { - use std::error::Error; - let err = AssignError::FailedToParseIndex { - offset: 3, - source: ParseIndexError::InvalidInteger("a".parse::().unwrap_err()), - }; - assert!(err.source().is_some()); - assert!(err.source().unwrap().is::()); - - let err = AssignError::OutOfBounds { - offset: 3, - source: OutOfBoundsError { - index: 3, - length: 2, - }, - }; - - assert!(err.source().unwrap().is::()); - } - #[test] fn from_encoded() { assert_eq!(Token::from_encoded("~1").unwrap().encoded(), "~1"); From e892a48639c01046d0e7294178241c6647a319d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mello?= <3285133+asmello@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:29:27 +0100 Subject: [PATCH 02/30] rename SlicePointer --> PointerIndex (#92) --- src/pointer.rs | 4 ++-- src/pointer/slice.rs | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/pointer.rs b/src/pointer.rs index 8e04bb5..b31dccf 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -7,7 +7,7 @@ use alloc::{ vec::Vec, }; use core::{borrow::Borrow, cmp::Ordering, ops::Deref, str::FromStr}; -use slice::SlicePointer; +use slice::PointerIndex; mod slice; @@ -320,7 +320,7 @@ impl Pointer { /// ``` pub fn get<'p, I>(&'p self, index: I) -> Option where - I: SlicePointer<'p>, + I: PointerIndex<'p>, { index.get(self) } diff --git a/src/pointer/slice.rs b/src/pointer/slice.rs index 26f4dd6..437be6a 100644 --- a/src/pointer/slice.rs +++ b/src/pointer/slice.rs @@ -2,13 +2,13 @@ use super::Pointer; use crate::Token; use core::ops::Bound; -pub trait SlicePointer<'p>: private::Sealed { +pub trait PointerIndex<'p>: private::Sealed { type Output: 'p; fn get(self, pointer: &'p Pointer) -> Option; } -impl<'p> SlicePointer<'p> for usize { +impl<'p> PointerIndex<'p> for usize { type Output = Token<'p>; fn get(self, pointer: &'p Pointer) -> Option { @@ -16,7 +16,7 @@ impl<'p> SlicePointer<'p> for usize { } } -impl<'p> SlicePointer<'p> for core::ops::Range { +impl<'p> PointerIndex<'p> for core::ops::Range { type Output = &'p Pointer; fn get(self, pointer: &'p Pointer) -> Option { @@ -56,7 +56,7 @@ impl<'p> SlicePointer<'p> for core::ops::Range { } } -impl<'p> SlicePointer<'p> for core::ops::RangeFrom { +impl<'p> PointerIndex<'p> for core::ops::RangeFrom { type Output = &'p Pointer; fn get(self, pointer: &'p Pointer) -> Option { @@ -81,7 +81,7 @@ impl<'p> SlicePointer<'p> for core::ops::RangeFrom { } } -impl<'p> SlicePointer<'p> for core::ops::RangeTo { +impl<'p> PointerIndex<'p> for core::ops::RangeTo { type Output = &'p Pointer; fn get(self, pointer: &'p Pointer) -> Option { @@ -114,7 +114,7 @@ impl<'p> SlicePointer<'p> for core::ops::RangeTo { } } -impl<'p> SlicePointer<'p> for core::ops::RangeFull { +impl<'p> PointerIndex<'p> for core::ops::RangeFull { type Output = &'p Pointer; fn get(self, pointer: &'p Pointer) -> Option { @@ -122,7 +122,7 @@ impl<'p> SlicePointer<'p> for core::ops::RangeFull { } } -impl<'p> SlicePointer<'p> for core::ops::RangeInclusive { +impl<'p> PointerIndex<'p> for core::ops::RangeInclusive { type Output = &'p Pointer; fn get(self, pointer: &'p Pointer) -> Option { @@ -160,7 +160,7 @@ impl<'p> SlicePointer<'p> for core::ops::RangeInclusive { } } -impl<'p> SlicePointer<'p> for core::ops::RangeToInclusive { +impl<'p> PointerIndex<'p> for core::ops::RangeToInclusive { type Output = &'p Pointer; fn get(self, pointer: &'p Pointer) -> Option { @@ -190,7 +190,7 @@ impl<'p> SlicePointer<'p> for core::ops::RangeToInclusive { } } -impl<'p> SlicePointer<'p> for (Bound, Bound) { +impl<'p> PointerIndex<'p> for (Bound, Bound) { type Output = &'p Pointer; fn get(self, pointer: &'p Pointer) -> Option { From 5dbf3306de7ff6ed8e074aec42d4c6d24b32b95c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Mello?= <3285133+asmello@users.noreply.github.com> Date: Tue, 29 Oct 2024 14:56:38 +0000 Subject: [PATCH 03/30] Improve `ParseIndexError::InvalidCharacter` error (#94) * improves ParseIndexError::InvalidCharacter --- src/assign.rs | 23 ++++++++---- src/index.rs | 102 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 89 insertions(+), 36 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index e8386bb..62cdaee 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -585,10 +585,10 @@ mod toml { mod tests { use super::{Assign, Error}; use crate::{ - index::{OutOfBoundsError, ParseIndexError}, + index::{InvalidCharacterError, OutOfBoundsError, ParseIndexError}, Pointer, }; - use alloc::str::FromStr; + use alloc::vec; use core::fmt::{Debug, Display}; #[derive(Debug)] @@ -635,7 +635,6 @@ mod tests { #[test] #[cfg(feature = "json")] fn assign_json() { - use alloc::vec; use serde_json::json; [ Test { @@ -779,13 +778,16 @@ mod tests { expected_data: json!(["bar"]), }, Test { - ptr: "/a", + ptr: "/12a", data: json!([]), assign: json!("foo"), expected: Err(Error::FailedToParseIndex { position: 0, offset: 0, - source: ParseIndexError::InvalidInteger(usize::from_str("foo").unwrap_err()), + source: ParseIndexError::InvalidCharacter(InvalidCharacterError { + source: "12a".into(), + offset: 2, + }), }), expected_data: json!([]), }, @@ -807,7 +809,10 @@ mod tests { expected: Err(Error::FailedToParseIndex { position: 0, offset: 0, - source: ParseIndexError::InvalidCharacters("+".into()), + source: ParseIndexError::InvalidCharacter(InvalidCharacterError { + source: "+23".into(), + offset: 0, + }), }), expected_data: json!([]), }, @@ -826,7 +831,6 @@ mod tests { #[test] #[cfg(feature = "toml")] fn assign_toml() { - use alloc::vec; use toml::{toml, Table, Value}; [ Test { @@ -964,7 +968,10 @@ mod tests { expected: Err(Error::FailedToParseIndex { position: 0, offset: 0, - source: ParseIndexError::InvalidInteger(usize::from_str("foo").unwrap_err()), + source: ParseIndexError::InvalidCharacter(InvalidCharacterError { + source: "a".into(), + offset: 0, + }), }), expected_data: Value::Array(vec![]), }, diff --git a/src/index.rs b/src/index.rs index 6687448..0acbb4b 100644 --- a/src/index.rs +++ b/src/index.rs @@ -36,7 +36,7 @@ //! ``` use crate::Token; -use alloc::{string::String, vec::Vec}; +use alloc::string::String; use core::{fmt, num::ParseIntError, str::FromStr}; /// Represents an abstract index into an array. @@ -166,21 +166,22 @@ impl FromStr for Index { } else if s.starts_with('0') && s != "0" { Err(ParseIndexError::LeadingZeros) } else { - let idx = s.parse::().map(Index::Num)?; - if s.chars().all(|c| c.is_ascii_digit()) { - Ok(idx) - } else { - // this comes up with the `+` sign which is valid for - // representing a `usize` but not allowed in RFC 6901 array - // indices - let mut invalid: Vec<_> = s.chars().filter(|c| !c.is_ascii_digit()).collect(); - // remove duplicate characters - invalid.sort_unstable(); - invalid.dedup(); - Err(ParseIndexError::InvalidCharacters( - invalid.into_iter().collect(), - )) - } + s.chars().position(|c| !c.is_ascii_digit()).map_or_else( + || { + s.parse::() + .map(Index::Num) + .map_err(ParseIndexError::from) + }, + |offset| { + // this comes up with the `+` sign which is valid for + // representing a `usize` but not allowed in RFC 6901 array + // indices + Err(ParseIndexError::InvalidCharacter(InvalidCharacterError { + source: String::from(s), + offset, + })) + }, + ) } } } @@ -274,15 +275,15 @@ impl std::error::Error for OutOfBoundsError {} ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ -/// Indicates that the `Token` could not be parsed as valid RFC 6901 index. +/// Indicates that the `Token` could not be parsed as valid RFC 6901 array index. #[derive(Debug, PartialEq, Eq)] pub enum ParseIndexError { /// The Token does not represent a valid integer. InvalidInteger(ParseIntError), /// The Token contains leading zeros. LeadingZeros, - /// The Token contains non-digit characters. - InvalidCharacters(String), + /// The Token contains a non-digit character. + InvalidCharacter(InvalidCharacterError), } impl From for ParseIndexError { @@ -301,11 +302,7 @@ impl fmt::Display for ParseIndexError { f, "token contained leading zeros, which are disallowed by RFC 6901" ), - ParseIndexError::InvalidCharacters(chars) => write!( - f, - "token contains non-digit character(s) '{chars}', \ - which are disallowed by RFC 6901", - ), + ParseIndexError::InvalidCharacter(err) => err.fmt(f), } } } @@ -315,11 +312,56 @@ impl std::error::Error for ParseIndexError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { ParseIndexError::InvalidInteger(source) => Some(source), - ParseIndexError::LeadingZeros | ParseIndexError::InvalidCharacters(_) => None, + ParseIndexError::InvalidCharacter(source) => Some(source), + ParseIndexError::LeadingZeros => None, } } } +/// Indicates that a non-digit character was found when parsing the RFC 6901 array index. +#[derive(Debug, PartialEq, Eq)] +pub struct InvalidCharacterError { + pub(crate) source: String, + pub(crate) offset: usize, +} + +impl InvalidCharacterError { + /// Returns the offset of the character in the string. + /// + /// This offset is given in characters, not in bytes. + pub fn offset(&self) -> usize { + self.offset + } + + /// Returns the source string. + pub fn source(&self) -> &str { + &self.source + } + + /// Returns the offending character. + #[allow(clippy::missing_panics_doc)] + pub fn char(&self) -> char { + self.source + .chars() + .nth(self.offset) + .expect("char was found at offset") + } +} + +impl fmt::Display for InvalidCharacterError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "token contains the non-digit character '{}', \ + which is disallowed by RFC 6901", + self.char() + ) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for InvalidCharacterError {} + /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ @@ -435,9 +477,13 @@ mod tests { "token contained leading zeros, which are disallowed by RFC 6901" ); assert_eq!( - ParseIndexError::InvalidCharacters("+@".into()).to_string(), - "token contains non-digit character(s) '+@', \ - which are disallowed by RFC 6901" + ParseIndexError::InvalidCharacter(InvalidCharacterError { + source: "+10".into(), + offset: 0 + }) + .to_string(), + "token contains the non-digit character '+', \ + which is disallowed by RFC 6901" ); } From 0b60a5c3d0e1e24c0d2da64894840bfa5b164cd4 Mon Sep 17 00:00:00 2001 From: Chance Date: Sat, 9 Nov 2024 12:38:26 -0500 Subject: [PATCH 04/30] adds ParseBufError, report::{Report,IntoReport,Subject,ReportErr} --- CHANGELOG.md | 5 +- src/assign.rs | 34 +++++++---- src/lib.rs | 2 + src/pointer.rs | 151 ++++++++++++++++++++++++++++--------------------- src/report.rs | 72 +++++++++++++++++++++++ src/resolve.rs | 19 ++++--- 6 files changed, 199 insertions(+), 84 deletions(-) create mode 100644 src/report.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index f9686e3..4da3399 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,17 +5,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - ## [Unreleased] ### Added -- Adds method `into_buf` for `Box` and `impl From for Box`. +- Adds method `into_buf` for `Box` and `impl From for Box`. - Adds unsafe associated methods `Pointer::new_unchecked` and `PointerBuf::new_unchecked` for external zero-cost construction. - Adds `Pointer::starts_with` and `Pointer::ends_with` for prefix and suffix matching. - Adds new `ParseIndexError` variant to express the presence non-digit characters in the token. - Adds `Token::is_next` for checking if a token represents the `-` character. +- Adds `ParseBufError`, returned as the `Err` side of `PointerBuf::parse`, which includes the input `String`. ### Changed @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bumps minimum Rust version to 1.79. - `Pointer::get` now accepts ranges and can produce `Pointer` segments as output (similar to `slice::get`). +- `PointerBuf::parse` now returns `BufParseError` instead of `ParseError`. ### Fixed diff --git a/src/assign.rs b/src/assign.rs index 62cdaee..b43bdd7 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -129,6 +129,21 @@ pub trait Assign { V: Into; } +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ AssignError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ + +/// Alias for [`Error`] - indicates a value assignment failed. +#[deprecated(since = "0.7.0", note = "renamed to `AssignError`")] +pub type AssignError = Error; + + /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ @@ -139,13 +154,6 @@ pub trait Assign { ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ -// TODO: should AssignError be deprecated? -/// Alias for [`Error`]. -/// -/// Possible error returned from [`Assign`] implementations for -/// [`serde_json::Value`] and -/// [`toml::Value`](https://docs.rs/toml/0.8.14/toml/index.html). -pub type AssignError = Error; /// Possible error returned from [`Assign`] implementations for /// [`serde_json::Value`] and @@ -178,13 +186,17 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::FailedToParseIndex { .. } => { - write!(f, "assignment failed due to an invalid index") + Self::FailedToParseIndex {..} => { + write!( + f, + "value failed to be assigned by json pointer because an array index is not a valid integer or or '-'" + ) } - Self::OutOfBounds { .. } => { + Self::OutOfBounds {..} => { write!( f, - "assignment failed due to index an index being out of bounds" + "value failed to be assigned by json pointer because array index is out of bounds", + ) } } diff --git a/src/lib.rs b/src/lib.rs index 850dd2d..ca05f39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -64,6 +64,8 @@ pub mod resolve; #[cfg(feature = "resolve")] pub use resolve::{Resolve, ResolveMut}; +pub mod report; + mod pointer; pub use pointer::{ParseError, Pointer, PointerBuf}; diff --git a/src/pointer.rs b/src/pointer.rs index b31dccf..2d3b7be 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1,4 +1,8 @@ -use crate::{token::InvalidEncodingError, Components, Token, Tokens}; +use crate::{ + report::{IntoReport, Report, Subject}, + token::InvalidEncodingError, + Components, Token, Tokens, +}; use alloc::{ borrow::ToOwned, boxed::Box, @@ -905,10 +909,10 @@ impl PointerBuf { /// Attempts to parse a string into a `PointerBuf`. /// /// ## Errors - /// Returns a [`ParseError`] if the string is not a valid JSON Pointer. - pub fn parse(s: impl Into) -> Result { + /// Returns a [`ParseBufError`] if the string is not a valid JSON Pointer. + pub fn parse(s: impl Into) -> Result { let s = s.into(); - validate(&s)?; + validate(&s).map_err(|err| err.into_parse_buf_error(s.clone()))?; Ok(Self(s)) } @@ -1184,6 +1188,59 @@ const fn validate(value: &str) -> Result<&str, ParseError> { Ok(value) } +/* +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +╔══════════════════════════════════════════════════════════════════════════════╗ +║ ║ +║ ParseBufError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +╚══════════════════════════════════════════════════════════════════════════════╝ +░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ +*/ +#[derive(Debug, PartialEq)] +pub struct ParseBufError { + pub value: String, + pub source: ParseError, +} +impl std::error::Error for ParseBufError { + fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + Some(&self.source) + } +} + +impl core::fmt::Display for ParseBufError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + core::fmt::Display::fmt(&self.source, f) + } +} + +impl From for ParseError { + fn from(value: ParseBufError) -> Self { + value.source + } +} + +impl IntoReport for ParseBufError { + type Value = String; + + fn into_report(self, value: Self::Value) -> Report { + debug_assert_eq!(value, self.value); + let len = match &self.source { + ParseError::NoLeadingBackslash => 1, + ParseError::InvalidEncoding { offset, .. } => { + if *offset < value.len() - 1 { + 2 + } else { + 1 + } + } + }; + let offset = self.source.complete_offset(); + let subject = Subject::string(value, offset, len); + Report::new(subject, self) + } +} + /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ @@ -1210,6 +1267,33 @@ pub enum ParseError { source: InvalidEncodingError, }, } +impl ParseError { + fn into_parse_buf_error(self, value: String) -> ParseBufError { + ParseBufError { + source: self, + value, + } + } +} + +impl IntoReport for ParseError { + type Value = String; + + fn into_report(self, value: Self::Value) -> Report { + let len = match &self { + Self::NoLeadingBackslash => 1, + Self::InvalidEncoding { offset, .. } => { + if *offset < value.len() - 1 { + 2 + } else { + 1 + } + } + }; + let subject = Subject::string(value, self.complete_offset(), len); + Report::new(subject, self) + } +} impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -1438,23 +1522,6 @@ mod tests { assert!(p.starts_with(q)); } - #[test] - fn parse_error_is_no_leading_backslash() { - let err = ParseError::NoLeadingBackslash; - assert!(err.is_no_leading_backslash()); - assert!(!err.is_invalid_encoding()); - } - - #[test] - fn parse_error_is_invalid_encoding() { - let err = ParseError::InvalidEncoding { - offset: 0, - source: InvalidEncodingError { offset: 1 }, - }; - assert!(!err.is_no_leading_backslash()); - assert!(err.is_invalid_encoding()); - } - #[test] fn parse() { let tests = [ @@ -1513,25 +1580,6 @@ mod tests { assert!(err.source().is_none()); } - #[test] - #[cfg(feature = "std")] - fn parse_error_source() { - use std::error::Error; - let err = Pointer::parse("/foo/invalid~encoding").unwrap_err(); - assert!(err.source().is_some()); - let source = err.source().unwrap(); - assert!(source.is::()); - - let err = Pointer::parse("no-leading/slash").unwrap_err(); - assert!(err.source().is_none()); - } - - #[test] - fn pointerbuf_as_pointer_returns_pointer() { - let ptr = PointerBuf::parse("/foo/bar").unwrap(); - assert_eq!(ptr.as_ptr(), ptr); - } - #[test] fn pointer_buf_clear() { let mut ptr = PointerBuf::from_tokens(["foo", "bar"]); @@ -1601,12 +1649,6 @@ mod tests { assert_eq!(ptr, ""); } - #[test] - fn display_replace_token_error() { - let err = ReplaceError { index: 3, count: 2 }; - assert_eq!(format!("{err}"), "index 3 is out of bounds (2)"); - } - #[test] fn pop_front_works_with_empty_strings() { { @@ -1705,16 +1747,6 @@ mod tests { assert_eq!(ptr, into); } - #[test] - fn default() { - let ptr = PointerBuf::default(); - assert_eq!(ptr, ""); - assert_eq!(ptr.count(), 0); - - let ptr = <&Pointer>::default(); - assert_eq!(ptr, ""); - } - #[test] #[cfg(all(feature = "serde", feature = "json"))] fn to_json_value() { @@ -2266,13 +2298,6 @@ mod tests { assert_eq!(intersection, base); } } - #[test] - fn parse_error_display() { - assert_eq!( - ParseError::NoLeadingBackslash.to_string(), - "json pointer is malformed as it does not start with a backslash ('/')" - ); - } #[test] fn into_iter() { diff --git a/src/report.rs b/src/report.rs new file mode 100644 index 0000000..e7d0703 --- /dev/null +++ b/src/report.rs @@ -0,0 +1,72 @@ +//! Error reporting data structures and miette integration. + +use crate::PointerBuf; + +pub trait IntoReport: Sized { + type Value; + fn into_report(self, value: Self::Value) -> Report; +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Report { + pub subject: Subject, + pub error: E, +} + +impl Report { + pub fn new(subject: Subject, error: E) -> Self { + Self { subject, error } + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Subject { + String { + value: String, + offset: usize, + len: usize, + }, + Pointer { + value: PointerBuf, + position: usize, + }, +} +impl Subject { + pub(crate) fn string(value: String, offset: usize, len: usize) -> Self { + Self::String { value, offset, len } + } + pub(crate) fn pointer(value: PointerBuf, position: usize) -> Self { + Self::Pointer { value, position } + } +} +impl From<&str> for Subject { + fn from(value: &str) -> Self { + Self::String { + value: value.to_string(), + offset: 0, + len: value.len(), + } + } +} + +pub trait ReportErr { + type Error: IntoReport; + fn report_err( + self, + value: impl Into<::Value>, + ) -> Result>; +} + +impl ReportErr for Result +where + E: IntoReport, +{ + type Error = E; + + fn report_err( + self, + value: impl Into<::Value>, + ) -> Result> { + self.map_err(|error| error.into_report(value.into())) + } +} diff --git a/src/resolve.rs b/src/resolve.rs index 82c23ff..af54ca3 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -236,17 +236,20 @@ impl Error { impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::FailedToParseIndex { offset, .. } => { - write!(f, "failed to parse index at offset {offset}") + Self::FailedToParseIndex { .. } => { + write!(f, "value failed to resolve by json pointer because an array index is not a valid integer") } - Self::OutOfBounds { offset, .. } => { - write!(f, "index at offset {offset} out of bounds") + Self::OutOfBounds { .. } => { + write!( + f, + "value failed to resolve by json pointer because an array index is out of bounds" + ) } - Self::NotFound { offset, .. } => { - write!(f, "pointer starting at offset {offset} not found") + Self::NotFound { .. } => { + write!(f, "value failed to resolve by json pointer because a value within the path is not present") } - Self::Unreachable { offset, .. } => { - write!(f, "pointer starting at offset {offset} is unreachable") + Self::Unreachable { .. } => { + write!(f, "value failed to resolve by json pointer because a scalar or null value was encountered before exhausting the path") } } } From b48e05b09c79a80780c9c1145af640b57ff1e625 Mon Sep 17 00:00:00 2001 From: Chance Date: Sat, 9 Nov 2024 12:41:49 -0500 Subject: [PATCH 05/30] adds `Index` link to in docs --- src/assign.rs | 4 ++-- src/pointer.rs | 2 +- src/resolve.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index b43bdd7..cfc9be4 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -162,7 +162,7 @@ pub type AssignError = Error; pub enum Error { /// A [`Token`] within the [`Pointer`] failed to be parsed as an array index. FailedToParseIndex { - /// Position (index) of the token which failed to parse as an `Index` + /// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index) position: usize, /// Offset of the partial pointer starting with the invalid index. offset: usize, @@ -174,7 +174,7 @@ pub enum Error { /// /// The current or resulting array's length is less than the index. OutOfBounds { - /// Position (index) of the token which failed to parse as an `Index` + /// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index) position: usize, /// Offset of the partial pointer starting with the invalid index. offset: usize, diff --git a/src/pointer.rs b/src/pointer.rs index 2d3b7be..cc4d101 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1301,7 +1301,7 @@ impl fmt::Display for ParseError { Self::NoLeadingBackslash { .. } => { write!( f, - "json pointer is malformed as it does not start with a backslash ('/')" + "json pointer is malformed as it does not start with a backslash ('/') and is not empty" ) } Self::InvalidEncoding { source, .. } => write!(f, "{source}"), diff --git a/src/resolve.rs b/src/resolve.rs index af54ca3..38f2d79 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -122,7 +122,7 @@ pub enum Error { /// assert!(ptr.resolve(&data).unwrap_err().is_failed_to_parse_index()); /// ``` FailedToParseIndex { - /// Position (index) of the token which failed to parse as an `Index` + /// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index) position: usize, /// Offset of the partial pointer starting with the invalid index. offset: usize, @@ -141,7 +141,7 @@ pub enum Error { /// let ptr = Pointer::from_static("/foo/1"); /// assert!(ptr.resolve(&data).unwrap_err().is_out_of_bounds()); OutOfBounds { - /// Position (index) of the token which failed to parse as an `Index` + /// Position (index) of the token which failed to parse as an [`Index`](crate::index::Index) position: usize, /// Offset of the partial pointer starting with the invalid index. offset: usize, From 16731e1357955e3d433ea03cb4499bc7fe1da102 Mon Sep 17 00:00:00 2001 From: Chance Date: Thu, 14 Nov 2024 10:38:24 -0500 Subject: [PATCH 06/30] impls `miette::Diagnostic` for `Report` --- Cargo.lock | 70 +++++++++++++++-- Cargo.toml | 23 ++++-- src/assign.rs | 34 +++++++-- src/pointer.rs | 22 ++++-- src/report.rs | 202 +++++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 313 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dd2668a..0817435 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + [[package]] name = "env_logger" version = "0.8.2" @@ -39,7 +45,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" dependencies = [ - "cfg-if", + "cfg-if 0.1.2", "libc", "wasi", ] @@ -70,6 +76,7 @@ checksum = "d2f3e61cf687687b30c9e6ddf0fc36cf15f035e66d491e6da968fa49ffa9a378" name = "jsonptr" version = "0.6.3" dependencies = [ + "miette", "quickcheck", "quickcheck_macros", "serde", @@ -96,7 +103,7 @@ version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" dependencies = [ - "cfg-if", + "cfg-if 0.1.2", ] [[package]] @@ -105,11 +112,34 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "miette" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" +dependencies = [ + "cfg-if 1.0.0", + "miette-derive", + "thiserror", + "unicode-width", +] + +[[package]] +name = "miette-derive" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "proc-macro2" -version = "1.0.74" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2de98502f212cfcea8d0bb305bd0f49d7ebdd75b64ba0a68f937d888f4e0d6db" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] @@ -204,7 +234,7 @@ checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.46", + "syn 2.0.87", ] [[package]] @@ -240,15 +270,35 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.46" +version = "2.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89456b690ff72fddcecf231caedbe615c59480c93358a93dfae7fc29e3ebbf0e" +checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "thread_local" version = "0.3.6" @@ -298,6 +348,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index ae3d3d3..9cc3edd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ rust-version = "1.79.0" version = "0.6.3" [dependencies] +miette = { version = "7.2.0", optional = true } serde = { version = "1.0.203", optional = true, features = ["alloc"] } serde_json = { version = "1.0.119", optional = true, features = ["alloc"] } toml = { version = "0.8", optional = true } @@ -30,10 +31,20 @@ quickcheck_macros = "1.0.0" syn = { version = "1.0.109", optional = true } [features] -assign = [] -default = ["std", "serde", "json", "toml", "resolve", "assign", "delete"] -delete = ["resolve"] -json = ["dep:serde_json", "serde"] +assign = [] +default = [ + "std", + "serde", + "json", + "toml", # TODO: remove + "resolve", + "assign", + "delete", + "miette", # TODO: remove +] +delete = ["resolve"] +json = ["dep:serde_json", "serde"] +miette = ["dep:miette", "std"] resolve = [] -std = ["serde/std", "serde_json?/std"] -toml = ["dep:toml", "serde", "std"] +std = ["serde/std", "serde_json?/std"] +toml = ["dep:toml", "serde", "std"] diff --git a/src/assign.rs b/src/assign.rs index cfc9be4..2b74eda 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -37,8 +37,7 @@ //! use crate::{ - index::{OutOfBoundsError, ParseIndexError}, - Pointer, + index::{OutOfBoundsError, ParseIndexError}, report::{reportable, IntoReport, Report, Subject}, Pointer, PointerBuf }; use core::fmt::{self, Debug}; @@ -183,6 +182,18 @@ pub enum Error { }, } +reportable!(enum Error); + +impl Error { + /// Returns the position (index) of the [`Token`](crate::Token) which was out of bounds + pub fn position(&self) -> usize { + match self { + Self::OutOfBounds { position, .. } | Self::FailedToParseIndex { position, .. } => *position, + } + } +} + + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -203,6 +214,14 @@ impl fmt::Display for Error { } } +impl IntoReport for Error { + type Subject = PointerBuf; + fn into_report(self, value: Self::Subject) -> Report { + Report::new(Subject::PointerBuf { pointer: value, position: self.position() }, self) + } + +} + #[cfg(feature = "std")] impl std::error::Error for Error { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { @@ -213,11 +232,6 @@ impl std::error::Error for Error { } } -enum Assigned<'v, V> { - Done(Option), - Continue { next_dest: &'v mut V, same_value: V }, -} - /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ @@ -582,6 +596,12 @@ mod toml { } } +enum Assigned<'v, V> { + Done(Option), + Continue { next_dest: &'v mut V, same_value: V }, +} + + /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ diff --git a/src/pointer.rs b/src/pointer.rs index cc4d101..42fd328 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1,5 +1,5 @@ use crate::{ - report::{IntoReport, Report, Subject}, + report::{reportable, IntoReport, Report, Subject}, token::InvalidEncodingError, Components, Token, Tokens, }; @@ -1221,9 +1221,9 @@ impl From for ParseError { } impl IntoReport for ParseBufError { - type Value = String; + type Subject = String; - fn into_report(self, value: Self::Value) -> Report { + fn into_report(self, value: Self::Subject) -> Report { debug_assert_eq!(value, self.value); let len = match &self.source { ParseError::NoLeadingBackslash => 1, @@ -1276,10 +1276,12 @@ impl ParseError { } } +reportable!(struct ParseError); + impl IntoReport for ParseError { - type Value = String; + type Subject = String; - fn into_report(self, value: Self::Value) -> Report { + fn into_report(self, value: Self::Subject) -> Report { let len = match &self { Self::NoLeadingBackslash => 1, Self::InvalidEncoding { offset, .. } => { @@ -1428,6 +1430,8 @@ impl std::error::Error for ReplaceError {} mod tests { use std::error::Error; + use crate::report::ReportErr; + use super::*; use quickcheck::TestResult; use quickcheck_macros::quickcheck; @@ -1814,6 +1818,14 @@ mod tests { ); } + #[test] + fn into_report() { + let report = Pointer::parse("invalid~encoding") + .report_err("invalid~encoding") + .unwrap_err(); + assert_eq!(report.subject.as_str(), "invalid~encoding"); + } + #[test] fn get() { let ptr = Pointer::from_static("/0/1/2/3/4/5/6/7/8/9"); diff --git a/src/report.rs b/src/report.rs index e7d0703..cbd364f 100644 --- a/src/report.rs +++ b/src/report.rs @@ -1,48 +1,155 @@ //! Error reporting data structures and miette integration. -use crate::PointerBuf; +use core::iter::once; +use crate::{Pointer, PointerBuf}; + +/// Implemented by errors which can be converted into a [`Report`]. pub trait IntoReport: Sized { - type Value; - fn into_report(self, value: Self::Value) -> Report; + /// The value which caused the error. + /// + /// Depending on the error, this may be either [`String`] or [`PointerBuf`] + type Subject; + /// Convert the error into a [`Report`]. + fn into_report(self, value: Self::Subject) -> Report; } +/// An error wrapper which includes the [`String`] which failed to parse or the +/// [`PointerBuf`] being used. #[derive(Debug, Clone, PartialEq, Eq)] pub struct Report { - pub subject: Subject, + /// The error which occurred. pub error: E, + /// The value which caused the error. + pub subject: Subject, } impl Report { + /// Create a new `Report` with the given subject and error. pub fn new(subject: Subject, error: E) -> Self { - Self { subject, error } + Self { error, subject } + } +} + +impl Report { + /// The docs.rs URL for the error of this [`Report`]. + fn url() -> &'static str { + E::url() + } +} + +impl std::fmt::Display for Report +where + E: std::fmt::Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Display::fmt(&self.error, f) + } +} +impl std::error::Error for Report where E: std::error::Error {} + +#[cfg(feature = "miette")] +impl miette::Diagnostic for Report +where + E: Reportable + IntoReport + std::error::Error, +{ + fn url<'a>(&'a self) -> Option> { + Some(Box::new(Self::url())) + } + + fn source_code(&self) -> Option<&dyn miette::SourceCode> { + Some(&self.subject) + } + + fn labels(&self) -> Option + '_>> { + match &self.subject { + Subject::String { + string, + offset, + len, + } => { + let span = LabeledSpan::new(Some(string.to_string()), *offset, *len); + Some(Box::new(once(span))) + } + Subject::PointerBuf { pointer, position } => { + let offset = pointer.get(0..*position).unwrap().as_str().len(); + let token = pointer.get(*position).unwrap(); + let decoded = token.decoded(); + let len = decoded.len(); + let span = LabeledSpan::new(Some(decoded.to_string()), offset, len); + Some(Box::new(once(span))) + } + } } } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Subject { String { - value: String, + string: String, offset: usize, len: usize, }, - Pointer { - value: PointerBuf, + PointerBuf { + pointer: PointerBuf, position: usize, }, } + +#[cfg(feature = "miette")] +impl miette::SourceCode for Subject { + fn read_span<'a>( + &'a self, + span: &miette::SourceSpan, + context_lines_before: usize, + context_lines_after: usize, + ) -> Result + 'a>, miette::MietteError> { + self.as_str() + .read_span(span, context_lines_before, context_lines_after) + } +} + impl Subject { pub(crate) fn string(value: String, offset: usize, len: usize) -> Self { - Self::String { value, offset, len } + Self::String { + string: value, + offset, + len, + } } pub(crate) fn pointer(value: PointerBuf, position: usize) -> Self { - Self::Pointer { value, position } + Self::PointerBuf { + pointer: value, + position, + } + } + pub fn as_str(&self) -> &str { + match self { + Self::String { string: value, .. } => value, + Self::PointerBuf { pointer: value, .. } => value.as_str(), + } + } +} +impl PartialEq for Subject { + fn eq(&self, other: &str) -> bool { + match self { + Self::String { string: value, .. } => value == other, + Self::PointerBuf { pointer: value, .. } => value.as_str() == other, + } + } +} +impl PartialEq for Subject { + fn eq(&self, other: &Pointer) -> bool { + match self { + Self::String { string: value, .. } => other.as_str() == value, + Self::PointerBuf { pointer: value, .. } => value == other, + } } } impl From<&str> for Subject { fn from(value: &str) -> Self { Self::String { - value: value.to_string(), + string: value.to_string(), offset: 0, len: value.len(), } @@ -51,9 +158,10 @@ impl From<&str> for Subject { pub trait ReportErr { type Error: IntoReport; + #[allow(clippy::missing_errors_doc)] fn report_err( self, - value: impl Into<::Value>, + value: impl Into<::Subject>, ) -> Result>; } @@ -65,8 +173,76 @@ where fn report_err( self, - value: impl Into<::Value>, + value: impl Into<::Subject>, ) -> Result> { self.map_err(|error| error.into_report(value.into())) } } +pub trait Reportable { + /// The docs.rs URL for this error + fn url() -> &'static str; +} + +macro_rules! reportable { + (enum $type:ident) => { + crate::report::reportable!("enum", "", $type); + }; + (struct $type:ident) => { + crate::report::reportable!("struct", "", $type); + }; + (enum $mod:ident::$type:ident) => { + crate::report::reportable!("enum", concat!("/", stringify!($mod)), $type); + }; + (struct $mod:ident::$type:ident) => { + crate::report::reportable!("struct", concat!("/", stringify!($mod)), $type); + }; + ($kind:literal, $mod:expr, $type:ident) => { + impl $type { + #[doc = "The docs.rs URL for this error"] + pub const fn url() -> &'static str { + // https://docs.rs/jsonptr/{VERSION}/jsonptr{}/{}.{}.html + concat!( + "https://docs.rs/jsonptr/", + env!("CARGO_PKG_VERSION"), + "/jsonptr", + $mod, + "/", + $kind, + ".", + stringify!($type), + ".html", + ) + } + } + impl crate::report::Reportable for $type { + fn url() -> &'static str { + $type::url() + } + } + }; +} + +use miette::LabeledSpan; +pub(crate) use reportable; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[cfg(all( + feature = "assign", + feature = "miette", + feature = "serde", + feature = "json" + ))] + fn assign_error() { + use miette::Diagnostic; + use serde_json::json; + let ptr = PointerBuf::parse("/3").unwrap(); + let mut value = json!(["baz"]); + let error = ptr.assign(&mut value, json!("qux")).unwrap_err(); + let report = error.into_report(ptr); + println!("{report}"); + } +} From 78704a46378a2cca30e8789bb4c550b49fb9f14c Mon Sep 17 00:00:00 2001 From: Chance Date: Thu, 14 Nov 2024 13:01:53 -0500 Subject: [PATCH 07/30] refactoring --- Cargo.lock | 461 ++++++++++++++++++++++++++++++++++++++++--------- Cargo.toml | 2 +- src/assign.rs | 22 ++- src/pointer.rs | 103 ++++++----- src/report.rs | 272 ++++++++++++++++++----------- 5 files changed, 633 insertions(+), 227 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0817435..614e52f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,20 +2,59 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + [[package]] name = "aho-corasick" -version = "0.7.6" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] -name = "cfg-if" -version = "0.1.2" +name = "backtrace" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c819a1287eb618df47cc647173c5c4c66ba19d888a6e50d605672aed3140de" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets 0.52.6", +] + +[[package]] +name = "backtrace-ext" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537beee3be4a18fb023b570f80e3ae28003db9167a751266b259926e25539d50" +dependencies = [ + "backtrace", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cfg-if" @@ -25,9 +64,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "env_logger" -version = "0.8.2" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" +checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" dependencies = [ "log", "regex", @@ -35,42 +74,64 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] [[package]] name = "getrandom" -version = "0.2.0" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ - "cfg-if 0.1.2", + "cfg-if", "libc", "wasi", ] +[[package]] +name = "gimli" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" + [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3" [[package]] name = "indexmap" -version = "2.0.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", ] +[[package]] +name = "is_ci" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45" + [[package]] name = "itoa" -version = "1.0.0" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f3e61cf687687b30c9e6ddf0fc36cf15f035e66d491e6da968fa49ffa9a378" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jsonptr" @@ -86,31 +147,28 @@ dependencies = [ ] [[package]] -name = "lazy_static" -version = "1.0.0" +name = "libc" +version = "0.2.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" +checksum = "18d287de67fe55fd7e1581fe933d965a5a9477b38e949cfa9f8574ef01506398" [[package]] -name = "libc" -version = "0.2.64" +name = "linux-raw-sys" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74dfca3d9957906e8d1e6a0b641dc9a59848e793f1da2165889fd4f62d10d79c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" -version = "0.4.8" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" -dependencies = [ - "cfg-if 0.1.2", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "miette" @@ -118,8 +176,16 @@ version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4edc8853320c2a0dab800fbda86253c8938f6ea88510dc92c5f1ed20e794afc1" dependencies = [ - "cfg-if 1.0.0", + "backtrace", + "backtrace-ext", + "cfg-if", "miette-derive", + "owo-colors", + "supports-color", + "supports-hyperlinks", + "supports-unicode", + "terminal_size", + "textwrap", "thiserror", "unicode-width", ] @@ -135,6 +201,30 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "object" +version = "0.36.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +dependencies = [ + "memchr", +] + +[[package]] +name = "owo-colors" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb37767f6569cd834a413442455e0f066d0d522de8630436e2a1761d9726ba56" + [[package]] name = "proc-macro2" version = "1.0.89" @@ -168,69 +258,99 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.0" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a76330fb486679b4ace3670f117bbc9e16204005c4bde9c4bd372f45bed34f12" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "rand_core", ] [[package]] name = "rand_core" -version = "0.6.2" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "regex" -version = "1.3.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc98360d9e6ad383647702acc90f80b0582eac3ea577ab47d96325d3575de908" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", - "thread_local", ] [[package]] name = "regex-syntax" -version = "0.6.12" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11a7e20d1cce64ef2fed88b66d347f88bd9babb82845b2b858f3edbf59a4f716" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e4ea3e1cdc4b559b8e5650f9c8e5998e3e5c1343b4eaf034565f32318d63c0" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] [[package]] name = "ryu" -version = "1.0.0" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -239,24 +359,52 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.119" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8eddb61f0697cc3989c5d64b452f5488e2b8a60fd7d5076a3045076ffef8cb0" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "supports-color" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8775305acf21c96926c900ad056abeef436701108518cf890020387236ac5a77" +dependencies = [ + "is_ci", +] + +[[package]] +name = "supports-hyperlinks" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0a1e5168041f5f3ff68ff7d95dcb9c8749df29f6e7e89ada40dd4c9de404ee" + +[[package]] +name = "supports-unicode" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7401a30af6cb5818bb64852270bb722533397edcfc7344954a38f420819ece2" + [[package]] name = "syn" version = "1.0.109" @@ -279,40 +427,52 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.65" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn 2.0.87", ] -[[package]] -name = "thread_local" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" -dependencies = [ - "lazy_static", -] - [[package]] name = "toml" -version = "0.8.0" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c226a7bba6d859b63c92c4b4fe69c5b6b72d0cb897dbc8e6012298e6154cb56e" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -322,18 +482,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.3" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.20.0" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ff63e60a958cefbb518ae1fd6566af80d9d4be430a33f3723dfc47d1d411d95" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "serde", @@ -344,9 +504,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.0" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-width" @@ -356,15 +522,154 @@ checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] name = "wasi" -version = "0.9.0+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.0" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 9cc3edd..f658d1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ rust-version = "1.79.0" version = "0.6.3" [dependencies] -miette = { version = "7.2.0", optional = true } +miette = { version = "7.2.0", optional = true, features = ["fancy"] } serde = { version = "1.0.203", optional = true, features = ["alloc"] } serde_json = { version = "1.0.119", optional = true, features = ["alloc"] } toml = { version = "0.8", optional = true } diff --git a/src/assign.rs b/src/assign.rs index 2b74eda..5317053 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -37,7 +37,7 @@ //! use crate::{ - index::{OutOfBoundsError, ParseIndexError}, report::{reportable, IntoReport, Report, Subject}, Pointer, PointerBuf + index::{OutOfBoundsError, ParseIndexError}, report::{impl_diagnostic_url, Diagnostic, Label, Report, Subject}, Pointer, PointerBuf }; use core::fmt::{self, Debug}; @@ -182,8 +182,6 @@ pub enum Error { }, } -reportable!(enum Error); - impl Error { /// Returns the position (index) of the [`Token`](crate::Token) which was out of bounds pub fn position(&self) -> usize { @@ -214,10 +212,22 @@ impl fmt::Display for Error { } } -impl IntoReport for Error { +impl_diagnostic_url!(enum assign::Error); + + +impl Diagnostic for Error { type Subject = PointerBuf; - fn into_report(self, value: Self::Subject) -> Report { - Report::new(Subject::PointerBuf { pointer: value, position: self.position() }, self) + + fn into_report(self, source: Self::Subject) -> Report { + Report::new(self, source.into()) + } + + fn url() -> &'static str { + Self::url() + } + + fn label(&self, subject: &Subject) -> Option { + Label::for_pointer_buf(subject, self.position()) } } diff --git a/src/pointer.rs b/src/pointer.rs index 42fd328..3dc6ccb 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -1,5 +1,5 @@ use crate::{ - report::{reportable, IntoReport, Report, Subject}, + report::{impl_diagnostic_url, Diagnostic, Label, Report, Subject}, token::InvalidEncodingError, Components, Token, Tokens, }; @@ -910,9 +910,9 @@ impl PointerBuf { /// /// ## Errors /// Returns a [`ParseBufError`] if the string is not a valid JSON Pointer. - pub fn parse(s: impl Into) -> Result { + pub fn parse(s: impl Into) -> Result { let s = s.into(); - validate(&s).map_err(|err| err.into_parse_buf_error(s.clone()))?; + validate(&s).map_err(|err| err.with_subject(s.clone()))?; Ok(Self(s)) } @@ -1192,52 +1192,49 @@ const fn validate(value: &str) -> Result<&str, ParseError> { ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ ║ ║ -║ ParseBufError ║ -║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ +║ TopicalParseError ║ +║ ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯ ║ ╚══════════════════════════════════════════════════════════════════════════════╝ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ */ #[derive(Debug, PartialEq)] -pub struct ParseBufError { +pub struct TopicalParseError { pub value: String, pub source: ParseError, } -impl std::error::Error for ParseBufError { +impl std::error::Error for TopicalParseError { fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { Some(&self.source) } } -impl core::fmt::Display for ParseBufError { +impl core::fmt::Display for TopicalParseError { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { core::fmt::Display::fmt(&self.source, f) } } -impl From for ParseError { - fn from(value: ParseBufError) -> Self { +impl From for ParseError { + fn from(value: TopicalParseError) -> Self { value.source } } +impl_diagnostic_url!(struct TopicalParseError); -impl IntoReport for ParseBufError { - type Subject = String; +impl Diagnostic for TopicalParseError { + type Subject = (); - fn into_report(self, value: Self::Subject) -> Report { - debug_assert_eq!(value, self.value); - let len = match &self.source { - ParseError::NoLeadingBackslash => 1, - ParseError::InvalidEncoding { offset, .. } => { - if *offset < value.len() - 1 { - 2 - } else { - 1 - } - } - }; - let offset = self.source.complete_offset(); - let subject = Subject::string(value, offset, len); - Report::new(subject, self) + fn into_report(self, (): Self::Subject) -> Report { + let subject = Subject::String(self.value.clone()); + Report::new(self, subject) + } + + fn url() -> &'static str { + Self::url() + } + + fn label(&self, subject: &Subject) -> Option