3636//! ```
3737
3838use crate :: Token ;
39- use alloc:: { string:: String , vec :: Vec } ;
39+ use alloc:: string:: String ;
4040use core:: { fmt, num:: ParseIntError , str:: FromStr } ;
4141
4242/// Represents an abstract index into an array.
@@ -166,21 +166,22 @@ impl FromStr for Index {
166166 } else if s. starts_with ( '0' ) && s != "0" {
167167 Err ( ParseIndexError :: LeadingZeros )
168168 } else {
169- let idx = s. parse :: < usize > ( ) . map ( Index :: Num ) ?;
170- if s. chars ( ) . all ( |c| c. is_ascii_digit ( ) ) {
171- Ok ( idx)
172- } else {
173- // this comes up with the `+` sign which is valid for
174- // representing a `usize` but not allowed in RFC 6901 array
175- // indices
176- let mut invalid: Vec < _ > = s. chars ( ) . filter ( |c| !c. is_ascii_digit ( ) ) . collect ( ) ;
177- // remove duplicate characters
178- invalid. sort_unstable ( ) ;
179- invalid. dedup ( ) ;
180- Err ( ParseIndexError :: InvalidCharacters (
181- invalid. into_iter ( ) . collect ( ) ,
182- ) )
183- }
169+ s. chars ( ) . position ( |c| !c. is_ascii_digit ( ) ) . map_or_else (
170+ || {
171+ s. parse :: < usize > ( )
172+ . map ( Index :: Num )
173+ . map_err ( ParseIndexError :: from)
174+ } ,
175+ |offset| {
176+ // this comes up with the `+` sign which is valid for
177+ // representing a `usize` but not allowed in RFC 6901 array
178+ // indices
179+ Err ( ParseIndexError :: InvalidCharacter ( InvalidCharacterError {
180+ source : String :: from ( s) ,
181+ offset,
182+ } ) )
183+ } ,
184+ )
184185 }
185186 }
186187}
@@ -274,15 +275,15 @@ impl std::error::Error for OutOfBoundsError {}
274275░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
275276*/
276277
277- /// Indicates that the `Token` could not be parsed as valid RFC 6901 index.
278+ /// Indicates that the `Token` could not be parsed as valid RFC 6901 array index.
278279#[ derive( Debug , PartialEq , Eq ) ]
279280pub enum ParseIndexError {
280281 /// The Token does not represent a valid integer.
281282 InvalidInteger ( ParseIntError ) ,
282283 /// The Token contains leading zeros.
283284 LeadingZeros ,
284- /// The Token contains non-digit characters .
285- InvalidCharacters ( String ) ,
285+ /// The Token contains a non-digit character .
286+ InvalidCharacter ( InvalidCharacterError ) ,
286287}
287288
288289impl From < ParseIntError > for ParseIndexError {
@@ -301,11 +302,7 @@ impl fmt::Display for ParseIndexError {
301302 f,
302303 "token contained leading zeros, which are disallowed by RFC 6901"
303304 ) ,
304- ParseIndexError :: InvalidCharacters ( chars) => write ! (
305- f,
306- "token contains non-digit character(s) '{chars}', \
307- which are disallowed by RFC 6901",
308- ) ,
305+ ParseIndexError :: InvalidCharacter ( err) => err. fmt ( f) ,
309306 }
310307 }
311308}
@@ -315,11 +312,56 @@ impl std::error::Error for ParseIndexError {
315312 fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
316313 match self {
317314 ParseIndexError :: InvalidInteger ( source) => Some ( source) ,
318- ParseIndexError :: LeadingZeros | ParseIndexError :: InvalidCharacters ( _) => None ,
315+ ParseIndexError :: InvalidCharacter ( source) => Some ( source) ,
316+ ParseIndexError :: LeadingZeros => None ,
319317 }
320318 }
321319}
322320
321+ /// Indicates that a non-digit character was found when parsing the RFC 6901 array index.
322+ #[ derive( Debug , PartialEq , Eq ) ]
323+ pub struct InvalidCharacterError {
324+ pub ( crate ) source : String ,
325+ pub ( crate ) offset : usize ,
326+ }
327+
328+ impl InvalidCharacterError {
329+ /// Returns the offset of the character in the string.
330+ ///
331+ /// This offset is given in characters, not in bytes.
332+ pub fn offset ( & self ) -> usize {
333+ self . offset
334+ }
335+
336+ /// Returns the source string.
337+ pub fn source ( & self ) -> & str {
338+ & self . source
339+ }
340+
341+ /// Returns the offending character.
342+ #[ allow( clippy:: missing_panics_doc) ]
343+ pub fn char ( & self ) -> char {
344+ self . source
345+ . chars ( )
346+ . nth ( self . offset )
347+ . expect ( "char was found at offset" )
348+ }
349+ }
350+
351+ impl fmt:: Display for InvalidCharacterError {
352+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
353+ write ! (
354+ f,
355+ "token contains the non-digit character '{}', \
356+ which is disallowed by RFC 6901",
357+ self . char ( )
358+ )
359+ }
360+ }
361+
362+ #[ cfg( feature = "std" ) ]
363+ impl std:: error:: Error for InvalidCharacterError { }
364+
323365/*
324366░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
325367╔══════════════════════════════════════════════════════════════════════════════╗
@@ -435,9 +477,13 @@ mod tests {
435477 "token contained leading zeros, which are disallowed by RFC 6901"
436478 ) ;
437479 assert_eq ! (
438- ParseIndexError :: InvalidCharacters ( "+@" . into( ) ) . to_string( ) ,
439- "token contains non-digit character(s) '+@', \
440- which are disallowed by RFC 6901"
480+ ParseIndexError :: InvalidCharacter ( InvalidCharacterError {
481+ source: "+10" . into( ) ,
482+ offset: 0
483+ } )
484+ . to_string( ) ,
485+ "token contains the non-digit character '+', \
486+ which is disallowed by RFC 6901"
441487 ) ;
442488 }
443489
0 commit comments