1use std::{convert::TryInto, fmt};
4
5#[derive(Debug, Copy, Clone, PartialEq, Eq)]
6#[non_exhaustive]
7pub enum SINParseError {
9 TooLong,
10 TooShort,
11 InvalidChecksum,
12}
13
14#[derive(Debug, Copy, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum SINType {
18 CRAAssigned,
21 TemporaryResident,
22 BusinessNumber,
24 OverseasForces,
26 Alberta,
27 BritishColumbia,
28 Manitoba,
29 NewBrunswick,
30 NewfoundlandLabrador,
31 NorthwestTerritories,
32 NovaScotia,
33 Nunavut,
34 Ontario,
35 PrinceEdwardIsland,
36 Quebec,
37 Saskatchewan,
38 Yukon,
39}
40
41impl SINType {
42 pub fn is_province(self) -> bool {
44 use SINType::*;
45 matches!(
46 self,
47 Alberta
48 | BritishColumbia
49 | Manitoba
50 | NewBrunswick
51 | NewfoundlandLabrador
52 | NorthwestTerritories
53 | NovaScotia
54 | Nunavut
55 | Ontario
56 | PrinceEdwardIsland
57 | Quebec
58 | Saskatchewan
59 | Yukon
60 )
61 }
62 pub fn is_human(self) -> bool {
64 !matches!(self, Self::BusinessNumber)
65 }
66}
67
68#[derive(Debug, Copy, Clone, Eq, PartialEq)]
69pub struct SIN {
71 inner_digits: [u8; 9],
72}
73
74impl SIN {
75 pub fn parse(s: String) -> Result<Self, SINParseError> {
83 let mut digits = Vec::with_capacity(9);
84 for khar in s.chars() {
85 if let Some(digit) = khar.to_digit(10) {
86 digits.push(digit as u8);
87 };
88 }
89 match digits.len() {
90 n if n < 9 => return Err(SINParseError::TooShort),
91 n if n > 9 => return Err(SINParseError::TooLong),
92 9 => {
93 let luhn_sum: u8 = digits
95 .iter()
96 .enumerate()
97 .map(|(idx, digit)| digit * (if idx % 2 == 0 { 1u8 } else { 2u8 }))
98 .map(|val| {
99 if val > 9 {
100 (val % 10) + 1
103 } else {
104 val
105 }
106 })
107 .sum();
108 if dbg!(luhn_sum) % 10 != 0 {
109 return Err(SINParseError::InvalidChecksum);
110 }
111 }
112 _ => unreachable!(),
113 };
114 let boxed_digits = digits.into_boxed_slice();
115 let boxing_result: Result<Box<[u8; 9]>, _> = boxed_digits.try_into();
116 match boxing_result {
117 Ok(val) => Ok(Self { inner_digits: *val }),
118 Err(_) => unreachable!(),
119 }
120 }
121 pub fn types(&self) -> Vec<SINType> {
131 use SINType::*;
132 match self.inner_digits[0] {
133 0 => vec![CRAAssigned],
134 1 => vec![
135 NovaScotia,
136 NewBrunswick,
137 PrinceEdwardIsland,
138 NewfoundlandLabrador,
139 ],
140 2 | 3 => vec![Quebec],
141 4 | 5 => vec![Ontario, OverseasForces],
142 6 => vec![
143 Ontario,
144 Manitoba,
145 Saskatchewan,
146 Alberta,
147 NorthwestTerritories,
148 Nunavut,
149 ],
150 7 => vec![BritishColumbia, Yukon, BusinessNumber],
151 8 => vec![BusinessNumber],
152 9 => vec![TemporaryResident],
153 _ => unreachable!(),
154 }
155 }
156 pub fn digits(self) -> [u8; 9] {
158 self.inner_digits
159 }
160 fn gen_sin_string_part(part: &[u8]) -> String {
161 part.iter().map(|d| d.to_string()).collect::<String>()
162 }
163 pub fn digits_string(self) -> String {
172 Self::gen_sin_string_part(&self.inner_digits)
173 }
174 pub fn digits_dashed_string(self) -> String {
182 format!(
183 "{}-{}-{}",
184 Self::gen_sin_string_part(&self.inner_digits[0..3]),
185 Self::gen_sin_string_part(&self.inner_digits[3..6]),
186 Self::gen_sin_string_part(&self.inner_digits[6..9]),
187 )
188 }
189}
190
191impl fmt::Display for SIN {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203 write!(f, "{}", self.digits_dashed_string())
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn parse_sin_checks_luhn() {
213 assert_eq!(
214 SIN::parse("123456789".to_string()),
215 Err(SINParseError::InvalidChecksum)
216 );
217 assert_eq!(
218 SIN::parse("425453457".to_string()),
219 Err(SINParseError::InvalidChecksum)
220 );
221 assert_eq!(
222 SIN::parse("759268676".to_string()),
223 Err(SINParseError::InvalidChecksum)
224 );
225 assert_eq!(
226 SIN::parse("635563453".to_string()),
227 Err(SINParseError::InvalidChecksum)
228 );
229 assert_eq!(
231 SIN::parse("999999999".to_string()),
232 Err(SINParseError::InvalidChecksum)
233 );
234 assert!(SIN::parse("046454286".to_string()).is_ok());
235 assert!(SIN::parse("000000000".to_string()).is_ok());
236 }
237
238 #[test]
239 fn parse_sin_checks_too_short() {
240 assert_eq!(
241 SIN::parse("12345678".to_string()),
242 Err(SINParseError::TooShort)
243 );
244 assert_eq!(SIN::parse("123".to_string()), Err(SINParseError::TooShort));
245 assert_eq!(SIN::parse("0".to_string()), Err(SINParseError::TooShort));
246 assert_eq!(SIN::parse("".to_string()), Err(SINParseError::TooShort));
247 }
248
249 #[test]
250 fn parse_sin_checks_too_long() {
251 assert_eq!(
252 SIN::parse("0000000000".to_string()),
253 Err(SINParseError::TooLong)
254 );
255 assert_eq!(
256 SIN::parse("4324234237".to_string()),
257 Err(SINParseError::TooLong)
258 );
259 assert_eq!(
260 SIN::parse("635462452452344343".to_string()),
261 Err(SINParseError::TooLong)
262 );
263 assert_eq!(
264 SIN::parse("999999999999999999999999999".to_string()),
265 Err(SINParseError::TooLong)
266 );
267 assert_eq!(
268 SIN::parse("000000000000000000000000000".to_string()),
269 Err(SINParseError::TooLong)
270 );
271 assert_eq!(
272 SIN::parse("543537672346234345464254235".to_string()),
273 Err(SINParseError::TooLong)
274 );
275 }
276
277 #[test]
278 fn digits_string() {
279 let sin = SIN::parse("000-000-000".to_string()).unwrap();
280 assert_eq!(sin.digits_string(), "000000000");
281 let sin = SIN::parse("999999998".to_string()).unwrap();
282 assert_eq!(sin.digits_string(), "999999998");
283 }
284
285 #[test]
286 fn digits_dashed_string() {
287 let sin = SIN::parse("000-000-000".to_string()).unwrap();
288 assert_eq!(sin.digits_dashed_string(), "000-000-000");
289 let sin = SIN::parse("999999998".to_string()).unwrap();
290 assert_eq!(sin.digits_dashed_string(), "999-999-998");
291 }
292}