Validation

VINs

Vehicle identification numbers checked and decoded: the check digit, the maker's region, country and name, the model year, plant and serial.

Version 1.0.0 · 3 functions · 0 crates

#vehicles#validation#forms#cars

Functions

What use vin.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use vin_check_digit, vin_valid from vin (Quillo 0.6).

vin_check_digit(vin: str) → stror an error

The check digit a VIN should have in its 9th place ("0" to "9" or "X").

vin_valid(vin: str) → bool

Whether vin is well formed (17 characters, no I, O or Q) and its 9th character is the right check digit. North American and Chinese VINs always carry one; many European ones do not, so check vin_decode's check_digit_ok with care for those.

vin_decode(vin: str) → anyor an error

What a well-formed VIN says (an error when it is not one): { check_digit_ok, wmi, region, country, manufacturer, model_year, model_years, plant, serial }. model_year is read from the 10th character: the code repeats every 30 years, so model_years gives both, and model_year picks by the 7th character as North American VINs do (a letter there means 2010 or later), never after next year. manufacturer is null for makers not in its list.

Get it

$ quillo install vin               # vin.qwer into this folder$ quillo install vin --example     # and its example, vin.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/vin.qwer$ curl -O https://quillolang.org/plugins/files/vin.qwe

Then, in your program, all of it or only what you call:

main.qwe
use vin.qweruse vin_check_digit, vin_valid from vin      // Quillo 0.6: only these

It uses these crates, which cargo fetches the first time the plugin is built:

[dependencies]

Example

vin.qwe
// A used-car listing form that reads the VIN: check digit, maker, country and model year.////   quillo run vin.qwe      then open http://localhost:8080/vin/1HGCM82633A004352use "vin.qwer"route GET /vin/:vin = vin_decode(vin)?route GET /check = ["1HGCM82633A004352", "1HGCM82633A004353", "5YJ3E1EA2KF317000", "WBA3A5C57CF256651"] | map((v) => { vin: v, valid: vin_valid(v), should_be: vin_check_digit(v)? })
$ quillo run vin.qwe$ curl 'localhost:8080/vin/1HGCM82633A004352'$ curl 'localhost:8080/check'

Source

