Skip to content

Commit 7e5bf35

Browse files
committed
Merge branch 'main' into token-from-encoded
2 parents a0148fa + 29840e7 commit 7e5bf35

File tree

5 files changed

+319
-50
lines changed

5 files changed

+319
-50
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## Unreleased
99

10+
### Fixed
11+
12+
- `EncodingError` and `ParseIndexError` now implement `Diagnostic`, which
13+
unifies the API for errors originating from parse-like operations.
14+
- Fixes returning an incorrect error type when parsing a `Token` that
15+
terminates with `~`. This now correctly classifies the error as a `Tilde`
16+
error.
17+
18+
### Removed
19+
20+
- Some methods were removed from `InvalidCharacterError`, as that type no
21+
longer holds a copy of the input string internally. This is a breaking
22+
change. To access equivalent functionality, use the `Diagnostic` API
23+
integration.
24+
1025
### Changed
1126
- `Token::from_encoded` now accepts owned or borrowed strings (any type that
1227
implements `Into<Cow<'_, str>>`).

src/assign.rs

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,6 @@ mod tests {
608608
index::{InvalidCharacterError, OutOfBoundsError, ParseIndexError},
609609
Pointer,
610610
};
611-
use alloc::vec;
612611
use core::fmt::{Debug, Display};
613612

