Skip to main content

rust_idcard/
lib.rs

1use reqwest::header::AUTHORIZATION;
2use reqwest::Error;
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Serialize, Deserialize)]
7pub struct CodeNameResp {
8    name: String,
9    #[serde(rename = "idNo")]
10    id_no: String,
11    #[serde(rename = "respMessage")]
12    resp_message: String,
13    #[serde(rename = "respCode")]
14    resp_code: String,
15    province: Option<String>,
16    city: Option<String>,
17    county: Option<String>,
18    birthday: Option<String>,
19    sex: Option<String>,
20    age: Option<String>,
21}
22
23fn validate_name(idcard: &str, name: &str, appcode: &str) -> Result<CodeNameResp, Error> {
24    let url = "https://idenauthen.market.alicloudapi.com/idenAuthentication";
25    let params = [("idNo", idcard), ("name", name)];
26    let client = reqwest::Client::new();
27    let res = client
28        .post(url)
29        .form(&params)
30        .header(AUTHORIZATION, format!("APPCODE {}", appcode))
31        .send()?
32        .json::<CodeNameResp>()?;
33
34    Ok(res)
35}
36
37fn validate_idcard(idcard: &str) -> bool {
38    let weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
39    let sum: u32 = idcard
40        .chars()
41        .take(17)
42        .zip(weights.iter())
43        .map(|(d, w)| d.to_digit(10).unwrap_or(10) * w)
44        .sum();
45
46    let code = match sum % 11 {
47        0 => '1',
48        1 => '0',
49        2 => 'X',
50        3 => '9',
51        4 => '8',
52        5 => '7',
53        6 => '6',
54        7 => '5',
55        8 => '4',
56        9 => '3',
57        10 => '2',
58        _ => ' ',
59    };
60
61    match idcard.chars().last() {
62        Some(v) => code == v.to_ascii_uppercase(),
63        None => false,
64    }
65}
66
67pub fn validate(idcard: &str, name: Option<&str>, appcode: Option<&str>) -> Result<bool, Error> {
68    match validate_idcard(idcard) {
69        true => match name {
70            Some(v) => match validate_name(idcard, v, appcode.unwrap_or("")) {
71                Ok(x) => Ok(x.resp_code == "0000"),
72                Err(ex) => Err(ex),
73            },
74
75            None => Ok(true),
76        },
77
78        false => Ok(false),
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    #[test]
85    fn it_works() {
86        assert_eq!(
87            super::validate("510108197205052138", None, None).unwrap(),
88            false
89        );
90
91        assert_eq!(
92            super::validate(
93                "510108197205052137",
94                Some("无名氏"),
95                Some("e61152457c5d41f99d383868d97e328e")
96            )
97            .unwrap(),
98            false
99        );
100
101        assert_eq!(
102            super::validate(
103                "510108197205052137",
104                Some("苏渝"),
105                Some("e61152457c5d41f99d383868d97e328e")
106            )
107            .unwrap(),
108            true
109        );
110
111        assert_eq!(super::validate_idcard("510108197205052137"), true);
112        assert_eq!(super::validate_idcard("15040419840217262X"), true);
113        assert_eq!(super::validate_idcard("15040419840217262x"), true);
114        assert_eq!(super::validate_idcard("150404198402172620"), false);
115        assert_eq!(super::validate_idcard("150404198402"), false);
116        assert_eq!(super::validate_idcard("1"), false);
117        assert_eq!(super::validate_idcard(""), false);
118
119        assert_eq!(
120            super::validate_name(
121                "510108197205052137",
122                "苏渝",
123                "e61152457c5d41f99d383868d97e328e"
124            )
125            .unwrap()
126            .resp_code,
127            "0000"
128        );
129        assert_eq!(
130            super::validate_name(
131                "510108197205052138",
132                "苏渝",
133                "e61152457c5d41f99d383868d97e328e"
134            )
135            .unwrap()
136            .resp_code,
137            "0004"
138        );
139    }
140}