pub(crate) fn validate_ogrn(code: &str) -> bool {
if code.len() != 13 || !code.chars().all(|c| c.is_ascii_digit()) {
return false;
}
let number: u64 = match code[..12].parse() {
Ok(n) => n,
Err(_) => return false,
};
let check_digit = ((number % 11) % 10) as u8;
let last_digit = code.as_bytes()[12] - b'0';
check_digit == last_digit
}
pub(crate) fn validate_inn(code: &str) -> bool {
let digits: Vec<u8> = match code.chars().map(|c| c.to_digit(10).map(|d| d as u8)).collect() {
Some(d) => d,
None => return false,
};
match digits.len() {
10 => {
let coeffs = [2, 4, 10, 3, 5, 9, 4, 6, 8];
inn_check_digit(&digits[..9], &coeffs) == digits[9]
}
12 => {
let coeffs1 = [7, 2, 4, 10, 3, 5, 9, 4, 6, 8, 0];
let coeffs2 = [3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8];
let n11 = inn_check_digit(&digits[..10], &coeffs1);
let n12 = inn_check_digit(&digits[..11], &coeffs2);
n11 == digits[10] && n12 == digits[11]
}
_ => false,
}
}
fn inn_check_digit(digits: &[u8], coeffs: &[u8]) -> u8 {
let sum: u32 = digits.iter().zip(coeffs.iter()).map(|(&d, &c)| (d as u32) * (c as u32)).sum();
((sum % 11) % 10) as u8
}
pub(crate) fn validate_imo_mmsi(code: &str) -> bool {
validate_imo(code) || validate_mmsi(code)
}
pub(crate) fn validate_mmsi(code: &str) -> bool {
code.len() == 9 && code.chars().all(|c| c.is_ascii_digit())
}
pub(crate) fn validate_imo(imo: &str) -> bool {
if imo.len() != 10 || !imo.to_uppercase().starts_with("IMO") {
return false;
}
let digits = &imo[3..];
if !digits.chars().all(|c| c.is_ascii_digit()) {
return false;
}
let weights = [7, 6, 5, 4, 3, 2];
let mut sum = 0;
for (i, w) in weights.iter().enumerate() {
let d = digits.chars().nth(i).unwrap().to_digit(10).unwrap();
sum += d * w;
}
let check_digit = sum % 10;
let actual_check_digit = digits.chars().nth(6).unwrap().to_digit(10).unwrap();
check_digit == actual_check_digit
}
pub(crate) fn validate_bic(code: &str) -> bool {
if code.len() != 8 && code.len() != 11 {
return false;
}
let chars: Vec<char> = code.chars().collect();
if !chars[..4].iter().all(|c| c.is_ascii_alphabetic()) {
return false;
}
if !chars[4..6].iter().all(|c| c.is_ascii_alphabetic()) {
return false;
}
if !chars[6..8].iter().all(|c| c.is_ascii_alphanumeric()) {
return false;
}
if code.len() == 11 && !chars[8..11].iter().all(|c| c.is_ascii_alphanumeric()) {
return false;
}
true
}
pub(crate) fn validate_isin(code: &str) -> bool {
if code.len() != 12 {
return false;
}
let chars: Vec<char> = code.chars().collect();
if !chars[..2].iter().all(|c| c.is_ascii_alphabetic()) {
return false;
}
if !chars[2..11].iter().all(|c| c.is_ascii_alphanumeric()) {
return false;
}
if !chars[11].is_ascii_digit() {
return false;
}
let code = format!("{}{}{}", chars[0] as u8 - 55, chars[1] as u8 - 55, &code[2..]);
luhn::valid(&code)
}
#[cfg(test)]
mod tests {
#[test]
fn validate_ogrn() {
assert!(super::validate_ogrn("1027700132195"));
assert!(!super::validate_ogrn("1027700132194"));
assert!(!super::validate_ogrn("123456789012"));
assert!(!super::validate_ogrn("abcdefghijklm"));
}
#[test]
fn validate_inn() {
assert!(super::validate_inn("7707083893"));
assert!(super::validate_inn("500100732259"));
assert!(!super::validate_inn("7707083894"));
assert!(!super::validate_inn("abcdefghij"));
}
#[test]
fn validate_mmo_mmsi() {
assert!(super::validate_imo_mmsi("366123456"));
assert!(!super::validate_imo_mmsi("12345678"));
assert!(!super::validate_imo_mmsi("1234567890"));
assert!(!super::validate_imo_mmsi("12345abc9"));
}
#[test]
fn validate_bic() {
assert!(super::validate_bic("DEUTDEFF"));
assert!(super::validate_bic("DEUTDEFF500"));
assert!(!super::validate_bic("DEUTDEFF50"));
assert!(!super::validate_bic("DEUT12FF500"));
assert!(!super::validate_bic("DEUTDE@F500"));
}
#[test]
fn validate_isin() {
assert!(super::validate_isin("US0378331005"));
assert!(super::validate_isin("GB0002634946"));
assert!(!super::validate_isin("US0378331006"));
assert!(!super::validate_isin("US03783310A5"));
assert!(!super::validate_isin("U0378331005"));
}
}