614613
#[derive(Debug)]
@@ -798,10 +797,7 @@ mod tests {
798797
expected: Err(Error::FailedToParseIndex {
799798
position: 0,
800799
offset: 0,
801-
source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
802-
source: "12a".into(),
803-
offset: 2,
804-
}),
800+
source: ParseIndexError::InvalidCharacter(InvalidCharacterError { offset: 2 }),
805801
}),
806802
expected_data: json!([]),
807803
},
@@ -823,10 +819,7 @@ mod tests {
823819
expected: Err(Error::FailedToParseIndex {
824820
position: 0,
825821
offset: 0,
826-
source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
827-
source: "+23".into(),
828-
offset: 0,
829-
}),
822+
source: ParseIndexError::InvalidCharacter(InvalidCharacterError { offset: 0 }),
830823
}),
831824
expected_data: json!([]),
832825
},
@@ -839,6 +832,7 @@ mod tests {
839832
#[test]
840833
#[cfg(feature = "toml")]
841834
fn assign_toml() {
835+
use alloc::vec;
842836
use toml::{toml, Table, Value};
843837
[
844838
Test {
@@ -976,10 +970,7 @@ mod tests {
976970
expected: Err(Error::FailedToParseIndex {
977971
position: 0,
978972
offset: 0,
979-
source: ParseIndexError::InvalidCharacter(InvalidCharacterError {
980-
source: "a".into(),
981-
offset: 0,
982-
}),
973+
source: ParseIndexError::InvalidCharacter(InvalidCharacterError { offset: 0 }),
983974
}),
984975
expected_data: Value::Array(vec![]),
985976
},

src/diagnostic.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,6 @@ mod private {
231231

232232
#[cfg(test)]
233233
mod tests {
234-
use super::*;
235-
use crate::{Pointer, PointerBuf};
236234
#[test]
237235
#[cfg(all(
238236
feature = "assign",
@@ -241,6 +239,8 @@ mod tests {
241239
feature = "json"
242240
))]
243241
fn assign_error() {
242+
use crate::{diagnostic::Diagnose, PointerBuf};
243+
244244
let mut v = serde_json::json!({"foo": {"bar": ["0"]}});
245245
let ptr = PointerBuf::parse("/foo/bar/invalid/cannot/reach").unwrap();
246246
let report = ptr.assign(&mut v, "qux").diagnose(ptr).unwrap_err();
@@ -259,6 +259,7 @@ mod tests {
259259
feature = "json"
260260
))]
261261
fn resolve_error() {
262+
use crate::{diagnostic::Diagnose, PointerBuf};
262263
let v = serde_json::json!({"foo": {"bar": ["0"]}});
263264
let ptr = PointerBuf::parse("/foo/bar/invalid/cannot/reach").unwrap();
264265
let report = ptr.resolve(&v).diagnose(ptr).unwrap_err();
@@ -272,6 +273,7 @@ mod tests {
272273
#[test]
273274
#[cfg(feature = "miette")]
274275
fn parse_error() {
276+
use crate::{diagnostic::Diagnose, Pointer, PointerBuf};
275277
let invalid = "/foo/bar/invalid~3~encoding/cannot/reach";
276278
let report = Pointer::parse(invalid).diagnose(invalid).unwrap_err();
277279

src/index.rs

Lines changed: 167 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@
3535
//! assert_eq!(Index::Next.for_len_unchecked(30), 30);
3636
//! ```
3737
38-
use crate::Token;
39-
use alloc::string::String;
40-
use core::{fmt, num::ParseIntError, str::FromStr};
38+
use crate::{
39+
diagnostic::{diagnostic_url, Diagnostic, Label},
40+
Token,
41+
};
42+
use alloc::{boxed::Box, string::String};
43+
use core::{fmt, iter::once, num::ParseIntError, str::FromStr};
4144

4245
/// Represents an abstract index into an array.
4346
///
@@ -177,7 +180,6 @@ impl FromStr for Index {
177180
// representing a `usize` but not allowed in RFC 6901 array
178181
// indices
179182
Err(ParseIndexError::InvalidCharacter(InvalidCharacterError {
180-
source: String::from(s),
181183
offset,
182184
}))
183185
},
@@ -309,6 +311,88 @@ impl fmt::Display for ParseIndexError {
309311
}
310312
}
311313

314+
// shouldn't be used directly, but is part of a public interface
315+
#[doc(hidden)]
316+
#[derive(Debug)]
317+
pub enum StringOrToken {
318+
String(String),
319+
Token(Token<'static>),
320+
}
321+
322+
impl From<String> for StringOrToken {
323+
fn from(value: String) -> Self {
324+
Self::String(value)
325+
}
326+
}
327+
328+
impl From<Token<'static>> for StringOrToken {
329+
fn from(value: Token<'static>) -> Self {
330+
Self::Token(value)
331+
}
332+
}
333+
334+
impl core::ops::Deref for StringOrToken {
335+
type Target = str;
336+
337+
fn deref(&self) -> &Self::Target {
338+
match self {
339+
StringOrToken::String(s) => s.as_str(),
340+
StringOrToken::Token(t) => t.encoded(),
341+
}
342+
}
343+
}
344+
345+
#[cfg(feature = "miette")]
346+
impl miette::SourceCode for StringOrToken {
347+
fn read_span<'a>(
348+
&'a self,
349+
span: &miette::SourceSpan,
350+
context_lines_before: usize,
351+
context_lines_after: usize,
352+
) -> Result<Box<dyn miette::SpanContents<'a> + 'a>, miette::MietteError> {
353+
let s: &str = &**self;
354+
s.read_span(span, context_lines_before, context_lines_after)
355+
}
356+
}
357+
358+
impl Diagnostic for ParseIndexError {
359+
type Subject = StringOrToken;
360+
361+
fn url() -> &'static str {
362+
diagnostic_url!(enum ParseIndexError)
363+
}
364+
365+
fn labels(
366+
&self,
367+
subject: &Self::Subject,
368+
) -> Option<Box<dyn Iterator<Item = crate::diagnostic::Label>>> {
369+
let subject = &**subject;
370+
match self {
371+
ParseIndexError::InvalidInteger(_) => None,
372+
ParseIndexError::LeadingZeros => {
373+
let len = subject
374+
.chars()
375+
.position(|c| c != '0')
376+
.expect("starts with zeros");
377+
let text = String::from("leading zeros");
378+
Some(Box::new(once(Label::new(text, 0, len))))
379+
}
380+
ParseIndexError::InvalidCharacter(err) => {
381+
let len = subject
382+
.chars()
383+
.skip(err.offset)
384+
.position(|c| c.is_ascii_digit())
385+
.unwrap_or(subject.len());
386+
let text = String::from("invalid character(s)");
387+
Some(Box::new(once(Label::new(text, err.offset, len))))
388+
}
389+
}
390+
}
391+
}
392+
393+
#[cfg(feature = "miette")]
394+
impl miette::Diagnostic for ParseIndexError {}
395+
312396
#[cfg(feature = "std")]
313397
impl std::error::Error for ParseIndexError {
314398
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
@@ -323,7 +407,6 @@ impl std::error::Error for ParseIndexError {
323407
/// Indicates that a non-digit character was found when parsing the RFC 6901 array index.
324408
#[derive(Debug, Clone, PartialEq, Eq)]
325409
pub struct InvalidCharacterError {
326-
pub(crate) source: String,
327410
pub(crate) offset: usize,
328411
}
329412

@@ -334,29 +417,14 @@ impl InvalidCharacterError {
334417
pub fn offset(&self) -> usize {
335418
self.offset
336419
}
337-
338-
/// Returns the source string.
339-
pub fn source(&self) -> &str {
340-
&self.source
341-
}
342-
343-
/// Returns the offending character.
344-
#[allow(clippy::missing_panics_doc)]
345-
pub fn char(&self) -> char {
346-
self.source
347-
.chars()
348-
.nth(self.offset)
349-
.expect("char was found at offset")
350-
}
351420
}
352421

353422
impl fmt::Display for InvalidCharacterError {
354423
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355424
write!(
356425
f,
357-
"token contains the non-digit character '{}', \
358-
which is disallowed by RFC 6901",
359-
self.char()
426+
"token contains a non-digit character, \
427+
which is disallowed by RFC 6901",
360428
)
361429
}
362430
}
@@ -377,7 +445,7 @@ impl std::error::Error for InvalidCharacterError {}
377445
#[cfg(test)]
378446
mod tests {
379447
use super::*;
380-
use crate::Token;
448+
use crate::{Diagnose, Token};
381449

382450
#[test]
383451
fn index_from_usize() {
@@ -467,4 +535,80 @@ mod tests {
467535
let index = Index::try_from(&token).unwrap();
468536
assert_eq!(index, Index::Next);
469537
}
538+
539+
#[test]
540+
fn diagnose_works_with_token_or_string() {
541+
let token = Token::new("foo");
542+
// despite the clone, this is cheap because `token` is borrowed
543+
Index::try_from(token.clone()).diagnose(token).unwrap_err();
544+
let s = String::from("bar");
545+
Index::try_from(&s).diagnose(s).unwrap_err();
546+
}
547+
548+
#[test]
549+
fn error_from_invalid_chars() {
550+
let s = String::from("bar");
551+
let err = Index::try_from(&s).diagnose(s).unwrap_err();
552+
553+
#[cfg(feature = "miette")]
554+
{
555+
let labels: Vec<_> = miette::Diagnostic::labels(&err)
556+
.unwrap()
557+
.into_iter()
558+
.collect();
559+
assert_eq!(
560+
labels,
561+
vec![miette::LabeledSpan::new(
562+
Some("invalid character(s)".into()),
563+
0,
564+
3
565+
)]
566+
);
567+
}
568+
569+
let (src, sub) = err.decompose();
570+
let labels: Vec<_> = src.labels(&sub).unwrap().into_iter().collect();
571+
572+
assert_eq!(
573+
labels,
574+
vec![Label::new("invalid character(s)".into(), 0, 3)]
575+
);
576+
}
577+
578+
#[test]
579+
fn error_from_leading_zeros() {
580+
let s = String::from("000001");
581+
let err = Index::try_from(&s).diagnose(s).unwrap_err();
582+
583+
#[cfg(feature = "miette")]
584+
{
585+
let labels: Vec<_> = miette::Diagnostic::labels(&err)
586+
.unwrap()
587+
.into_iter()
588+
.collect();
589+
assert_eq!(
590+
labels,
591+
vec![miette::LabeledSpan::new(Some("leading zeros".into()), 0, 5)]
592+
);
593+
}
594+
595+
let (src, sub) = err.decompose();
596+
let labels: Vec<_> = src.labels(&sub).unwrap().into_iter().collect();
597+
598+
assert_eq!(labels, vec![Label::new("leading zeros".into(), 0, 5)]);
599+
}
600+
601+
#[test]
602+
fn error_from_empty_string() {
603+
let s = String::from("");
604+
let err = Index::try_from(&s).diagnose(s).unwrap_err();
605+
606+
#[cfg(feature = "miette")]
607+
{
608+
assert!(miette::Diagnostic::labels(&err).is_none());
609+
}
610+
611+
let (src, sub) = err.decompose();
612+
assert!(src.labels(&sub).is_none());
613+
}
470614
}

0 commit comments

Comments
 (0)