#[macro_use]
extern crate lazy_static;
use std::collections::HashSet;
use std::fmt;
use std::time::SystemTime;
use crate::VINError::{ChecksumError, IncorrectLength, InvalidCharacters};
use crate::dicts::{get_region, get_country, get_manufacturer};
mod dicts;
#[derive(Debug, Copy, Clone)]
pub struct ChecksumErrorInfo {
pub expected: char,
pub received: char,
}
#[derive(Debug)]
pub enum VINError {
IncorrectLength,
InvalidCharacters(HashSet<char>),
ChecksumError(ChecksumErrorInfo),
}
impl fmt::Display for VINError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
VINError::IncorrectLength =>
write!(f, "Incorrect length of given string, 17 chars expected."),
VINError::InvalidCharacters(chars) =>
write!(f, "Invalid characters received in given string: {:?}.", chars),
VINError::ChecksumError(err) =>
write!(f, "Invalid checksum symbol on 9th place, {} expected, {} received.", err.expected, err.received),
}
}
}
#[derive(Debug, Clone)]
pub struct VIN {
pub vin: String,
pub country: String,
pub manufacturer: String,
pub region: String,
pub valid_checksum: Result<(), ChecksumErrorInfo>,
}
impl VIN {
pub fn wmi(&self) -> &str { &self.vin[..3] }
pub fn vds(&self) -> &str { &self.vin[3..9] }
pub fn vis(&self) -> &str { &self.vin[9..] }
pub fn small_manufacturer(&self) -> bool { &self.wmi()[2..] == "9" }
pub fn region_code(&self) -> &str { &self.wmi()[..1] }
pub fn country_code(&self) -> &str { &self.wmi()[1..] }
pub fn years(&self) -> Vec<u32> {
let letters = "ABCDEFGHJKLMNPRSTVWXY123456789";
let year_letter = &self.vis().chars().nth(0).unwrap();
let mut year: u32 = 1979;
let cur_year = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs_f64();
let cur_year = cur_year / 3600.0 / 24.0 / 365.25 + 1970.0; let cur_year = (cur_year.round() + 2.0) as u32;
let mut result = vec![];
for letter in letters.chars().cycle() {
year += 1;
if letter == *year_letter {
result.push(year);
}
if year == cur_year { break; }
}
result
}
}
pub fn check_validity(vin: &str) -> Result<(), VINError> {
let vin = vin.to_uppercase();
if vin.chars().count() != 17 {
return Err(IncorrectLength);
}
let used_chars: HashSet<char> = vin.chars().collect();
let odd_chars: HashSet<char> = used_chars.difference(&dicts::ALLOWED_CHARS).cloned().collect();
if odd_chars.len() > 0 {
return Err(InvalidCharacters(odd_chars));
}
Ok(())
}
pub fn verify_checksum(vin: &str) -> Result<(), VINError> {
let vin = vin.to_uppercase();
check_validity(&vin)?;
let checksum: u32 = vin
.chars()
.map(|x| dicts::VALUE_MAP.get(&x).unwrap())
.zip(dicts::WEIGHTS.iter())
.map(|(l, r)| l * r)
.sum();
let checknumber = match checksum % 11 {
10 => 'X',
i => std::char::from_digit(i, 10).unwrap()
};
let pr_number = vin.chars().nth(8).unwrap();
if pr_number == checknumber {
Ok(())
} else {
Err(ChecksumError(ChecksumErrorInfo {
expected: checknumber,
received: pr_number,
}))
}
}
pub fn get_info(vin: &str) -> Result<VIN, VINError> {
let vin = vin.to_uppercase();
check_validity(&vin)?;
return Ok(VIN {
vin: vin.clone(),
country: get_country(&vin[..2]),
manufacturer: get_manufacturer(&vin[..3]),
region: get_region(&vin[..1]),
valid_checksum: match verify_checksum(&vin) {
Ok(()) => Ok(()),
Err(VINError::ChecksumError(x)) => Err(x),
_ => Ok(()) }
})
}