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
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 errorThe check digit a VIN should have in its 9th place ("0" to "9" or "X").
vin_valid(vin: str) → boolWhether 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 errorWhat 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.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/vin.qwer$ curl -O https://quillolang.org/plugins/files/vin.qweThen, in your program, all of it or only what you call:
use vin.qweruse vin_check_digit, vin_valid from vin // Quillo 0.6: only theseIt uses these crates, which cargo fetches the first time the plugin is built:
Example
// 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
//! 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..], }))}Related
cards.qwer · 1.0.0Payment card numbers checked before they reach a payment provider: Luhn check digit, brand and length, expiry, and the number formatted or masked for receipts.7 views · 7 downloadsNational ID numbers national_ids.qwer · 1.0.0National identity numbers of 29 countries checked by their own rules (US SSN, UK NINO, CPF, Aadhaar, PH TIN, DNI and NIE, codice fiscale, PESEL...), with the birth date and sex read out where the number holds them.7 views · 7 downloadsPostal codes postal_codes.qwer · 1.0.0Postal codes checked for 45 countries and written the post office's way: SW1A 1AA, 1012 JS, K1A 0B1, 100-0001, 20500-0001.6 views · 7 downloadsVAT numbers vat.qwer · 1.0.0EU VAT numbers checked offline by format and check digits for all 27 countries, Northern Ireland, the UK, Switzerland and Norway, to catch typos before an invoice goes out.8 views · 8 downloadsJSON Schema json_schema.qwer · 1.0.0Check values against a JSON Schema (drafts 4 to 2020-12), with every error and the path of the value at fault.8 views · 8 downloadsCheck digits check_digits.qwer · 1.0.0Check digits made and checked: ISBN-10 and ISBN-13 with conversion, EAN-13, EAN-8 and UPC-A barcodes, IMEI, Luhn, ISO 7064 mod 97-10 references and Verhoeff.6 views · 7 downloadsThread 0 posts
On VINs: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.