use crate::constants::mrz_field_name::{
BIRTHDATE_FIELD, COUNTRY_CODE_FIELD, DOCUMENT_NUMBER_FIELD, DOCUMENT_TYPE_FIELD, EXPIRY_DATE_FIELD,
FINAL_CHECK_DIGIT_FIELD, NAME_FIELD, NATIONALITY_FIELD, OPTIONAL_DATA_1_FIELD, SEX_FIELD,
};
use crate::constants::mrz_utils::{ISSUING_COUNTRY_CODES, TYPE3_NUMBER_OF_CHARACTERS_PER_LINE};
use crate::parser::field_formatter::FieldFormatter;
use crate::parser::field_formatter::FieldType::{
Birthdate, CountryCode, DocumentNumber, DocumentType, ExpiryDate, Hash, Names, Nationality, PersonalNumber, Sex,
};
use crate::parser::mrz_field::MrzField;
use crate::parser::parser::{IMRZParser, MRZResult};
use crate::utils::utils::calculate_check_digits;
use std::collections::HashMap;
#[derive(Default, Debug, Clone)]
pub struct TD3 {}
impl TD3 {
pub fn new() -> Self {
TD3 {}
}
fn validate_all_check_digits(
&self, document_number: &MrzField, birthdate: &MrzField, expiry_date: &MrzField, personal_number: &MrzField,
final_check_digit: Option<&MrzField>,
) -> Result<bool, &'static str> {
if let Some(final_check) = final_check_digit {
let composite_str = format!(
"{}{}{}{}{}{}{}{}",
document_number.raw_value,
document_number.check_digit,
birthdate.raw_value,
birthdate.check_digit,
expiry_date.raw_value,
expiry_date.check_digit,
personal_number.raw_value,
personal_number.check_digit,
);
let calculated_check_digit = calculate_check_digits(&composite_str)?;
Ok(document_number.is_valid
&& birthdate.is_valid
&& expiry_date.is_valid
&& personal_number.is_valid
&& calculated_check_digit == final_check.raw_value)
} else {
Ok(document_number.is_valid && birthdate.is_valid && expiry_date.is_valid)
}
}
}
impl IMRZParser for TD3 {
fn parse(&self, input: &Vec<String>) -> Result<MRZResult, &'static str> {
if input.len() != 2 {
return Err("invalid mrz length");
}
for line in input {
if line.len() != TYPE3_NUMBER_OF_CHARACTERS_PER_LINE {
return Err("invalid mrz type 2 line length");
}
}
let mut is_visa = false;
let first_line = &input[0];
let second_line = &input[1];
let formatter = FieldFormatter::new(true);
if first_line.chars().nth(0) == Some('V') {
is_visa = true;
}
let document_type = formatter.field(DocumentType, first_line, 0, 2, false)?;
let country_code = formatter.field(CountryCode, first_line, 2, 3, false)?;
let name = formatter.field(Names, first_line, 5, 39, false)?;
let document_number = formatter.field(DocumentNumber, second_line, 0, 9, true)?;
let nationality = formatter.field(Nationality, second_line, 10, 3, false)?;
let birthdate = formatter.field(Birthdate, second_line, 13, 6, true)?;
let sex = formatter.field(Sex, second_line, 20, 1, false)?;
let expiry_date = formatter.field(ExpiryDate, second_line, 21, 6, true)?;
let mut optional_data: MrzField = MrzField {
value: "".to_string(),
raw_value: "".to_string(),
check_digit: "".to_string(),
is_valid: true,
};
let mut final_check_digit: MrzField = MrzField {
value: "".to_string(),
raw_value: "".to_string(),
check_digit: "".to_string(),
is_valid: true,
};
let mut is_valid: bool = false;
if is_visa {
optional_data = formatter.field(PersonalNumber, second_line, 28, 16, false)?;
is_valid =
self.validate_all_check_digits(&document_number, &birthdate, &expiry_date, &optional_data, None)?;
} else {
optional_data = formatter.field(PersonalNumber, second_line, 28, 14, true)?;
final_check_digit = formatter.field(Hash, second_line, 43, 1, false)?;
is_valid = self.validate_all_check_digits(
&document_number,
&birthdate,
&expiry_date,
&optional_data,
Some(&final_check_digit),
)?;
}
let mut parsed_result: HashMap<String, MrzField> = HashMap::new();
parsed_result.insert(NAME_FIELD.to_string(), name);
parsed_result.insert(DOCUMENT_TYPE_FIELD.to_string(), document_type);
parsed_result.insert(COUNTRY_CODE_FIELD.to_string(), country_code);
parsed_result.insert(DOCUMENT_NUMBER_FIELD.to_string(), document_number);
parsed_result.insert(NATIONALITY_FIELD.to_string(), nationality);
parsed_result.insert(BIRTHDATE_FIELD.to_string(), birthdate);
parsed_result.insert(SEX_FIELD.to_string(), sex);
parsed_result.insert(EXPIRY_DATE_FIELD.to_string(), expiry_date);
parsed_result.insert(OPTIONAL_DATA_1_FIELD.to_string(), optional_data);
parsed_result.insert(FINAL_CHECK_DIGIT_FIELD.to_string(), final_check_digit);
let issuing_state = ISSUING_COUNTRY_CODES
.get(&parsed_result[COUNTRY_CODE_FIELD].value as &str)
.unwrap_or(&"Unknown")
.to_string();
Ok(MRZResult {
is_visa,
is_valid,
fields: parsed_result,
issuing_state,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_td3() {
let mrz_string: Vec<String> = vec![
"P<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<".to_string(),
"L898902C36UTO7408122F1204159ZE184226B<<<<<10".to_string(),
];
let mut td3 = TD3::new();
let result = td3.parse(&mrz_string).unwrap();
assert_eq!(result.is_visa, false);
assert_eq!(result.is_valid, true);
println!("{:?}", result)
}
#[test]
fn test_parse_td3_visa() {
let mrz_string: Vec<String> = vec![
"V<UTOERIKSSON<<ANNA<MARIA<<<<<<<<<<<<<<<<<<<".to_string(),
"L8988901C4XXX4009078F96121096ZE184226B<<<<<<".to_string(),
];
let mut td3 = TD3::new();
let result = td3.parse(&mrz_string).unwrap();
assert_eq!(result.is_visa, true);
assert_eq!(result.is_valid, true);
println!("{:?}", result)
}
}