1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
//! The error type returned by [`NHSNumber::from_str`](crate::NHSNumber).
//!
//! `ParseError` is a zero-sized unit struct that signals "this string is
//! not a syntactically valid NHS Number". It deliberately carries no
//! detail — callers who need a richer taxonomy (wrong length, wrong
//! separator, non-digit character, …) wrap or map it at the parse site.
//!
//! See [`spec/index.md`](https://github.com/joelparkerhenderson/nhs-number-using-rust/blob/main/spec/index.md)
//! §12 for the design rationale.
/// Error returned by `<NHSNumber as FromStr>::from_str` for any string
/// that is not one of the two accepted shapes (see
/// [`spec/05-string-forms.md`] §5).
///
/// `ParseError` is a unit struct — every error value compares equal:
///
/// ```rust
/// use nhs_number::NHSNumber;
/// use nhs_number::parse_error::ParseError;
/// use std::str::FromStr;
///
/// let a = NHSNumber::from_str("not even close").unwrap_err();
/// let b = NHSNumber::from_str("wrong length").unwrap_err();
/// assert_eq!(a, b);
/// assert_eq!(a, ParseError);
/// ```
///
/// To map it to your own richer error type:
///
/// ```rust
/// use nhs_number::NHSNumber;
/// use std::str::FromStr;
///
/// #[derive(Debug, PartialEq)]
/// enum MyError {
/// BadNhsNumber(String),
/// }
///
/// let bad = "not a number";
/// let result: Result<NHSNumber, MyError> =
/// NHSNumber::from_str(bad).map_err(|_| MyError::BadNhsNumber(bad.into()));
/// assert_eq!(result, Err(MyError::BadNhsNumber("not a number".into())));
/// ```
///
/// `ParseError` also implements [`Display`](std::fmt::Display) and
/// [`std::error::Error`], so it flows through `?` into boxed-error
/// stacks without manual mapping:
///
/// ```rust
/// use nhs_number::NHSNumber;
/// use std::error::Error;
/// use std::str::FromStr;
///
/// fn parse(s: &str) -> Result<NHSNumber, Box<dyn Error>> {
/// Ok(NHSNumber::from_str(s)?)
/// }
///
/// assert!(parse("999 100 0003").is_ok());
/// assert!(parse("not a number").is_err());
/// ```
///
/// [`spec/05-string-forms.md`]: https://github.com/joelparkerhenderson/nhs-number-using-rust/blob/main/spec/05-string-forms.md
;
/// A fixed message with **no input echo** — the rejected candidate
/// string must never leak into error messages or logs
/// (see `AGENTS/safety.md` §3).
///
/// ```rust
/// use nhs_number::parse_error::ParseError;
///
/// assert_eq!(ParseError.to_string(), "invalid NHS Number string");
/// ```