1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
use std::convert::TryFrom;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::ops::Deref;

#[derive(PartialOrd, PartialEq, Debug)]
pub struct NationalId(String);

impl TryFrom<&str> for NationalId {
    type Error = NationalIdError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        let value = format!("{:0>10}", value.trim());
        let digits: Vec<u32> = value.chars().filter_map(|c| c.to_digit(10)).collect();

        if digits.len() != 10 {
            return Err(NationalIdError);
        }

        let sum: u32 = (0..9).map(|i| { digits[i] * (10 - i) as u32 }).sum();
        if sum == 0 { return Err(NationalIdError); }
        let control_digit = *digits.last().unwrap();

        let rem = sum % 11;
        if (rem < 2 && rem == control_digit) || (rem >= 2 && rem + control_digit == 11) {
            return Ok(NationalId(value));
        }
        Err(NationalIdError)
    }
}

impl Deref for NationalId {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

#[derive(Debug, Clone, PartialOrd, PartialEq)]
pub struct NationalIdError;

impl Error for NationalIdError {}

impl Display for NationalIdError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "invalid iranian national id number")
    }
}

#[cfg(test)]
mod tests {
    use crate::{NationalId, NationalIdError};
    use std::convert::{TryFrom, TryInto};

    #[test]
    fn test_length_of_code_should_pad_to_10_digit() {
        assert_eq!(NationalId::try_from("0451726707"), Ok(NationalId(String::from("0451726707"))));
    }

    #[test]
    fn test_invalid_input() {
        assert!(NationalId::try_from("").is_err());
        assert!(NationalId::try_from("123").is_err());
        assert!(NationalId::try_from("123456ab").is_err());
        assert!(NationalId::try_from("12345678ab").is_err());
        assert!(NationalId::try_from("a814659438").is_err());
    }

    #[test]
    fn test_should_deref_to_string() {
        let code = "0814659438";
        let result: Result<NationalId, _> = code.try_into();
        assert_eq!(code, *result.unwrap())
    }

    #[test]
    fn test_validate_national_id() {
        let ni: Result<NationalId, NationalIdError> = "0040010007".try_into();
        assert!(ni.is_ok());

        let ni: Result<NationalId, NationalIdError> = "0814659438".try_into();
        assert!(ni.is_ok());
    }
}