1extern crate regex;
3
4#[cfg(feature = "region")]
5pub mod region;
6
7use chrono::NaiveDate;
8use once_cell::sync::Lazy;
9use regex::Regex;
10
11pub fn validate(id_number: &str, validate_region: bool) -> bool {
12 static ID_PATTERN: Lazy<Regex> =
13 Lazy::new(|| Regex::new(r"[1-9][0-9]{14}[0-9]{2}[0-9Xx]").unwrap());
14 if !ID_PATTERN.is_match(id_number) {
15 return false;
16 }
17 #[cfg(feature = "region")]
20 if validate_region && !region::validate_code(&id_number[0..6]) {
21 return false;
22 }
23
24 #[cfg(not(feature = "region"))]
25 if validate_region {
26 panic!("unsupported. To validate region, enable region feature.");
27 }
28
29 let birth_date = NaiveDate::parse_from_str(&id_number[6..14], "%Y%m%d");
31 if birth_date.is_err() {
32 return false;
33 }
34
35 let mut chars = id_number.chars().rev();
36 let check_code = chars.next().unwrap();
37 let items = chars.map(|c| c.to_digit(10).unwrap()).rev();
39 let factors: [u32; 17] = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
40 let s: u32 = items.zip(&factors).map(|(x, y)| x * y).sum();
41 let verify_code_expected = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
42 let modulo = s % 11;
43 verify_code_expected[modulo as usize] == check_code.to_ascii_uppercase()
44}
45
46
47pub fn validate_code(credit_code_number: &str, _validate_region: bool) -> bool {
48 static ID_PATTERN: Lazy<Regex> =
49 Lazy::new(|| Regex::new(r"[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}").unwrap());
50 if !ID_PATTERN.is_match(credit_code_number) {
51 return false;
52 }
53
54 let mut chars = credit_code_number.chars().rev();
55 let check_code = chars.next().unwrap();
56 let items = chars.map(|c| char_to_number(c)).rev();
58 let factors: [u32; 17] = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
59 let s: u32 = items.zip(&factors).map(|(x, y)| x * y).sum();
60 let modulo = 31 -s % 31;
61 modulo == char_to_number(check_code)
62}
63
64fn char_to_number(c: char) -> u32 {
65 match c {
66 '0' => 0,
67 '1' => 1,
68 '2' => 2,
69 '3' => 3,
70 '4' => 4,
71 '5' => 5,
72 '6' => 6,
73 '7' => 7,
74 '8' => 8,
75 '9' => 9,
76 'A' => 10,
77 'B' => 11,
78 'C' => 12,
79 'D' => 13,
80 'E' => 14,
81 'F' => 15,
82 'G' => 16,
83 'H' => 17,
84 'J' => 18,
85 'K' => 19,
86 'L' => 20,
87 'M' => 21,
88 'N' => 22,
89 'P' => 23,
90 'Q' => 24,
91 'R' => 25,
92 'T' => 26,
93 'U' => 27,
94 'W' => 28,
95 'X' => 29,
96 'Y' => 30,
97 _ => 0,
98 }
99}