diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d80fb7..90482aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - 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` variant of `PointerBuf::parse`, which includes the input `String` (from `Into::into`). -- Adds `InvalidEncoding` +- Adds `InvalidEncoding` to represent the two possible encoding errors when decoding a token. +- Adds `diagnostic` mod, including: +- Adds `diagnotic::Diagnostic` trait to facilitate error reporting and + `miette` integration. All errors intended for usage with `assign::Assign` or + `resolve::Resolve` must implement this trait. +- Adds `"miette"` featureflag to enable `miette` integration for error reporting. ### Changed @@ -25,11 +29,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `Pointer::get` now accepts ranges and can produce `Pointer` segments as output (similar to `slice::get`). - `PointerBuf::parse` now returns `BufParseError` instead of `ParseError`. +- Adds field `position` to variants of `resolve::Error` and `assign::Error` to indicate the + token index of where the error occurred. +- Renames `ParseError::NoLeadingBackslash` to `ParseError::NoLeadingSlash`. ### Fixed - Make validation of array indices conform to RFC 6901 in the presence of non-digit characters. +### Deprecated + +- `assign::AssignError` renamed to `assign::Error` +- `resolve::ResolveError` renamed to `resolve::Error` +- `InvalidEncodingError` renamed to `EncodingError` + ## [0.6.2] 2024-09-30 ### Added diff --git a/src/assign.rs b/src/assign.rs index d173ad9..f8f4803 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -173,16 +173,38 @@ impl Error { Self::OutOfBounds { offset, .. } | Self::FailedToParseIndex { offset, .. } => *offset, } } + + /// Returns `true` if the error is [`OutOfBounds`]. + /// + /// [`OutOfBounds`]: Error::OutOfBounds + #[must_use] + pub fn is_out_of_bounds(&self) -> bool { + matches!(self, Self::OutOfBounds { .. }) + } + + /// Returns `true` if the error is [`FailedToParseIndex`]. + /// + /// [`FailedToParseIndex`]: Error::FailedToParseIndex + #[must_use] + pub fn is_failed_to_parse_index(&self) -> bool { + matches!(self, Self::FailedToParseIndex { .. }) + } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "value failed to be assigned, caused by token (position: {}, offset: {}) of the json pointer", - self.position(), - self.offset() - ) + match self { + Self::FailedToParseIndex { offset, .. } => { + write!( + f, + "assign failed: json pointer token at offset {offset} failed to parse as an array index" + ) + } + Self::OutOfBounds { offset, .. } => write!( + f, + "assign failed: json pointer token at offset {offset} is out of bounds", + ), + } } } @@ -203,9 +225,7 @@ impl Diagnostic for Error { }; let len = token.encoded().len(); let text = match self { - Error::FailedToParseIndex { .. } => { - format!("expected array index or '-', found \"{}\"", token.decoded()) - } + Error::FailedToParseIndex { .. } => "expected array index or '-'".to_string(), Error::OutOfBounds { source, .. } => { format!("{} is out of bounds (len: {})", source.index, source.length) } diff --git a/src/index.rs b/src/index.rs index b7e64a4..0695532 100644 --- a/src/index.rs +++ b/src/index.rs @@ -256,7 +256,7 @@ impl fmt::Display for OutOfBoundsError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, - "index {} out of bounds (limit: {})", + "index {} out of bounds (len: {})", self.index, self.length ) } diff --git a/src/pointer.rs b/src/pointer.rs index cec22fe..0216c3e 100644 --- a/src/pointer.rs +++ b/src/pointer.rs @@ -207,10 +207,11 @@ impl Pointer { } /// Splits the `Pointer` at the given index if the character at the index is - /// a separator backslash (`'/'`), returning `Some((head, tail))`. Otherwise, + /// a separator slash (`'/'`), returning `Some((head, tail))`. Otherwise, /// returns `None`. /// - /// For the following JSON Pointer, the following splits are possible (0, 4, 8): + /// For the following JSON Pointer, the following splits are possible (0, 4, + /// 8): /// ```text /// /foo/bar/baz /// ↑ ↑ ↑ @@ -1166,8 +1167,8 @@ impl fmt::Display for NoLeadingSlash { /// Indicates that a `Pointer` was malformed and unable to be parsed. #[derive(Debug, PartialEq)] pub enum ParseError { - /// `Pointer` did not start with a backslash (`'/'`). - NoLeadingBackslash, + /// `Pointer` did not start with a slash (`'/'`). + NoLeadingSlash, /// `Pointer` contained invalid encoding (e.g. `~` not followed by `0` or /// `1`). @@ -1185,14 +1186,14 @@ impl ParseError { /// invalid encoding pub fn offset(&self) -> usize { match self { - Self::NoLeadingBackslash => 0, + Self::NoLeadingSlash => 0, Self::InvalidEncoding { offset, .. } => *offset, } } /// Length of the invalid encoding pub fn invalid_encoding_len(&self, subject: &str) -> usize { match self { - Self::NoLeadingBackslash => 0, + Self::NoLeadingSlash => 0, Self::InvalidEncoding { offset, .. } => { if *offset < subject.len() - 1 { 2 @@ -1215,7 +1216,7 @@ impl Diagnostic for ParseError { let offset = self.complete_offset(); let len = self.invalid_encoding_len(subject); let text = match self { - ParseError::NoLeadingBackslash => "must start with a backslash ('/')", + ParseError::NoLeadingSlash => "must start with a backslash ('/')", ParseError::InvalidEncoding { .. } => "'~' must be followed by '0' or '1'", } .to_string(); @@ -1229,7 +1230,7 @@ impl miette::Diagnostic for ParseError {} impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Self::NoLeadingBackslash { .. } => { + Self::NoLeadingSlash { .. } => { write!( f, "json pointer failed to parse; does not start with a backslash ('/') and is not empty" @@ -1248,7 +1249,7 @@ impl fmt::Display for ParseError { impl ParseError { /// Returns `true` if this error is `NoLeadingBackslash` pub fn is_no_leading_backslash(&self) -> bool { - matches!(self, Self::NoLeadingBackslash { .. }) + matches!(self, Self::NoLeadingSlash { .. }) } /// Returns `true` if this error is `InvalidEncoding` @@ -1270,7 +1271,7 @@ impl ParseError { /// ``` pub fn pointer_offset(&self) -> usize { match *self { - Self::NoLeadingBackslash { .. } => 0, + Self::NoLeadingSlash { .. } => 0, Self::InvalidEncoding { offset, .. } => offset, } } @@ -1290,7 +1291,7 @@ impl ParseError { /// ``` pub fn source_offset(&self) -> usize { match self { - Self::NoLeadingBackslash { .. } => 0, + Self::NoLeadingSlash { .. } => 0, Self::InvalidEncoding { source, .. } => source.offset, } } @@ -1316,7 +1317,7 @@ impl std::error::Error for ParseError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { Self::InvalidEncoding { source, .. } => Some(source), - Self::NoLeadingBackslash => None, + Self::NoLeadingSlash => None, } } } @@ -1355,7 +1356,7 @@ const fn validate(value: &str) -> Result<&str, ParseError> { const fn validate_bytes(bytes: &[u8], offset: usize) -> Result<(), ParseError> { if bytes[0] != b'/' && offset == 0 { - return Err(ParseError::NoLeadingBackslash); + return Err(ParseError::NoLeadingSlash); } let mut ptr_offset = offset; // offset within the pointer of the most recent '/' separator @@ -1521,7 +1522,7 @@ mod tests { ("/foo/bar/baz/~10", Ok("/foo/bar/baz/~10")), ("/foo/bar/baz/~11", Ok("/foo/bar/baz/~11")), ("/foo/bar/baz/~1/~0", Ok("/foo/bar/baz/~1/~0")), - ("missing-slash", Err(ParseError::NoLeadingBackslash)), + ("missing-slash", Err(ParseError::NoLeadingSlash)), ( "/~", Err(ParseError::InvalidEncoding { diff --git a/src/resolve.rs b/src/resolve.rs index a71456b..b7ce22a 100644 --- a/src/resolve.rs +++ b/src/resolve.rs @@ -235,20 +235,23 @@ impl Diagnostic for Error { impl core::fmt::Display for Error { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { match self { - Self::FailedToParseIndex { .. } => { - write!(f, "value failed to resolve by json pointer because an array index is not a valid integer") + Self::FailedToParseIndex { offset, .. } => { + write!(f, "resolve failed: json pointer token at offset {offset} failed to parse as an index") } - Self::OutOfBounds { .. } => { + Self::OutOfBounds { offset, .. } => { write!( f, - "value failed to resolve by json pointer because an array index is out of bounds" + "resolve failed: json pointer token at offset {offset} is out of bounds" ) } - Self::NotFound { .. } => { - write!(f, "value failed to resolve by json pointer because a value within the path is not present") + Self::NotFound { offset, .. } => { + write!( + f, + "resolve failed: json pointer token at {offset} was not found in value" + ) } - Self::Unreachable { .. } => { - write!(f, "value failed to resolve by json pointer because a scalar or null value was encountered before exhausting the path") + Self::Unreachable { offset, .. } => { + write!(f, "resolve failed: json pointer token at {offset} is unreachable (previous token resolved to a scalar or null value)") } } } diff --git a/src/token.rs b/src/token.rs index 0944751..fe574a2 100644 --- a/src/token.rs +++ b/src/token.rs @@ -383,7 +383,7 @@ pub struct EncodingError { #[cfg(feature = "std")] impl std::error::Error for EncodingError { - fn source(&self) -> Option<&(dyn core::error::Error + 'static)> { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.source) } }