nhs_number/
from_str.rs

1use crate::NHSNumber;
2use crate::parse_error::ParseError;
3use std::str::FromStr;
4
5/// Implement the `FromStr` trait for NHSNumber to allow parsing from a string.
6///
7/// This parser allows for optional space separators in the NHS Number string,
8/// so long as the space separators are in their expected positions.
9///
10/// A valid NHS Number format can be either:
11///
12/// - 10 digits e.g. "0123456789"
13///
14/// - 12 characters with spaces e.g. "012 345 6789".
15///
16/// Example:
17///
18/// ```rust
19/// use nhs_number::NHSNumber;
20/// use std::str::FromStr;
21/// let nhs_number_str = "012 345 6789";
22/// let nhs_number: NHSNumber = NHSNumber::from_str(nhs_number_str).unwrap();
23/// assert_eq!(nhs_number.digits, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
24/// ```
25///
26impl FromStr for NHSNumber {
27    type Err = ParseError;
28    fn from_str(s: &str) -> Result<Self, Self::Err> {
29        let chars: Vec<char> = s.chars().collect();
30        match chars.len() {
31            10 => {
32                let mut digits: [i8; 10] = [0; 10];
33                for i in 0..10 {
34                    digits[i] = chars[i].to_digit(10).ok_or(ParseError)? as i8
35                }
36                Ok(NHSNumber { digits: digits })
37            }
38            12 => {
39                if chars[3] != ' ' || chars[7] != ' ' {
40                    return Err(ParseError);
41                }
42                let mut digits: [i8; 10] = [0; 10];
43                for i in 0..3 {
44                    digits[i] = chars[i].to_digit(10).ok_or(ParseError)? as i8
45                }
46                for i in 0..3 {
47                    digits[i + 3] = chars[i + 4].to_digit(10).ok_or(ParseError)? as i8
48                }
49                for i in 0..4 {
50                    digits[i + 6] = chars[i + 8].to_digit(10).ok_or(ParseError)? as i8
51                }
52                Ok(NHSNumber { digits: digits })
53            }
54            _ => {
55                return Err(ParseError);
56            }
57        }
58    }
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64
65    #[test]
66    fn test_from_str_with_length_10_without_spaces() {
67        let s = String::from("0123456789");
68        let actual: NHSNumber = NHSNumber::from_str(&s).unwrap();
69        let expect: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
70        assert_eq!(actual, expect);
71    }
72
73    #[test]
74    fn test_from_str_with_length_12_with_spaces() {
75        let s = String::from("012 345 6789");
76        let actual: NHSNumber = NHSNumber::from_str(&s).unwrap();
77        let expect: NHSNumber = NHSNumber::new([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
78        assert_eq!(actual, expect);
79    }
80
81    #[test]
82    fn test_from_str_with_wrong_characters() {
83        let s = String::from("012-345-6789");
84        let result: Result<NHSNumber, ParseError> = NHSNumber::from_str(&s);
85        assert!(result.is_err());
86    }
87
88    #[test]
89    fn test_from_str_with_wrong_leading_space() {
90        let s = String::from(" 012 345 6789");
91        let result: Result<NHSNumber, ParseError> = NHSNumber::from_str(&s);
92        assert!(result.is_err());
93    }
94
95    #[test]
96    fn test_from_str_with_first_space_without_last_space() {
97        let s = String::from("012 3456789");
98        let result: Result<NHSNumber, ParseError> = NHSNumber::from_str(&s);
99        assert!(result.is_err());
100    }
101
102    #[test]
103    fn test_from_str_without_first_space_with_last_space() {
104        let s = String::from("012345 6789");
105        let result: Result<NHSNumber, ParseError> = NHSNumber::from_str(&s);
106        assert!(result.is_err());
107    }
108
109    #[test]
110    fn test_from_str_with_wrong_inner_space() {
111        let s = String::from("012  345  6789");
112        let result: Result<NHSNumber, ParseError> = NHSNumber::from_str(&s);
113        assert!(result.is_err());
114    }
115
116    #[test]
117    fn test_from_str_with_wrong_trailing_space() {
118        let s = String::from("012 345 6789 ");
119        let result: Result<NHSNumber, ParseError> = NHSNumber::from_str(&s);
120        assert!(result.is_err());
121    }
122
123    #[test]
124    fn test_from_str_with_wrong_length() {
125        let s = String::from("012");
126        let result: Result<NHSNumber, ParseError> = NHSNumber::from_str(&s);
127        assert!(result.is_err());
128    }
129}