From c621b26259d9cb9a1de1b6aa6e0e3136cfff3969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1=20de=20Mello?= Date: Tue, 18 Feb 2025 20:42:53 +0000 Subject: [PATCH] migrate remaining errors to new model --- src/assign.rs | 15 ++------ src/index.rs | 97 ++++++++++++++++++++++++++++++++++++++++----------- src/token.rs | 72 +++++++++++++++++++++++++++++++------- 3 files changed, 139 insertions(+), 45 deletions(-) diff --git a/src/assign.rs b/src/assign.rs index 8b4e3bf..175f522 100644 --- a/src/assign.rs +++ b/src/assign.rs @@ -798,10 +798,7 @@ mod tests { expected: Err(Error::FailedToParseIndex { position: 0, offset: 0, - source: ParseIndexError::InvalidCharacter(InvalidCharacterError { - source: "12a".into(), - offset: 2, - }), + source: ParseIndexError::InvalidCharacter(InvalidCharacterError { offset: 2 }), }), expected_data: json!([]), }, @@ -823,10 +820,7 @@ mod tests { expected: Err(Error::FailedToParseIndex { position: 0, offset: 0, - source: ParseIndexError::InvalidCharacter(InvalidCharacterError { - source: "+23".into(), - offset: 0, - }), + source: ParseIndexError::InvalidCharacter(InvalidCharacterError { offset: 0 }), }), expected_data: json!([]), }, @@ -976,10 +970,7 @@ mod tests { expected: Err(Error::FailedToParseIndex { position: 0, offset: 0, - source: ParseIndexError::InvalidCharacter(InvalidCharacterError { - source: "a".into(), - offset: 0, - }), + source: ParseIndexError::InvalidCharacter(InvalidCharacterError { offset: 0 }), }), expected_data: Value::Array(vec![]), }, diff --git a/src/index.rs b/src/index.rs index bad40bc..b61226b 100644 --- a/src/index.rs +++ b/src/index.rs @@ -35,9 +35,12 @@ //! assert_eq!(Index::Next.for_len_unchecked(30), 30); //! ``` -use crate::Token; +use crate::{ + diagnostic::{diagnostic_url, IntoReport, Label}, + Token, +}; use alloc::string::String; -use core::{fmt, num::ParseIntError, str::FromStr}; +use core::{fmt, iter::once, num::ParseIntError, str::FromStr}; /// Represents an abstract index into an array. /// @@ -177,7 +180,6 @@ impl FromStr for Index { // representing a `usize` but not allowed in RFC 6901 array // indices Err(ParseIndexError::InvalidCharacter(InvalidCharacterError { - source: String::from(s), offset, })) }, @@ -309,6 +311,75 @@ impl fmt::Display for ParseIndexError { } } +// shouldn't be used directly, but is part of a public interface +#[doc(hidden)] +#[derive(Debug)] +pub enum StringOrToken { + String(String), + Token(Token<'static>), +} + +impl From for StringOrToken { + fn from(value: String) -> Self { + Self::String(value) + } +} + +impl From> for StringOrToken { + fn from(value: Token<'static>) -> Self { + Self::Token(value) + } +} + +impl core::ops::Deref for StringOrToken { + type Target = str; + + fn deref(&self) -> &Self::Target { + match self { + StringOrToken::String(s) => s.as_str(), + StringOrToken::Token(t) => t.encoded(), + } + } +} + +impl IntoReport for ParseIndexError { + type Subject = StringOrToken; + + fn url() -> &'static str { + diagnostic_url!(enum ParseIndexError) + } + + fn labels( + &self, + subject: &Self::Subject, + ) -> Option>> { + let subject = &**subject; + match self { + ParseIndexError::InvalidInteger(_) => None, + ParseIndexError::LeadingZeros => { + let len = subject + .chars() + .position(|c| c != '0') + .expect("starts with zeros"); + let text = String::from("leading zeros"); + Some(Box::new(once(Label::new(text, 0, len)))) + } + ParseIndexError::InvalidCharacter(err) => { + let len = subject + .chars() + .skip(err.offset) + .position(|c| !c.is_ascii_digit()) + .expect("at least one non-digit char"); + let text = String::from("invalid character(s)"); + Some(Box::new(once(Label::new(text, err.offset, len)))) + } + } + } +} + +#[cfg(feature = "miette")] +impl miette::Diagnostic for ParseIndexError {} + #[cfg(feature = "std")] impl std::error::Error for ParseIndexError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { @@ -323,7 +394,6 @@ impl std::error::Error for ParseIndexError { /// Indicates that a non-digit character was found when parsing the RFC 6901 array index. #[derive(Debug, Clone, PartialEq, Eq)] pub struct InvalidCharacterError { - pub(crate) source: String, pub(crate) offset: usize, } @@ -334,29 +404,14 @@ impl InvalidCharacterError { 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() + "token contains a non-digit character, \ + which is disallowed by RFC 6901", ) } } diff --git a/src/token.rs b/src/token.rs index fe574a2..17c9415 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,6 +1,9 @@ -use core::str::Split; +use core::{iter::once, str::Split}; -use crate::index::{Index, ParseIndexError}; +use crate::{ + diagnostic::{diagnostic_url, IntoReport, Label}, + index::{Index, ParseIndexError}, +}; use alloc::{ borrow::Cow, fmt, @@ -99,7 +102,7 @@ impl<'a> Token<'a> { if escaped { return Err(EncodingError { offset: s.len(), - source: InvalidEncoding::Slash, + source: InvalidEncoding::Tilde, }); } Ok(Self { inner: s.into() }) @@ -381,13 +384,6 @@ pub struct EncodingError { pub source: InvalidEncoding, } -#[cfg(feature = "std")] -impl std::error::Error for EncodingError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.source) - } -} - impl fmt::Display for EncodingError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( @@ -398,6 +394,38 @@ impl fmt::Display for EncodingError { } } +impl IntoReport for EncodingError { + type Subject = String; + + fn url() -> &'static str { + diagnostic_url!(struct EncodingError) + } + + fn labels(&self, subject: &Self::Subject) -> Option>> { + let (text, offset) = match self.source { + InvalidEncoding::Tilde => { + if self.offset == subject.len() { + ("incomplete escape sequence", self.offset - 1) + } else { + ("must be 0 or 1", self.offset) + } + } + InvalidEncoding::Slash => ("invalid character", self.offset), + }; + Some(Box::new(once(Label::new(text.to_string(), offset, 1)))) + } +} + +#[cfg(feature = "miette")] +impl miette::Diagnostic for EncodingError {} + +#[cfg(feature = "std")] +impl std::error::Error for EncodingError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + Some(&self.source) + } +} + /* ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ ╔══════════════════════════════════════════════════════════════════════════════╗ @@ -425,6 +453,7 @@ impl fmt::Display for InvalidEncoding { } } } + #[cfg(feature = "std")] impl std::error::Error for InvalidEncoding {} @@ -476,8 +505,27 @@ mod tests { assert_eq!(Token::from_encoded("~0~1").unwrap().encoded(), "~0~1"); let t = Token::from_encoded("a~1b").unwrap(); assert_eq!(t.decoded(), "a/b"); - assert!(Token::from_encoded("a/b").is_err()); - assert!(Token::from_encoded("a~a").is_err()); + assert_eq!( + Token::from_encoded("a/b"), + Err(EncodingError { + offset: 1, + source: InvalidEncoding::Slash + }) + ); + assert_eq!( + Token::from_encoded("a~a"), + Err(EncodingError { + offset: 2, + source: InvalidEncoding::Tilde + }) + ); + assert_eq!( + Token::from_encoded("a~"), + Err(EncodingError { + offset: 2, + source: InvalidEncoding::Tilde + }) + ); } #[test]