1use core::str;
2use std::str::FromStr;
3
4const SEQUENCE_FIRST_CHECKSUM_DIGITS: &'static [u8; 9] = &[3, 7, 6, 1, 8, 9, 4, 5, 2];
5const SEQUENCE_SECOND_CHECKSUM_DIGITS: &'static [u8; 10] = &[5, 4, 3, 2, 7, 6, 5, 4, 3, 2];
6const TIN_LENGTH: usize = 11;
7const ORG_LENGTH: usize = 9;
8const SEQUENCE_ORG_CHECKSUM_DIGITS: &'static [u8; 8] = &[3, 2, 7, 6, 5, 4, 3, 2];
9
10#[derive(Debug, PartialEq, Eq, Clone, Copy)]
11#[non_exhaustive]
12pub enum PersonKind {
13 Normal,
14 HNumber,
15 Anonymous,
16 Synthetic,
17}
18
19#[derive(Debug, PartialEq, Eq, Clone, Copy)]
20pub struct PersonNumber {
21 kind: PersonKind,
22 value: [u8; TIN_LENGTH],
23}
24
25#[derive(Debug, PartialEq, Eq, Clone, Copy)]
26pub struct OrgNumber {
27 value: [u8; ORG_LENGTH],
28}
29
30#[derive(Debug, PartialEq, Eq, Clone, Copy)]
31#[non_exhaustive]
32pub enum NorwegianTin {
33 FNumber(PersonNumber),
34 DNumber(PersonNumber),
35 OrgNumber(OrgNumber),
36}
37
38#[derive(Debug, PartialEq)]
39pub enum NorwegianTinError {
40 InvalidLength,
41 NonNumericValue,
42 InvalidChecksum,
43 InvalidDate,
44}
45
46impl std::fmt::Display for NorwegianTinError {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 NorwegianTinError::InvalidLength => write!(f, "InvalidLength"),
50 NorwegianTinError::NonNumericValue => write!(f, "NonNumericValue"),
51 NorwegianTinError::InvalidChecksum => write!(f, "InvalidChecksum"),
52 NorwegianTinError::InvalidDate => write!(f, "InvalidDate"),
53 }
54 }
55}
56
57impl FromStr for NorwegianTin {
58 type Err = NorwegianTinError;
59 fn from_str(s: &str) -> Result<Self, Self::Err> {
60 Self::parse(s)
61 }
62}
63
64impl Into<String> for NorwegianTin {
65 fn into(self) -> String {
66 let bytes = self.get_value();
67 let s: String = bytes.iter().map(|&d| (d + b'0') as char).collect();
68 s
69 }
70}
71
72impl AsRef<[u8]> for NorwegianTin {
73 fn as_ref(&self) -> &[u8] {
74 self.get_value()
75 }
76}
77
78impl std::fmt::Display for NorwegianTin {
79 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80 let s: String = self.clone().into();
81 let kind = match self.get_kind() {
82 PersonKind::Anonymous => " (Anonymous) ",
83 PersonKind::HNumber => " (H-Number) ",
84 PersonKind::Synthetic => " (Synthetic) ",
85 PersonKind::Normal => "",
86 };
87 write!(f, "{}{}*****", kind, &s[0..6])
89 }
90}
91impl NorwegianTin {
92 pub fn get_value(&self) -> &[u8] {
93 match self {
94 NorwegianTin::FNumber(fnr) => &fnr.value,
95 NorwegianTin::DNumber(dnr) => &dnr.value,
96 NorwegianTin::OrgNumber(org) => &org.value,
97 }
98 }
99 pub fn get_kind(&self) -> PersonKind {
100 match self {
101 NorwegianTin::FNumber(fnr) => fnr.kind,
102 NorwegianTin::DNumber(dnr) => dnr.kind,
103 NorwegianTin::OrgNumber(_) => PersonKind::Normal, }
105 }
106
107 pub fn parse(s: &str) -> Result<NorwegianTin, NorwegianTinError> {
108 let bytes = s.as_bytes();
109 if bytes.len() != TIN_LENGTH && bytes.len() != ORG_LENGTH {
110 return Err(NorwegianTinError::InvalidLength);
111 }
112
113 let mut digits = [0u8; TIN_LENGTH];
114 for (i, &b) in bytes.iter().enumerate() {
115 if b < b'0' || b > b'9' {
116 return Err(NorwegianTinError::NonNumericValue);
117 }
118 digits[i] = b - b'0';
119 }
120 if bytes.len() == ORG_LENGTH {
121 let _ =
122 Self::calculate_checksum(
123 &digits[0..8],
124 SEQUENCE_ORG_CHECKSUM_DIGITS,
125 |r| match (11 - r) % 11 {
126 10 => Err(NorwegianTinError::InvalidChecksum),
127 v if v == digits[8] => Ok(()),
128 _ => Err(NorwegianTinError::InvalidChecksum),
129 },
130 )?;
131
132 return Ok(NorwegianTin::OrgNumber(OrgNumber {
133 value: digits[0..9].try_into().unwrap(),
134 }));
135 }
136
137 let _ =
138 Self::calculate_checksum(&digits[0..9], SEQUENCE_FIRST_CHECKSUM_DIGITS, |r| {
139 match (r + digits[9]) % 11 {
140 0..=3 => Ok(()),
141 _ => Err(NorwegianTinError::InvalidChecksum),
142 }
143 })?;
144 let _ =
145 Self::calculate_checksum(
146 &digits[0..10],
147 SEQUENCE_SECOND_CHECKSUM_DIGITS,
148 |r| match (r + digits[10]) % 11 {
149 0 => Ok(()),
150 _ => Err(NorwegianTinError::InvalidChecksum),
151 },
152 )?;
153 let kind = Self::check_kind(digits[2])?;
154
155 let day = digits[0] * 10 + digits[1];
156 let month = kind.get_base_month(digits[2] * 10 + digits[3]);
157 let year = digits[4] as u16 * 10 + digits[5] as u16;
158 match digits[0] {
160 0..=3 => {
161 if !Self::is_valid_date(day, month, year) {
163 return Err(NorwegianTinError::InvalidDate);
164 }
165 Ok(NorwegianTin::FNumber(PersonNumber {
166 kind: kind,
167 value: digits,
168 }))
169 }
170 4..=7 => {
171 let actual_day = day - 40;
172 if !Self::is_valid_date(actual_day, month, year) {
173 return Err(NorwegianTinError::InvalidDate);
174 }
175 Ok(NorwegianTin::DNumber(PersonNumber {
176 kind: kind,
177 value: digits,
178 }))
179 }
180 _ => Err(NorwegianTinError::InvalidDate),
181 }
182 }
183
184 fn check_kind(month: u8) -> Result<PersonKind, NorwegianTinError> {
185 match month {
186 0..=1 => Ok(PersonKind::Normal),
187 4..=5 => Ok(PersonKind::HNumber),
188 6..=7 => Ok(PersonKind::Anonymous),
189 8..=9 => Ok(PersonKind::Synthetic),
190 _ => Err(NorwegianTinError::InvalidDate),
191 }
192 }
193
194 fn calculate_checksum<T: FnOnce(u8) -> Result<(), NorwegianTinError>>(
195 digits: &[u8],
196 weights: &[u8],
197 matcher: T,
198 ) -> Result<(), NorwegianTinError> {
199 let sum: u32 = weights
200 .iter()
201 .zip(digits.iter())
202 .map(|(&w, &d)| w as u32 * d as u32)
203 .sum();
204 let remainder = (sum % 11) as u8;
205 matcher(remainder)
206 }
207
208 fn is_valid_date(day: u8, month: u8, year: u16) -> bool {
209 if month == 0 || month > 12 || day == 0 || year >= 100 {
210 return false;
211 }
212 let days_in_month = match month {
213 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
214 4 | 6 | 9 | 11 => 30,
215 2 => {
216 if year % 4 == 0 {
217 29
218 } else {
219 28
220 }
221 }
222 _ => return false,
223 };
224 day <= days_in_month
225 }
226}
227
228impl PersonKind {
229 pub fn is_test_id(&self) -> bool {
230 match self {
231 PersonKind::Normal => false,
232 PersonKind::HNumber => true,
233 PersonKind::Anonymous => true,
234 PersonKind::Synthetic => true,
235 }
236 }
237 fn get_base_month(&self, month: u8) -> u8 {
238 match self {
239 PersonKind::Normal => month,
240 PersonKind::HNumber => month - 40,
241 PersonKind::Anonymous => month - 60,
242 PersonKind::Synthetic => month - 80,
243 }
244 }
245}
246
247#[cfg(test)]
248mod test {
249 use std::vec;
250
251 pub use super::*;
252 #[test]
253 fn test_invalid_length() {
254 let tins = vec!["0123456789", "123456789012", "123", "12345678", ""];
255 for tin in tins {
256 assert_eq!(
257 NorwegianTin::parse(tin).unwrap_err(),
258 NorwegianTinError::InvalidLength
259 );
260 }
261 }
262 #[test]
263 fn test_invalid_characters() {
264 let tins = vec!["1234567890a", "abcdefghijk", "12345abc678"];
265 for tin in tins {
266 assert_eq!(
267 NorwegianTin::parse(tin).unwrap_err(),
268 NorwegianTinError::NonNumericValue
269 );
270 }
271 }
272 #[test]
273 fn test_invalid_checksum() {
274 let tins = vec![
275 "12345678901",
276 "11111111111",
277 "22222222222",
278 "33333333333",
279 "44444444444",
280 "55555555555",
281 "66666666666",
282 "77777777777",
283 "88888888888",
284 "99999999999",
285 "12345678912",
286 "98765432109",
287 ];
288 for tin in tins {
289 assert_eq!(
290 NorwegianTin::parse(tin).unwrap_err(),
291 NorwegianTinError::InvalidChecksum
292 );
293 }
294 }
295
296 #[test]
297 fn test_2032_format() {
298 let tins = vec!["11010000000", "11010000019", "11010000027", "11010000035"];
299 for tin in tins {
300 assert!(NorwegianTin::parse(tin).is_ok());
301 }
302 }
303
304 #[test]
305 fn test_invalid_date() {
306 let tins = vec!["00000000000", "11001000073"];
307 for tin in tins {
308 assert_eq!(
309 NorwegianTin::parse(tin).unwrap_err(),
310 NorwegianTinError::InvalidDate
311 );
312 }
313 }
314
315 #[test]
316 fn test_display() {
317 let err: NorwegianTinError = "00000000000".parse::<NorwegianTin>().unwrap_err();
318 assert_eq!(format!("{}", err), "InvalidDate");
319
320 let tin: NorwegianTin = "16057902284".parse().unwrap();
321 assert_eq!(format!("{}", tin), "160579*****");
322
323 let tin: NorwegianTin = "22517149261".parse().unwrap();
324 assert_eq!(format!("{}", tin), " (H-Number) 225171*****");
325
326 let tin: NorwegianTin = "08639815316".parse().unwrap();
327 assert_eq!(format!("{}", tin), " (Anonymous) 086398*****");
328
329 let tin: NorwegianTin = "70887100797".parse().unwrap();
330 assert_eq!(format!("{}", tin), " (Synthetic) 708871*****");
331 }
332
333 #[test]
334 fn test_valid_f_number() {
335 let tins = vec![
336 "16057902284",
337 "09063332523",
338 "17068632781",
339 "20069290990",
340 "06071732280",
341 "14033147466",
342 "24059638544",
343 "26073600501",
344 "24043444170",
345 "06113401662",
346 "25098514123",
347 "04015896960",
348 "28076429032",
349 "27017709351",
350 "17051023862",
351 "10093039144",
352 "16048999194",
353 "15019401615",
354 "08057294664",
355 "19045132024",
356 "29067796398",
357 "14056549677",
358 "26111739128",
359 "10103621777",
360 "17120108360",
361 "24041646671",
362 "23082311440",
363 "12042637996",
364 "01052609131",
365 "10016402791",
366 "10023737472",
367 "05048334972",
368 "23129407840",
369 "20090624106",
370 "17047438926",
371 "28093304286",
372 "24052024052",
373 "28128590643",
374 "12123717110",
375 "30123017591",
376 "17093748212",
377 "24084932503",
378 "30091723170",
379 "16032039514",
380 "09100106539",
381 "03078922159",
382 "23016141046",
383 "07119746641",
384 "14093744021",
385 "19057647348",
386 "02013299997",
387 ];
388 for tin in tins {
389 assert_eq!(NorwegianTin::parse(tin).is_ok(), true);
390 assert!(matches!(
391 NorwegianTin::parse(tin).unwrap(),
392 NorwegianTin::FNumber(_)
393 ));
394 assert_eq!(
395 NorwegianTin::parse(tin).unwrap().get_kind(),
396 PersonKind::Normal
397 );
398 }
399 }
400 #[test]
401 fn test_h_number_fnr() {
402 let tins = vec![
403 "22517149261",
404 "16501622854",
405 "10496524328",
406 "22492434063",
407 "30512441595",
408 "26420623894",
409 "15446406660",
410 "11490936698",
411 "31419107320",
412 "09486435124",
413 "29497811978",
414 "24516544984",
415 "28449596104",
416 "21482101531",
417 "09526738303",
418 "31473336629",
419 "04520606691",
420 "07502323096",
421 "24484292627",
422 "19468111414",
423 "07467214181",
424 "30418893342",
425 "09449633846",
426 "17507311458",
427 "03477998770",
428 "22487823574",
429 "02492215786",
430 "12512213926",
431 "21434290810",
432 "03431642904",
433 "06490734730",
434 "04424032617",
435 "16484997113",
436 "15424333392",
437 "02455927105",
438 "19431825034",
439 "23453707415",
440 "02498505069",
441 "14419493563",
442 "28519698023",
443 "08474914586",
444 "29457592246",
445 "10467613974",
446 "21436941169",
447 "09443311979",
448 "25431429198",
449 "19412412594",
450 "01527994698",
451 "18483710999",
452 "29500441176",
453 ];
454
455 for tin in tins {
456 assert_eq!(NorwegianTin::parse(tin).is_ok(), true);
457 assert!(matches!(
458 NorwegianTin::parse(tin).unwrap(),
459 NorwegianTin::FNumber(_)
460 ));
461 assert_eq!(
462 NorwegianTin::parse(tin).unwrap().get_kind(),
463 PersonKind::HNumber
464 );
465 }
466 }
467
468 #[test]
469 fn test_synthetic_dnr() {
470 let dnr = vec![
471 "70848000149",
472 "56865400190",
473 "60889201749",
474 "70859800961",
475 "61915201511",
476 "70887100797",
477 "47914500210",
478 "52909301009",
479 "49867500528",
480 "55896000267",
481 "62847600204",
482 "65929301905",
483 "45817500521",
484 "56878500771",
485 "52910875191",
486 "44908800521",
487 "54815601311",
488 "57839200716",
489 "44915500312",
490 "61887100240",
491 "70875300782",
492 "47858100948",
493 "64857300139",
494 "46826902183",
495 "45819901183",
496 "50828300927",
497 "55826700334",
498 "50924700936",
499 "49878300261",
500 "48898400142",
501 "46885500463",
502 "58925800463",
503 "44883300154",
504 "41901200279",
505 "69915100880",
506 "45896500361",
507 "59918601168",
508 "54928500449",
509 "45875301105",
510 "64858900172",
511 "50866301562",
512 "41916500333",
513 "57875400381",
514 "47869000198",
515 "56836700363",
516 "63843400118",
517 "55918100171",
518 "49818302737",
519 "50866900512",
520 "70924700201",
521 ];
522 for tin in dnr {
523 assert_eq!(NorwegianTin::parse(tin).is_ok(), true);
524 assert!(matches!(
525 NorwegianTin::parse(tin).unwrap(),
526 NorwegianTin::DNumber(_)
527 ));
528 assert_eq!(
529 NorwegianTin::parse(tin).unwrap().get_kind(),
530 PersonKind::Synthetic
531 );
532 }
533 }
534 #[test]
535 fn test_anonymous_fnr() {
536 let tins = vec![
537 "08639815316",
538 "13620315545",
539 "26622700351",
540 "09624093701",
541 "06673803375",
542 "17639516431",
543 "06721148228",
544 "08610428933",
545 "01713417137",
546 "02611935342",
547 "21646200102",
548 "05654134997",
549 "28616909969",
550 "06695929704",
551 "03690058307",
552 "28711842685",
553 "10706095352",
554 "19655548648",
555 "11671742127",
556 "20640081725",
557 ];
558 for tin in tins {
559 assert_eq!(NorwegianTin::parse(tin).is_ok(), true);
560 assert!(matches!(
561 NorwegianTin::parse(tin).unwrap(),
562 NorwegianTin::FNumber(_)
563 ));
564 assert_eq!(
565 NorwegianTin::parse(tin).unwrap().get_kind(),
566 PersonKind::Anonymous
567 );
568 }
569 }
570
571 #[test]
572 fn test_org_number() {
573 let orgs = vec![
574 "905661833",
575 "085649779",
576 "255399985",
577 "917766150",
578 "406099474",
579 "169994803",
580 "127412626",
581 "661532777",
582 "627143508",
583 "532464390",
584 "625711045",
585 "968668056",
586 "452238063",
587 "882897311",
588 "428621840",
589 "134511966",
590 "565768123",
591 "888302964",
592 "360559912",
593 "635203536",
594 "557117245",
595 "021244716",
596 "331370207",
597 "639703991",
598 "769676260",
599 "084416371",
600 "635606576",
601 "190537102",
602 "945867159",
603 "350759131",
604 "008303274",
605 "515559396",
606 "381740196",
607 "001686313",
608 "977279410",
609 "282720493",
610 "052965152",
611 "310958352",
612 "687739515",
613 "399784646",
614 "389213292",
615 "638522837",
616 "981000463",
617 "973289829",
618 "934708431",
619 "094979358",
620 "003221571",
621 "283978958",
622 "003437353",
623 "123480759",
624 ];
625 for org in orgs {
626 assert!(matches!(
627 NorwegianTin::parse(org).unwrap(),
628 NorwegianTin::OrgNumber(_)
629 ));
630 }
631 }
632 #[test]
633 fn test_org_number_invalid() {
634 let orgs = vec!["905661834", "085649778", "255399984", "917766151"];
635 for org in orgs {
636 assert_eq!(
637 NorwegianTin::parse(org).unwrap_err(),
638 NorwegianTinError::InvalidChecksum
639 );
640 }
641 }
642}