vin.qwer
//! Vehicle identification numbers (VINs) checked and decoded, for garages, insurers and car marketplaces: the check digit, the maker's region, country and name, the model year, plant and serial.//! version: 1.0.0//!//! [dependencies]use serde_json::{json, Value};const WEIGHTS: [u32; 17] = [8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2];fn value(c: char) -> Option<u32> {    match c {        '0'..='9' => c.to_digit(10),        'A' | 'J' => Some(1), 'B' | 'K' | 'S' => Some(2), 'C' | 'L' | 'T' => Some(3), 'D' | 'M' | 'U' => Some(4),        'E' | 'N' | 'V' => Some(5), 'F' | 'W' => Some(6), 'G' | 'P' | 'X' => Some(7), 'H' | 'Y' => Some(8), 'R' | 'Z' => Some(9),        _ => None, // I, O and Q are never used.    }}fn clean(vin: &str) -> Result<Vec<char>, String> {    let v: Vec<char> = vin.trim().to_uppercase().chars().filter(|c| *c != ' ' && *c != '-').collect();    if v.len() != 17 {        return Err(format!("a VIN has 17 characters, this has {}", v.len()));    }    if let Some(bad) = v.iter().find(|c| value(**c).is_none()) {        return Err(format!("{:?} cannot be in a VIN (letters I, O and Q never are)", bad));    }    Ok(v)}fn expected(v: &[char]) -> char {    let s: u32 = v.iter().zip(WEIGHTS).map(|(c, w)| value(*c).unwrap_or(0) * w).sum();    match s % 11 { 10 => 'X', r => char::from_digit(r, 10).unwrap_or('0') }}/// The check digit a VIN should have in its 9th place ("0" to "9" or "X").pub fn vin_check_digit(vin: &str) -> Result<String, String> {    Ok(expected(&clean(vin)?).to_string())}/// Whether `vin` is well formed (17 characters, no I, O or Q) and its 9th character is the right check digit. North American and Chinese VINs always carry one; many European ones do not, so check `vin_decode`'s `check_digit_ok` with care for those.pub fn vin_valid(vin: &str) -> bool {    clean(vin).map_or(false, |v| v[8] == expected(&v))}fn region(c: char) -> &'static str {    match c {        'A'..='H' => "Africa",        'J'..='R' => "Asia",        'S'..='Z' => "Europe",        '1'..='5' => "North America",        '6' | '7' => "Oceania",        _ => "South America",    }}fn country(a: char, b: char) -> Option<&'static str> {    let in_ = |lo: char, hi: char| b >= lo && b <= hi;    Some(match a {        'A' if in_('A', 'H') => "South Africa",        'J' => "Japan",        'K' if in_('L', 'R') => "South Korea",        'K' if in_('5', '9') => "South Korea",        'L' => "China",        'M' if in_('A', 'E') => "India",        'M' if in_('F', 'K') => "Indonesia",        'M' if in_('L', 'R') => "Thailand",        'N' if in_('L', 'R') => "Turkey",        'P' if in_('L', 'R') => "Malaysia",        'R' if in_('L', 'M') => "Taiwan",        'S' if in_('A', 'M') => "United Kingdom",        'S' if in_('N', 'T') => "Germany",        'S' if in_('U', 'Z') => "Poland",        'T' if in_('A', 'H') => "Switzerland",        'T' if in_('J', 'P') => "Czech Republic",        'T' if in_('R', 'V') => "Hungary",        'T' if in_('W', 'Z') || in_('1', '1') => "Portugal",        'U' if in_('U', 'Z') => "Romania",        'V' if in_('A', 'E') => "Austria",        'V' if in_('F', 'R') => "France",        'V' if in_('S', 'W') => "Spain",        'V' if in_('X', 'Z') || in_('1', '2') => "Serbia",        'W' => "Germany",        'X' if in_('L', 'R') => "Netherlands",        'X' if in_('S', 'W') || in_('3', '0') => "Russia",        'Y' if in_('A', 'E') => "Belgium",        'Y' if in_('F', 'K') => "Finland",        'Y' if in_('S', 'W') => "Sweden",        'Z' if in_('A', 'R') => "Italy",        '1' | '4' | '5' => "United States",        '2' => "Canada",        '3' if in_('A', 'W') => "Mexico",        '3' if in_('X', 'Z') || in_('1', '7') => "Costa Rica",        '6' if in_('A', 'W') => "Australia",        '7' if in_('A', 'E') => "New Zealand",        '8' if in_('A', 'E') => "Argentina",        '8' if in_('F', 'K') => "Chile",        '9' if in_('A', 'E') || in_('3', '9') => "Brazil",        _ => return None,    })}/// Makers by their first three characters (WMI), the most common ones.const MAKERS: &[(&str, &str)] = &[    ("1FA", "Ford"), ("1FT", "Ford"), ("1FM", "Ford"), ("1G1", "Chevrolet"), ("1GC", "Chevrolet"), ("1GN", "Chevrolet"), ("1G6", "Cadillac"), ("1GT", "GMC"),    ("1HG", "Honda"), ("1J4", "Jeep"), ("1C4", "Chrysler"), ("1C6", "Ram"), ("1N4", "Nissan"), ("1VW", "Volkswagen"), ("1YV", "Mazda"), ("2HG", "Honda"),    ("2T1", "Toyota"), ("3VW", "Volkswagen"), ("3FA", "Ford"), ("3N1", "Nissan"), ("4T1", "Toyota"), ("4S3", "Subaru"), ("5YJ", "Tesla"), ("7SA", "Tesla"),    ("5NP", "Hyundai"), ("5UX", "BMW"), ("JHM", "Honda"), ("JN1", "Nissan"), ("JT2", "Toyota"), ("JTD", "Toyota"), ("JTE", "Toyota"), ("JF1", "Subaru"),    ("JM1", "Mazda"), ("JA3", "Mitsubishi"), ("JS3", "Suzuki"), ("KMH", "Hyundai"), ("KNA", "Kia"), ("KND", "Kia"), ("LRW", "Tesla"), ("LFV", "FAW-Volkswagen"),    ("SAL", "Land Rover"), ("SAJ", "Jaguar"), ("SCC", "Lotus"), ("SCF", "Aston Martin"), ("TMB", "Škoda"), ("TRU", "Audi"), ("VF1", "Renault"), ("VF3", "Peugeot"),    ("VF7", "Citroën"), ("VSS", "SEAT"), ("WAU", "Audi"), ("WBA", "BMW"), ("WBS", "BMW M"), ("WDB", "Mercedes-Benz"), ("WDD", "Mercedes-Benz"), ("W1K", "Mercedes-Benz"),    ("WMW", "MINI"), ("WP0", "Porsche"), ("WP1", "Porsche"), ("WVW", "Volkswagen"), ("WV1", "Volkswagen Commercial"), ("WV2", "Volkswagen Commercial"),    ("W0L", "Opel"), ("YV1", "Volvo"), ("YS3", "Saab"), ("ZFA", "Fiat"), ("ZAR", "Alfa Romeo"), ("ZFF", "Ferrari"), ("ZHW", "Lamborghini"), ("ZAM", "Maserati"),];/// What a well-formed VIN says (an error when it is not one): `{ check_digit_ok, wmi, region, country, manufacturer, model_year, model_years, plant, serial }`. `model_year` is read from the 10th character: the code repeats every 30 years, so `model_years` gives both, and `model_year` picks by the 7th character as North American VINs do (a letter there means 2010 or later), never after next year. `manufacturer` is null for makers not in its list.pub fn vin_decode(vin: &str) -> Result<Value, String> {    let v = clean(vin)?;    let s: String = v.iter().collect();    let wmi = &s[..3];    let codes = "ABCDEFGHJKLMNPRSTVWXY123456789";    let year = codes.find(v[9]).map(|i| 1980 + i as i64);    let now_year = 1970 + std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_secs() as i64) / 31_556_952;    let model_year = year.map(|y| {        let late = y + 30;        if v[6].is_ascii_alphabetic() && late <= now_year + 1 { late } else { y }    });    Ok(json!({        "check_digit_ok": v[8] == expected(&v),        "wmi": wmi,        "region": region(v[0]),        "country": country(v[0], v[1]),        "manufacturer": MAKERS.iter().find(|(w, _)| *w == wmi).map(|(_, m)| *m),        "model_year": model_year,        "model_years": year.map(|y| vec![y, y + 30]),        "plant": v[10].to_string(),        "serial": &s[11..],    }))}
Thread
98Useful? Like it, or add to its thread below.

Thread 0 posts

On VINs: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.