#![warn(
missing_debug_implementations,
missing_docs,
clippy::all,
clippy::todo,
future_incompatible,
nonstandard_style
)]
#![forbid(unsafe_code)]
#[derive(Debug)]
pub enum Error {
InvalidLength,
InvalidCharacter(char),
InvalidLowercaseCharacter,
InvalidCheckDigit {
expected: char,
found: char,
},
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::InvalidLength => {
write!(
f,
"VIN must be exactly {} characters",
crate::MAX_LENGTH
)
}
Error::InvalidCharacter(c) => {
write!(f, "VIN contains invalid character: '{}' (valid characters are alphanumeric excluding I, O, and Q)", c)
}
Error::InvalidLowercaseCharacter => {
write!(f, "VIN must only contain uppercase characters")
}
Error::InvalidCheckDigit { expected, found } => {
write!(
f,
"VIN has invalid check digit: expected '{}', found '{}'",
expected, found
)
}
}
}
}
impl std::error::Error for Error {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WmiInfo {
pub manufacturer: &'static str,
pub country: &'static str,
pub region: &'static str,
}
#[cfg(not(feature = "phf"))]
use std::collections::HashMap;
#[cfg(not(feature = "phf"))]
use std::sync::LazyLock;
#[cfg(not(feature = "phf"))]
static REGIONS: LazyLock<HashMap<char, &'static str>> = LazyLock::new(|| {
let mut r = HashMap::new();
r.insert('1', "North America");
r.insert('2', "North America");
r.insert('3', "North America");
r.insert('4', "North America");
r.insert('5', "North America");
r.insert('J', "Asia (Japan)");
r.insert('K', "Asia (Korea)");
r.insert('L', "Asia (China)");
r.insert('S', "Europe (UK, Germany)");
r.insert('T', "Europe (Switzerland, Czech Republic)");
r.insert('V', "Europe (Austria, France)");
r.insert('W', "Europe (Germany)");
r.insert('Y', "Europe (Sweden, Finland)");
r.insert('Z', "Europe (Italy)");
r
});
#[cfg(not(feature = "phf"))]
#[rustfmt::skip]
static WMI_DATA: LazyLock<HashMap<&'static [u8], WmiInfo>> = LazyLock::new(|| {
let mut m = HashMap::new();
m.insert(b"1G".as_ref(), WmiInfo {
manufacturer: "General Motors",
country: "United States",
region: "North America",
});
m.insert(b"1H".as_ref(), WmiInfo {
manufacturer: "Honda".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"1J".as_ref(), WmiInfo {
manufacturer: "Jeep".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"1N".as_ref(), WmiInfo {
manufacturer: "Nissan".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"1V".as_ref(), WmiInfo {
manufacturer: "Volkswagen".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"1Y".as_ref(), WmiInfo {
manufacturer: "General Motors".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"2G".as_ref(), WmiInfo {
manufacturer: "General Motors Canada".as_ref(),
country: "Canada".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"2T".as_ref(), WmiInfo {
manufacturer: "Toyota".as_ref(),
country: "Canada".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"3G".as_ref(), WmiInfo {
manufacturer: "General Motors Mexico".as_ref(),
country: "Mexico".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"3H".as_ref(), WmiInfo {
manufacturer: "Honda Mexico".as_ref(),
country: "Mexico".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"3N".as_ref(), WmiInfo {
manufacturer: "Nissan Mexico".as_ref(),
country: "Mexico".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"3V".as_ref(), WmiInfo {
manufacturer: "Volkswagen Mexico".as_ref(),
country: "Mexico".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"4F".as_ref(), WmiInfo {
manufacturer: "Mazda".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"4S".as_ref(), WmiInfo {
manufacturer: "Subaru-Isuzu Automotive".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"4T".as_ref(), WmiInfo {
manufacturer: "Toyota".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"5G".as_ref(), WmiInfo {
manufacturer: "General Motors".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"5GZ".as_ref(), WmiInfo {
manufacturer: "General Motors".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"5L".as_ref(), WmiInfo {
manufacturer: "Lincoln".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"5N".as_ref(), WmiInfo {
manufacturer: "Hyundai".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"5T".as_ref(), WmiInfo {
manufacturer: "Toyota".as_ref(),
country: "United States".as_ref(),
region: "North America".as_ref(),
});
m.insert(b"WA".as_ref(), WmiInfo {
manufacturer: "Audi".as_ref(),
country: "Germany".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"WB".as_ref(), WmiInfo {
manufacturer: "BMW".as_ref(),
country: "Germany".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"WD".as_ref(), WmiInfo {
manufacturer: "Mercedes-Benz".as_ref(),
country: "Germany".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"WF".as_ref(), WmiInfo {
manufacturer: "Ford Germany".as_ref(),
country: "Germany".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"WP".as_ref(), WmiInfo {
manufacturer: "Porsche".as_ref(),
country: "Germany".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"WV".as_ref(), WmiInfo {
manufacturer: "Volkswagen".as_ref(),
country: "Germany".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"WVW".as_ref(), WmiInfo {
manufacturer: "Volkswagen".as_ref(),
country: "Germany".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"YV".as_ref(), WmiInfo {
manufacturer: "Volvo".as_ref(),
country: "Sweden".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"ZFA".as_ref(), WmiInfo {
manufacturer: "Fiat".as_ref(),
country: "Italy".as_ref(),
region: "Europe".as_ref(),
});
m.insert(b"JH".as_ref(), WmiInfo {
manufacturer: "Honda".as_ref(),
country: "Japan".as_ref(),
region: "Asia".as_ref(),
});
m.insert(b"JM".as_ref(), WmiInfo {
manufacturer: "Mazda".as_ref(),
country: "Japan".as_ref(),
region: "Asia".as_ref(),
});
m.insert(b"JN".as_ref(), WmiInfo {
manufacturer: "Nissan".as_ref(),
country: "Japan".as_ref(),
region: "Asia".as_ref(),
});
m.insert(b"JS".as_ref(), WmiInfo {
manufacturer: "Suzuki".as_ref(),
country: "Japan".as_ref(),
region: "Asia".as_ref(),
});
m.insert(b"JT".as_ref(), WmiInfo {
manufacturer: "Toyota".as_ref(),
country: "Japan".as_ref(),
region: "Asia".as_ref(),
});
m.insert(b"KL".as_ref(), WmiInfo {
manufacturer: "Daewoo".as_ref(),
country: "South Korea".as_ref(),
region: "Asia".as_ref(),
});
m.insert(b"KM".as_ref(), WmiInfo {
manufacturer: "Hyundai".as_ref(),
country: "South Korea".as_ref(),
region: "Asia".as_ref(),
});
m.insert(b"KN".as_ref(), WmiInfo {
manufacturer: "Kia".as_ref(),
country: "South Korea".as_ref(),
region: "Asia".as_ref(),
});
m
});
#[cfg(feature = "phf")]
static WMI_DATA: phf::Map<&'static [u8], WmiInfo> = phf::phf_map! {
b"1G" => WmiInfo {
manufacturer: "General Motors",
country: "United States",
region: "North America",
},
b"1H" => WmiInfo {
manufacturer: "Honda",
country: "United States",
region: "North America",
},
b"1J" => WmiInfo {
manufacturer: "Jeep",
country: "United States",
region: "North America",
},
b"1N" => WmiInfo {
manufacturer: "Nissan",
country: "United States",
region: "North America",
},
b"1V" => WmiInfo {
manufacturer: "Volkswagen",
country: "United States",
region: "North America",
},
b"1Y" => WmiInfo {
manufacturer: "General Motors",
country: "United States",
region: "North America",
},
b"2G" => WmiInfo {
manufacturer: "General Motors Canada",
country: "Canada",
region: "North America",
},
b"2T" => WmiInfo {
manufacturer: "Toyota",
country: "Canada",
region: "North America",
},
b"3G" => WmiInfo {
manufacturer: "General Motors Mexico",
country: "Mexico",
region: "North America",
},
b"3H" => WmiInfo {
manufacturer: "Honda Mexico",
country: "Mexico",
region: "North America",
},
b"3N" => WmiInfo {
manufacturer: "Nissan Mexico",
country: "Mexico",
region: "North America",
},
b"3V" => WmiInfo {
manufacturer: "Volkswagen Mexico",
country: "Mexico",
region: "North America",
},
b"4F" => WmiInfo {
manufacturer: "Mazda",
country: "United States",
region: "North America",
},
b"4S" => WmiInfo {
manufacturer: "Subaru-Isuzu Automotive",
country: "United States",
region: "North America",
},
b"4T" => WmiInfo {
manufacturer: "Toyota",
country: "United States",
region: "North America",
},
b"5G" => WmiInfo {
manufacturer: "General Motors",
country: "United States",
region: "North America",
},
b"5GZ" => WmiInfo {
manufacturer: "General Motors",
country: "United States",
region: "North America",
},
b"5L" => WmiInfo {
manufacturer: "Lincoln",
country: "United States",
region: "North America",
},
b"5N" => WmiInfo {
manufacturer: "Hyundai",
country: "United States",
region: "North America",
},
b"5T" => WmiInfo {
manufacturer: "Toyota",
country: "United States",
region: "North America",
},
b"WA" => WmiInfo {
manufacturer: "Audi",
country: "Germany",
region: "Europe",
},
b"W" => WmiInfo {
manufacturer: "BMW",
country: "Germany",
region: "Europe",
},
b"WD" => WmiInfo {
manufacturer: "Mercedes-Benz",
country: "Germany",
region: "Europe",
},
b"WF" => WmiInfo {
manufacturer: "Ford Germany",
country: "Germany",
region: "Europe",
},
b"WP" => WmiInfo {
manufacturer: "Porsche",
country: "Germany",
region: "Europe",
},
b"WV" => WmiInfo {
manufacturer: "Volkswagen",
country: "Germany",
region: "Europe",
},
b"WVW" => WmiInfo {
manufacturer: "Volkswagen",
country: "Germany",
region: "Europe",
},
b"YV" => WmiInfo {
manufacturer: "Volvo",
country: "Sweden",
region: "Europe",
},
b"ZFA" => WmiInfo {
manufacturer: "Fiat",
country: "Italy",
region: "Europe",
},
b"JH" => WmiInfo {
manufacturer: "Honda",
country: "Japan",
region: "Asia",
},
b"JM" => WmiInfo {
manufacturer: "Mazda",
country: "Japan",
region: "Asia",
},
b"JN" => WmiInfo {
manufacturer: "Nissan",
country: "Japan",
region: "Asia",
},
b"JS" => WmiInfo {
manufacturer: "Suzuki",
country: "Japan",
region: "Asia",
},
b"JT" => WmiInfo {
manufacturer: "Toyota",
country: "Japan",
region: "Asia",
},
b"KL" => WmiInfo {
manufacturer: "Daewoo",
country: "South Korea",
region: "Asia",
},
b"KM" => WmiInfo {
manufacturer: "Hyundai",
country: "South Korea",
region: "Asia",
},
b"KN" => WmiInfo {
manufacturer: "Kia",
country: "South Korea",
region: "Asia",
},
};
#[cfg(feature = "phf")]
static REGIONS: phf::Map<char, &'static str> = phf::phf_map! {
'1' => "North America",
'2' => "North America",
'3' => "North America",
'4' => "North America",
'5' => "North America",
'J' => "Asia (Japan)",
'K' => "Asia (Korea)",
'L' => "Asia (China)",
'S' => "Europe (UK, Germany)",
'T' => "Europe (Switzerland, Czech Republic)",
'V' => "Europe (Austria, France)",
'W' => "Europe (Germany)",
'Y' => "Europe (Sweden, Finland)",
'Z' => "Europe (Italy)",
};
impl WmiInfo {
pub fn lookup(wmi: &[u8]) -> Option<&'static WmiInfo> {
if let Some(info) = WMI_DATA.get(wmi) {
return Some(info);
}
if wmi.len() >= 2 {
let prefix = &wmi[0..2];
if let Some(info) = WMI_DATA.get(prefix) {
return Some(info);
}
}
None
}
pub fn region_from_char(wmi_char: char) -> Option<&'static str> {
REGIONS.get(&wmi_char).copied()
}
}
const WEIGHTS: [u32; 17] = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2];
const CURRENT_YEAR: u16 = 2025;
pub const MAX_LENGTH: usize = 17;
static VIN_CHAR_TABLE: [u8; 256] = {
let mut table = [0xFF; 256];
table[b'0' as usize] = 0;
table[b'1' as usize] = 1;
table[b'2' as usize] = 2;
table[b'3' as usize] = 3;
table[b'4' as usize] = 4;
table[b'5' as usize] = 5;
table[b'6' as usize] = 6;
table[b'7' as usize] = 7;
table[b'8' as usize] = 8;
table[b'9' as usize] = 9;
table[b'A' as usize] = 1;
table[b'B' as usize] = 2;
table[b'C' as usize] = 3;
table[b'D' as usize] = 4;
table[b'E' as usize] = 5;
table[b'F' as usize] = 6;
table[b'G' as usize] = 7;
table[b'H' as usize] = 8;
table[b'J' as usize] = 1;
table[b'K' as usize] = 2;
table[b'L' as usize] = 3;
table[b'M' as usize] = 4;
table[b'N' as usize] = 5;
table[b'P' as usize] = 7;
table[b'R' as usize] = 9;
table[b'S' as usize] = 2;
table[b'T' as usize] = 3;
table[b'U' as usize] = 4;
table[b'V' as usize] = 5;
table[b'W' as usize] = 6;
table[b'X' as usize] = 7;
table[b'Y' as usize] = 8;
table[b'Z' as usize] = 9;
table
};
#[inline]
fn check_and_transliterate(byte: u8) -> Result<u8, Error> {
let value = VIN_CHAR_TABLE[byte as usize];
if value == 0xFF {
Err(Error::InvalidCharacter(byte as char))
} else {
Ok(value)
}
}
#[derive(Debug)]
pub struct Vin<'a> {
#[allow(dead_code)]
raw: &'a [u8],
check_digit: char,
wmi: &'a [u8],
vds: &'a [u8],
vis: &'a [u8],
}
impl<'a> Vin<'a> {
#[must_use]
pub fn year_of_manufacture(&self) -> Option<u16> {
self.year_of_manufacture_with_base(CURRENT_YEAR)
}
#[must_use]
pub fn year_of_manufacture_with_base(&self, base_year: u16) -> Option<u16> {
let year_char = self.vis[0] as char;
#[rustfmt::skip]
static YEAR_MAP: &[(char, u16)] = &[
('A', 1980), ('B', 1981), ('C', 1982), ('D', 1983), ('E', 1984),
('F', 1985), ('G', 1986), ('H', 1987), ('J', 1988), ('K', 1989),
('L', 1990), ('M', 1991), ('N', 1992), ('P', 1993), ('R', 1994),
('S', 1995), ('T', 1996), ('V', 1997), ('W', 1998), ('X', 1999),
('Y', 2000), ('1', 2001), ('2', 2002), ('3', 2003), ('4', 2004),
('5', 2005), ('6', 2006), ('7', 2007), ('8', 2008), ('9', 2009),
('A', 2010), ('B', 2011), ('C', 2012), ('D', 2013), ('E', 2014),
('F', 2015), ('G', 2016), ('H', 2017), ('J', 2018), ('K', 2019),
('L', 2020), ('M', 2021), ('N', 2022), ('P', 2023), ('R', 2024),
('S', 2025), ('T', 2026), ('V', 2027), ('W', 2028), ('X', 2029),
('Y', 2030), ('1', 2031), ('2', 2032), ('3', 2033), ('4', 2034),
('5', 2035), ('6', 2036), ('7', 2037), ('8', 2038), ('9', 2039),
];
let mut candidates: Vec<u16> = YEAR_MAP
.iter()
.filter(|(c, _)| *c == year_char)
.map(|(_, year)| *year)
.collect();
candidates.retain(|&y| y <= base_year + 1);
candidates.into_iter().max()
}
pub fn lookup_wmi_info(&self) -> Option<&'static crate::WmiInfo> {
crate::WmiInfo::lookup(self.wmi)
}
#[inline]
pub fn vds(&self) -> &str {
std::str::from_utf8(self.vds).unwrap()
}
#[inline]
pub fn vis(&self) -> &str {
std::str::from_utf8(self.vis).unwrap()
}
#[inline]
pub fn wmi(&self) -> &str {
std::str::from_utf8(self.wmi).unwrap()
}
#[inline]
pub fn check_digit(&self) -> &char {
&self.check_digit
}
pub fn parse(raw: &'a [u8]) -> Result<Self, Error> {
if raw.len() != MAX_LENGTH {
return Err(Error::InvalidLength);
}
let expected = raw[8] as char;
let mut sum: u32 = 0;
for i in 0..MAX_LENGTH {
let b = raw[i];
if is_lowercase_alpha(b) {
return Err(Error::InvalidLowercaseCharacter);
}
let value = match check_and_transliterate(b) {
Ok(v) => v as u32,
Err(e) => return Err(e),
};
if i != 8 {
sum += value * WEIGHTS[i];
}
}
let result = sum % 11;
let found = if result == 10 {
'X'
} else {
std::char::from_digit(result, 10).unwrap()
};
if expected != found {
return Err(Error::InvalidCheckDigit { expected, found });
}
Ok(Self {
raw,
check_digit: expected,
wmi: &raw[0..3],
vds: &raw[3..9],
vis: &raw[9..],
})
}
pub fn parse_str(input: &'a str) -> Result<Self, Error> {
Self::parse(input.as_bytes())
}
pub fn parse_trusted(raw: &'a [u8]) -> Result<Self, Error> {
let found = raw[8] as char;
let mut sum: u32 = 0;
for i in 0..MAX_LENGTH {
if i != 8 {
let value = VIN_CHAR_TABLE[raw[i] as usize] as u32;
sum += value * WEIGHTS[i];
}
}
let result = sum % 11;
let expected = if result == 10 {
'X'
} else {
std::char::from_digit(result, 10).unwrap()
};
if expected != found {
return Err(Error::InvalidCheckDigit { expected, found });
}
Ok(Self {
raw,
check_digit: expected,
wmi: &raw[0..3],
vds: &raw[3..9],
vis: &raw[9..],
})
}
#[cfg(not(feature = "simd"))]
pub fn parse_batch(vins: &[&'a &str]) -> Vec<Result<Vin<'a>, Error>> {
vins.iter().map(|&vin| Self::parse_str(vin)).collect()
}
#[cfg(feature = "simd")]
pub fn parse_batch(vins: &[&'a [u8]]) -> Vec<Result<Vin<'a>, Error>> {
use wide::{u32x8, u8x16};
vins.iter()
.map(|&vin_bytes| {
if vin_bytes.len() != MAX_LENGTH {
return Err(Error::InvalidLength);
}
if is_any_lowercase_simd(vin_bytes) {
return Err(Error::InvalidLowercaseCharacter);
}
for &b in vin_bytes {
if VIN_CHAR_TABLE[b as usize] == 0xFF {
return Err(Error::InvalidCharacter(b as char));
}
}
let found = vin_bytes[8] as char;
let mut sum: u32 = 0;
{
let values = u8x16::from([
VIN_CHAR_TABLE[vin_bytes[0] as usize],
VIN_CHAR_TABLE[vin_bytes[1] as usize],
VIN_CHAR_TABLE[vin_bytes[2] as usize],
VIN_CHAR_TABLE[vin_bytes[3] as usize],
VIN_CHAR_TABLE[vin_bytes[4] as usize],
VIN_CHAR_TABLE[vin_bytes[5] as usize],
VIN_CHAR_TABLE[vin_bytes[6] as usize],
VIN_CHAR_TABLE[vin_bytes[7] as usize],
0,
0,
0,
0,
0,
0,
0,
0, ]);
let weights = u32x8::from([
WEIGHTS[0], WEIGHTS[1], WEIGHTS[2], WEIGHTS[3],
WEIGHTS[4], WEIGHTS[5], WEIGHTS[6], WEIGHTS[7],
]);
let values_u32 = u32x8::from([
values.as_array_ref()[0] as u32,
values.as_array_ref()[1] as u32,
values.as_array_ref()[2] as u32,
values.as_array_ref()[3] as u32,
values.as_array_ref()[4] as u32,
values.as_array_ref()[5] as u32,
values.as_array_ref()[6] as u32,
values.as_array_ref()[7] as u32,
]);
let products = values_u32 * weights;
sum += products.as_array_ref().iter().sum::<u32>();
}
{
let values = u8x16::from([
VIN_CHAR_TABLE[vin_bytes[9] as usize],
VIN_CHAR_TABLE[vin_bytes[10] as usize],
VIN_CHAR_TABLE[vin_bytes[11] as usize],
VIN_CHAR_TABLE[vin_bytes[12] as usize],
VIN_CHAR_TABLE[vin_bytes[13] as usize],
VIN_CHAR_TABLE[vin_bytes[14] as usize],
VIN_CHAR_TABLE[vin_bytes[15] as usize],
VIN_CHAR_TABLE[vin_bytes[16] as usize],
0,
0,
0,
0,
0,
0,
0,
0, ]);
let weights = u32x8::from([
WEIGHTS[9],
WEIGHTS[10],
WEIGHTS[11],
WEIGHTS[12],
WEIGHTS[13],
WEIGHTS[14],
WEIGHTS[15],
WEIGHTS[16],
]);
let values_u32 = u32x8::from([
values.as_array_ref()[0] as u32,
values.as_array_ref()[1] as u32,
values.as_array_ref()[2] as u32,
values.as_array_ref()[3] as u32,
values.as_array_ref()[4] as u32,
values.as_array_ref()[5] as u32,
values.as_array_ref()[6] as u32,
values.as_array_ref()[7] as u32,
]);
let products = values_u32 * weights;
sum += products.as_array_ref().iter().sum::<u32>();
}
let result = sum % 11;
let expected = if result == 10 {
'X'
} else {
std::char::from_digit(result, 10).unwrap()
};
if expected != found {
return Err(Error::InvalidCheckDigit { expected, found });
}
Ok(Vin {
raw: vin_bytes,
check_digit: expected,
wmi: &vin_bytes[0..3],
vds: &vin_bytes[3..9],
vis: &vin_bytes[9..],
})
})
.collect()
}
}
#[cfg(feature = "simd")]
#[inline]
fn is_any_lowercase_simd(bytes: &[u8]) -> bool {
use wide::{i8x16, CmpGt, CmpLt};
if bytes.len() >= 16 {
let mut chunk = [0i8; 16];
for i in 0..16 {
chunk[i] = bytes[i] as i8;
}
let v = i8x16::from(chunk);
let a = i8x16::splat(b'a' as i8);
let z = i8x16::splat(b'z' as i8);
let ge_a = v.cmp_gt(a);
let le_z = v.cmp_lt(z);
let is_lower = ge_a & le_z;
if is_lower.any() {
return true;
}
return bytes[16..].iter().any(|&b| b.is_ascii_lowercase());
}
bytes.iter().any(|&b| b.is_ascii_lowercase())
}
#[inline]
fn is_lowercase_alpha(byte: u8) -> bool {
byte.is_ascii_lowercase()
}
impl<'a> std::fmt::Display for Vin<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}-{}-{}", self.wmi(), self.vds(), self.vis())
}
}
#[cfg(feature = "serde")]
mod serde_impl {
use super::{Error, Vin};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::marker::PhantomData;
#[derive(Debug, Clone)]
pub struct OwnedVin {
pub raw: String,
pub check_digit: char,
pub wmi: String,
pub vds: String,
pub vis: String,
}
impl OwnedVin {
pub fn new(vin_str: &str) -> Result<Self, Error> {
let vin = Vin::parse_str(vin_str)?;
Ok(OwnedVin {
raw: vin_str.to_string(),
check_digit: vin.check_digit,
wmi: vin.wmi().to_string(),
vds: vin.vds().to_string(),
vis: vin.vis().to_string(),
})
}
pub fn as_vin(&self) -> Vin<'_> {
Vin {
raw: self.raw.as_bytes(),
check_digit: self.check_digit,
wmi: self.wmi.as_bytes(),
vds: self.vds.as_bytes(),
vis: self.vis.as_bytes(),
}
}
}
impl<'a> Serialize for Vin<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_bytes(self.raw)
}
}
impl Serialize for OwnedVin {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.raw)
}
}
impl<'de> Deserialize<'de> for OwnedVin {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
OwnedVin::new(&s).map_err(serde::de::Error::custom)
}
}
impl<'de> Deserialize<'de> for Vin<'de> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct VinVisitor<'de>(PhantomData<&'de str>);
impl<'de> VinVisitor<'de> {
fn new() -> Self {
VinVisitor(PhantomData)
}
}
impl<'de> serde::de::Visitor<'de> for VinVisitor<'de> {
type Value = Vin<'de>;
fn expecting(
&self,
formatter: &mut std::fmt::Formatter,
) -> std::fmt::Result {
formatter.write_str("a valid VIN string")
}
fn visit_str<E>(self, _value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Err(E::custom("Cannot deserialize Vin from non-borrowed data. Use OwnedVin instead."))
}
fn visit_borrowed_str<E>(
self,
value: &'de str,
) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Vin::parse_str(value).map_err(serde::de::Error::custom)
}
}
deserializer.deserialize_str(VinVisitor::new())
}
}
#[cfg(test)]
fn deserialize_borrowed_vin<'a>(
s: &'a str,
) -> Result<Vin<'a>, serde_json::Error> {
struct BorrowedStrDeserializer<'a>(&'a str);
impl<'de, 'a> Deserializer<'de> for BorrowedStrDeserializer<'a>
where
'a: 'de,
{
type Error = serde_json::Error;
fn deserialize_str<V>(
self,
visitor: V,
) -> Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
let s = self.0.trim();
let s =
if s.starts_with('"') && s.ends_with('"') && s.len() >= 2 {
&s[1..s.len() - 1]
} else {
s
};
visitor.visit_borrowed_str(s)
}
serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char string
bytes byte_buf option unit unit_struct newtype_struct seq tuple
tuple_struct map struct enum identifier ignored_any
}
fn deserialize_any<V>(
self,
_visitor: V,
) -> Result<V::Value, Self::Error>
where
V: serde::de::Visitor<'de>,
{
Err(serde::de::Error::custom(
"This deserializer only supports strings",
))
}
}
let deserializer = BorrowedStrDeserializer(s);
Vin::deserialize(deserializer)
}
#[cfg(all(test, feature = "serde"))]
mod serde_tests {
use serde::{Deserialize, Serialize};
use serde_json;
use super::*;
#[test]
fn serialize_vin() {
let vin_bytes = b"5GZCZ43D13S812715";
let vin = Vin::parse(vin_bytes).unwrap();
let json = serde_json::to_string(&vin).unwrap();
assert_eq!(
json,
"[53,71,90,67,90,52,51,68,49,51,83,56,49,50,55,49,53]"
);
}
#[test]
fn deserialize_owned_vin() {
let vin: OwnedVin =
serde_json::from_str("\"5GZCZ43D13S812715\"").unwrap();
assert_eq!(vin.wmi, "5GZ");
assert_eq!(vin.vds, "CZ43D1");
assert_eq!(vin.vis, "3S812715");
}
#[test]
fn borrowed_vin_deserialization() {
let input = "\"5GZCZ43D13S812715\"";
let vin = deserialize_borrowed_vin(input).unwrap();
assert_eq!(vin.wmi, b"5GZ");
}
#[test]
fn invalid_vin_deserialize() {
let result: Result<OwnedVin, _> =
serde_json::from_str("\"INVALID\"");
assert!(result.is_err());
}
#[test]
fn in_struct() {
#[derive(Serialize, Deserialize, Debug)]
struct Vehicle {
vin: OwnedVin,
make: String,
year: u16,
}
let json = r#"{
"vin": "5GZCZ43D13S812715",
"make": "General Motors",
"year": 2003
}"#;
let vehicle: Vehicle = serde_json::from_str(json).unwrap();
assert_eq!(vehicle.vin.wmi, "5GZ");
assert_eq!(vehicle.year, 2003);
let serialized = serde_json::to_string(&vehicle).unwrap();
let deserialized: Vehicle =
serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.vin.raw, "5GZCZ43D13S812715");
}
#[test]
fn owned_vin_methods() {
let vin_str = "5GZCZ43D13S812715";
let owned = OwnedVin::new(vin_str).unwrap();
let borrowed = owned.as_vin();
assert_eq!(borrowed.wmi(), "5GZ");
assert_eq!(owned.wmi, "5GZ");
assert_eq!(owned.raw, vin_str);
}
}
}
#[cfg(feature = "serde")]
pub use self::serde_impl::OwnedVin;
#[cfg(test)]
mod tests {
use std::error::Error as StdError;
use super::*;
#[test]
fn year_of_manufacture() {
let test_cases = [
(b"5GZCZ43D13S812715", Some(2003)), (b"1M8GDM9AXKP042788", Some(2019)), (b"WVWZZZ1K8AW180599", Some(2010)), (b"JN1DA31A82T216982", Some(2002)), (b"1HGCM82613A004351", Some(2003)), (b"WDBNG70J42A225892", Some(2002)), (b"SAJDA32C214F28662", Some(2001)), (b"4T1BE32K45U678742", Some(2005)), (b"1FAFP34N57W207249", Some(2007)), ];
for (vin_bytes, expected_year) in test_cases {
let vin = Vin::parse(vin_bytes).unwrap();
assert_eq!(
vin.year_of_manufacture(),
expected_year,
"Failed for VIN {:?}",
vin_bytes
);
}
}
#[test]
fn year_of_manufacture_with_base() {
let vin_bytes = b"5GZCZ43D13S812715"; let vin = Vin::parse(vin_bytes).unwrap();
assert_eq!(vin.year_of_manufacture_with_base(2025), Some(2003));
assert_eq!(vin.year_of_manufacture_with_base(1995), None);
assert_eq!(vin.year_of_manufacture_with_base(2055), Some(2033));
}
#[test]
fn years_at_cycle_boundaries() {
let test_cases = [
(b"1FTYR10U2YPB32455", 1995, None), (b"1FTYR10U2YPB32455", 2025, Some(2000)), (b"1FADP3F2XAL278143", 1995, Some(1980)), (b"1FADP3F2XAL278143", 2025, Some(2010)), (b"5NMSG73P89H303658", 1995, None), (b"5NMSG73P89H303658", 2025, Some(2009)), ];
for (vin_bytes, base_year, expected_year) in test_cases {
let vin = Vin::parse(vin_bytes).unwrap();
assert_eq!(
vin.year_of_manufacture_with_base(base_year),
expected_year,
"Failed for VIN {:?} with base year {}",
vin_bytes,
base_year
);
}
}
#[test]
fn invalid_year_character() {
struct MockVin {
vis: String,
}
impl MockVin {
fn year_char_to_year(&self) -> Option<u16> {
let year_char = self.vis.chars().next()?;
match year_char {
'I' | 'O' | 'Q' => None,
_ => Some(2000),
}
}
}
let invalid_chars = ['I', 'O', 'Q'];
for &c in &invalid_chars {
let mock_vin = MockVin { vis: format!("{}ABCDEFG", c) };
assert_eq!(mock_vin.year_char_to_year(), None);
}
}
#[test]
fn wmi_lookup() {
let vin_bytes = b"5GZCZ43D13S812715";
let vin = Vin::parse(vin_bytes).unwrap();
let info = vin.lookup_wmi_info().unwrap();
assert_eq!(info.manufacturer, "General Motors");
assert_eq!(info.country, "United States");
assert_eq!(info.region, "North America");
}
#[test]
fn parse_trusted_valid() {
let vin_bytes = b"5GZCZ43D13S812715";
let vin =
Vin::parse_trusted(vin_bytes).expect("should parse successfully");
assert_eq!(vin.check_digit, '1');
assert_eq!(vin.wmi(), "5GZ");
assert_eq!(vin.vds(), "CZ43D1");
assert_eq!(vin.vis(), "3S812715");
}
#[test]
fn parse_trusted_invalid_check_digit() {
let vin_bytes = b"1HGCM82633A004351";
let err = Vin::parse_trusted(vin_bytes).unwrap_err();
match err {
Error::InvalidCheckDigit { expected, found } => {
assert_eq!(expected, '1');
assert_eq!(found, '3');
}
_ => panic!("unexpected error type"),
}
}
#[cfg(not(feature = "simd"))]
#[test]
fn parse_batch_all_valid() {
let vins =
["5GZCZ43D13S812715", "1M8GDM9AXKP042788", "WVWZZZ1K8AW180599"];
let vins: Vec<&[u8]> = vins.iter().map(|v| v.as_bytes()).collect();
let results = Vin::parse_batch(&vins);
assert_eq!(results.len(), 3);
for result in &results {
assert!(result.is_ok());
}
let first_vin = &results[0].as_ref().unwrap();
assert_eq!(first_vin.wmi(), "5GZ");
let second_vin = &results[1].as_ref().unwrap();
assert_eq!(second_vin.check_digit, 'X');
}
#[cfg(not(feature = "simd"))]
#[test]
fn parse_batch_with_invalid() {
let vins = [
"5GZCZ43D13S812715", "INVALID", "1HGCM82633A004351", ];
let vins: Vec<&[u8]> = vins.iter().map(|v| v.as_bytes()).collect();
let results = Vin::parse_batch(&vins);
assert_eq!(results.len(), 3);
assert!(results[0].is_ok());
match &results[1] {
Err(Error::InvalidLength) => (),
_ => panic!("Expected InvalidLength error"),
}
match &results[2] {
Err(Error::InvalidCheckDigit { expected, found }) => {
assert_eq!(*expected, '3');
assert_eq!(*found, '1');
}
_ => panic!("Expected InvalidCheckDigit error"),
}
}
#[cfg(feature = "simd")]
#[test]
fn parse_batch_simd_all_valid() {
let vins =
["5GZCZ43D13S812715", "1M8GDM9AXKP042788", "WVWZZZ1K8AW180599"];
let vins: Vec<&[u8]> = vins.iter().map(|v| v.as_bytes()).collect();
let results = Vin::parse_batch(&vins);
assert_eq!(results.len(), 3);
for result in &results {
assert!(result.is_ok());
}
let first_vin = &results[0].as_ref().unwrap();
assert_eq!(first_vin.wmi(), "5GZ");
let second_vin = &results[1].as_ref().unwrap();
assert_eq!(second_vin.check_digit, 'X');
}
#[cfg(feature = "simd")]
#[test]
fn parse_batch_simd_with_invalid() {
let vins = [
"5GZCZ43D13S812715", "INVALID", "1HGCM82633A004351", ];
let vins: Vec<&[u8]> = vins.iter().map(|v| v.as_bytes()).collect();
let results = Vin::parse_batch(&vins);
assert_eq!(results.len(), 3);
assert!(results[0].is_ok());
match &results[1] {
Err(Error::InvalidLength) => (),
_ => panic!("Expected InvalidLength error"),
}
match &results[2] {
Err(Error::InvalidCheckDigit { expected, found }) => {
assert_eq!(*expected, '1');
assert_eq!(*found, '3');
}
_ => panic!("Expected InvalidCheckDigit error"),
}
}
#[test]
fn vin_display() {
let vin_bytes = b"5GZCZ43D13S812715";
let vin = Vin::parse(vin_bytes).expect("should parse successfully");
assert_eq!(vin.to_string(), "5GZ-CZ43D1-3S812715");
}
#[test]
fn vin_display_with_x_check_digit() {
let vin_bytes = b"1M8GDM9AXKP042788";
let vin = Vin::parse(vin_bytes).expect("should parse successfully");
assert_eq!(vin.to_string(), "1M8-GDM9AX-KP042788");
}
#[test]
fn invalid_length_display() {
let err = Error::InvalidLength;
assert_eq!(
err.to_string(),
format!("VIN must be exactly {} characters", MAX_LENGTH)
);
}
#[test]
fn invalid_character_display() {
let err = Error::InvalidCharacter('*');
assert_eq!(
err.to_string(),
"VIN contains invalid character: '*' (valid characters are alphanumeric excluding I, O, and Q)"
);
}
#[test]
fn invalid_lowercase_character_display() {
let err = Error::InvalidLowercaseCharacter;
assert_eq!(
err.to_string(),
"VIN must only contain uppercase characters"
);
}
#[test]
fn invalid_check_digit_display() {
let err = Error::InvalidCheckDigit { expected: '3', found: '1' };
assert_eq!(
err.to_string(),
"VIN has invalid check digit: expected '3', found '1'"
);
}
#[test]
fn error_is_std_error() {
fn is_error<T: StdError>(_: T) -> bool {
true
}
assert!(is_error(Error::InvalidLength));
assert!(is_error(Error::InvalidCharacter('!')));
assert!(is_error(Error::InvalidLowercaseCharacter));
assert!(is_error(Error::InvalidCheckDigit {
expected: 'X',
found: '0',
}));
}
#[test]
fn parse_valid_vin() {
let vin_bytes = b"5GZCZ43D13S812715";
let vin = Vin::parse(vin_bytes).expect("should parse successfully");
assert_eq!(vin.check_digit, '1');
assert_eq!(vin.wmi(), "5GZ");
assert_eq!(vin.vds(), "CZ43D1");
assert_eq!(vin.vis(), "3S812715");
}
#[test]
fn valid_check_digit_x() {
let vin_bytes = b"1M8GDM9AXKP042788";
let vin = Vin::parse(vin_bytes).unwrap();
assert_eq!(vin.check_digit, 'X');
}
#[test]
fn invalid_check_digit() {
let vin_bytes = b"1HGCM82633A004351";
let err = Vin::parse(vin_bytes).unwrap_err();
match err {
Error::InvalidCheckDigit { expected, found } => {
assert_eq!(expected, '3');
assert_eq!(found, '1');
}
_ => panic!("unexpected error type"),
}
}
#[test]
fn with_invalid_lowercase_char() {
let vin_bytes = b"HHGCM82633a00435A";
let err = Vin::parse(vin_bytes).unwrap_err();
assert!(matches!(err, Error::InvalidLowercaseCharacter));
}
#[test]
fn with_invalid_alphanumeric_char() {
let vin_bytes = b"/HGCM82633A00435A";
let err = Vin::parse(vin_bytes).unwrap_err();
assert!(matches!(err, Error::InvalidCharacter('/')));
}
#[test]
fn with_invalid_char_i() {
let vin_bytes = b"1HGCM82633A00435I";
let err = Vin::parse(vin_bytes).unwrap_err();
assert!(matches!(err, Error::InvalidCharacter('I')));
}
#[test]
fn with_invalid_char_o() {
let vin_bytes = b"1HGCM82633A00435O";
let err = Vin::parse(vin_bytes).unwrap_err();
assert!(matches!(err, Error::InvalidCharacter('O')));
}
#[test]
fn with_invalid_char_q() {
let vin_bytes = b"1HGCM82633A00435Q";
let err = Vin::parse(vin_bytes).unwrap_err();
assert!(matches!(err, Error::InvalidCharacter('Q')));
}
#[test]
fn invalid_length_less() {
let vin_bytes = b"123456789";
let err = Vin::parse(vin_bytes).unwrap_err();
assert!(matches!(err, Error::InvalidLength));
}
#[test]
fn invalid_length_greater() {
let vin_bytes = b"1HGCM82633A00435A1HGCM82633A00435A";
let err = Vin::parse(vin_bytes).unwrap_err();
assert!(matches!(err, Error::InvalidLength));
}
#[test]
fn lookup_exact_match() {
let info = WmiInfo::lookup(b"5GZ").unwrap();
assert_eq!(info.manufacturer, "General Motors");
assert_eq!(info.country, "United States");
}
#[test]
fn lookup_prefix_match() {
let info = WmiInfo::lookup(b"5G1").unwrap();
assert_eq!(info.manufacturer, "General Motors");
}
#[test]
fn unknown_wmi() {
assert!(WmiInfo::lookup(b"XXX").is_none());
}
#[test]
fn region_lookup() {
assert_eq!(WmiInfo::region_from_char('W'), Some("Europe (Germany)"));
assert_eq!(WmiInfo::region_from_char('J'), Some("Asia (Japan)"));
}
}