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" ) ]
313397impl 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 ) ]
325409pub 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
353422impl 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) ]
378446mod 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