36
36
//! ```
37
37
38
38
use crate :: Token ;
39
- use alloc:: { string:: String , vec :: Vec } ;
39
+ use alloc:: string:: String ;
40
40
use core:: { fmt, num:: ParseIntError , str:: FromStr } ;
41
41
42
42
/// Represents an abstract index into an array.
@@ -166,21 +166,22 @@ impl FromStr for Index {
166
166
} else if s. starts_with ( '0' ) && s != "0" {
167
167
Err ( ParseIndexError :: LeadingZeros )
168
168
} 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
+ )
184
185
}
185
186
}
186
187
}
@@ -274,15 +275,15 @@ impl std::error::Error for OutOfBoundsError {}
274
275
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
275
276
*/
276
277
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.
278
279
#[ derive( Debug , PartialEq , Eq ) ]
279
280
pub enum ParseIndexError {
280
281
/// The Token does not represent a valid integer.
281
282
InvalidInteger ( ParseIntError ) ,
282
283
/// The Token contains leading zeros.
283
284
LeadingZeros ,
284
- /// The Token contains non-digit characters .
285
- InvalidCharacters ( String ) ,
285
+ /// The Token contains a non-digit character .
286
+ InvalidCharacter ( InvalidCharacterError ) ,
286
287
}
287
288
288
289
impl From < ParseIntError > for ParseIndexError {
@@ -301,11 +302,7 @@ impl fmt::Display for ParseIndexError {
301
302
f,
302
303
"token contained leading zeros, which are disallowed by RFC 6901"
303
304
) ,
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) ,
309
306
}
310
307
}
311
308
}
@@ -315,11 +312,56 @@ impl std::error::Error for ParseIndexError {
315
312
fn source ( & self ) -> Option < & ( dyn std:: error:: Error + ' static ) > {
316
313
match self {
317
314
ParseIndexError :: InvalidInteger ( source) => Some ( source) ,
318
- ParseIndexError :: LeadingZeros | ParseIndexError :: InvalidCharacters ( _) => None ,
315
+ ParseIndexError :: InvalidCharacter ( source) => Some ( source) ,
316
+ ParseIndexError :: LeadingZeros => None ,
319
317
}
320
318
}
321
319
}
322
320
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
+
323
365
/*
324
366
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
325
367
╔══════════════════════════════════════════════════════════════════════════════╗
@@ -435,9 +477,13 @@ mod tests {
435
477
"token contained leading zeros, which are disallowed by RFC 6901"
436
478
) ;
437
479
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"
441
487
) ;
442
488
}
443
489
0 commit comments