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