Skip to main content

mrz_parser/
result.rs

1use chrono::NaiveDate;
2use std::fmt;
3
4use crate::Sex;
5
6/// Parsed contents of an MRZ (Machine Readable Zone) — the values typically
7/// extracted from passport / ID MRZ lines.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub struct MRZResult {
10    pub document_type: String,
11    pub country_code: String,
12    pub surnames: String,
13    pub given_names: String,
14    pub document_number: String,
15    pub nationality_country_code: String,
16    pub birth_date: NaiveDate,
17    pub sex: Sex,
18    pub expiry_date: NaiveDate,
19    pub personal_number: String,
20    pub personal_number2: Option<String>,
21}
22
23impl MRZResult {
24    /// Create a new `MRZResult`.
25    ///
26    /// `birth_date` and `expiry_date` are `NaiveDate` — date-only values are
27    /// all MRZ carries for these fields.
28    pub fn new(
29        document_type: impl Into<String>,
30        country_code: impl Into<String>,
31        surnames: impl Into<String>,
32        given_names: impl Into<String>,
33        document_number: impl Into<String>,
34        nationality_country_code: impl Into<String>,
35        birth_date: NaiveDate,
36        sex: Sex,
37        expiry_date: NaiveDate,
38        personal_number: impl Into<String>,
39        personal_number2: Option<impl Into<String>>,
40    ) -> Self {
41        MRZResult {
42            document_type: document_type.into(),
43            country_code: country_code.into(),
44            surnames: surnames.into(),
45            given_names: given_names.into(),
46            document_number: document_number.into(),
47            nationality_country_code: nationality_country_code.into(),
48            birth_date,
49            sex,
50            expiry_date,
51            personal_number: personal_number.into(),
52            personal_number2: personal_number2.map(|s| s.into()),
53        }
54    }
55}
56
57impl fmt::Display for MRZResult {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(
60            f,
61            "MRZResult {{ document_type: {}, country_code: {}, surnames: {}, given_names: {}, document_number: {}, nationality: {}, birth_date: {}, sex: {:?}, expiry_date: {}, personal_number: {}, personal_number2: {:?} }}",
62            self.document_type,
63            self.country_code,
64            self.surnames,
65            self.given_names,
66            self.document_number,
67            self.nationality_country_code,
68            self.birth_date,
69            self.sex,
70            self.expiry_date,
71            self.personal_number,
72            self.personal_number2,
73        )
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::Sex;
81    use chrono::NaiveDate;
82
83    #[test]
84    fn create_and_compare_results() {
85        let birth = NaiveDate::from_ymd_opt(1990, 5, 20).expect("valid date");
86        let expiry = NaiveDate::from_ymd_opt(2030, 5, 20).expect("valid date");
87
88        let r1 = MRZResult::new(
89            "P",
90            "UTO",
91            "DOE",
92            "JOHN",
93            "123456789",
94            "UTO",
95            birth,
96            Sex::Male,
97            expiry,
98            "ABC123<<<",
99            Some("SECOND".to_string()),
100        );
101
102        let r2 = MRZResult {
103            document_type: "P".into(),
104            country_code: "UTO".into(),
105            surnames: "DOE".into(),
106            given_names: "JOHN".into(),
107            document_number: "123456789".into(),
108            nationality_country_code: "UTO".into(),
109            birth_date: birth,
110            sex: Sex::Male,
111            expiry_date: expiry,
112            personal_number: "ABC123<<<".into(),
113            personal_number2: Some("SECOND".into()),
114        };
115
116        assert_eq!(r1, r2);
117        assert!(r1.personal_number2.is_some());
118    }
119
120    #[test]
121    fn display_contains_key_fields() {
122        let birth = NaiveDate::from_ymd_opt(1985, 1, 1).unwrap();
123        let expiry = NaiveDate::from_ymd_opt(2025, 1, 1).unwrap();
124
125        let r = MRZResult::new(
126            "ID",
127            "GBR",
128            "SMITH",
129            "JANE",
130            "X9876543",
131            "GBR",
132            birth,
133            Sex::Female,
134            expiry,
135            "ZZZ000<<<",
136            None::<String>,
137        );
138
139        let s = format!("{}", r);
140        assert!(s.contains("SMITH"));
141        assert!(s.contains("JANE"));
142        assert!(s.contains("GBR"));
143    }
144}