ftracker_identifiers/isin/error.rs
1use core::fmt;
2
3/// The class of characters permitted at a given position of an ISIN.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
5pub enum CharacterClass {
6 /// An ASCII digit, `'0'...='9'` (the check digit at position 12).
7 Digit,
8 /// An uppercase ASCII letter, `'A'...='Z'` (the two-character country code).
9 Letter,
10 /// An ASCII digit or an uppercase ASCII letter, `'0'...='9' | 'A'...='Z'` (the NSIN body).
11 Alphanumeric,
12}
13
14impl fmt::Display for CharacterClass {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match self {
17 CharacterClass::Digit => write!(f, "a digit (0-9)"),
18 CharacterClass::Letter => write!(f, "an uppercase letter (A-Z)"),
19 CharacterClass::Alphanumeric => {
20 write!(f, "a digit (0-9) or an uppercase letter (A-Z)")
21 }
22 }
23 }
24}
25
26/// The set of reasons an ISIN string can fail validation.
27///
28/// Every fallible constructor of [`Isin`](super::Isin) returns this type; each variant maps to a
29/// single, specific failure so callers can react programmatically (for example, highlighting the
30/// offending character in a form field) rather than parsing a human-readable message.
31#[non_exhaustive]
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub enum IsinError {
34 /// The input was an empty string.
35 Empty,
36
37 /// After trimming surrounding whitespace, the input did not contain exactly 12 characters.
38 InvalidLength {
39 /// The number of characters found after trimming.
40 found: usize,
41 },
42
43 /// A character outside the allowed set was found at a given position.
44 InvalidCharacter {
45 /// The offending character, as originally provided (before case folding).
46 character: char,
47 /// 1-indexed position within the 12 characters.
48 position: u8,
49 /// The character class that was expected at this position.
50 expected: CharacterClass,
51 },
52
53 /// The Luhn check digit (position 12) did not match the value computed from the first 11
54 /// characters.
55 InvalidCheckDigit {
56 /// The check digit computed by the ISO 6166 Luhn algorithm.
57 expected: u8,
58 /// The check digit actually present in the input.
59 found: u8,
60 },
61}
62
63impl fmt::Display for IsinError {
64 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65 match self {
66 IsinError::Empty => f.write_str("ISIN input is empty"),
67 IsinError::InvalidLength { found } => {
68 write!(f, "ISIN must contain exactly 12 characters, found {found}")
69 }
70 IsinError::InvalidCharacter {
71 character,
72 position,
73 expected,
74 } => write!(
75 f,
76 "invalid character '{character}' at position {position} of 12: expected {expected}"
77 ),
78 IsinError::InvalidCheckDigit { expected, found } => write!(
79 f,
80 "invalid check digit at position 12 of 12: expected {expected}, found {found}"
81 ),
82 }
83 }
84}
85
86impl core::error::Error for IsinError {}