cn_id_card/
lib.rs

1// #![deny(missing_docs)]
2extern 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    //check region code
18    // let region_code: String = id_number.chars().take(6).collect();
19    #[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    //check date
30    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    //transform the chars except the last to u32
38    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    //transform the chars except the last to u32
57    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}