Postal codes
Postal codes checked for 45 countries and written the post office's way: SW1A 1AA, 1012 JS, K1A 0B1, 100-0001, 20500-0001.
Version 1.0.0 · 4 functions · 1 crate
Functions
What use postal_codes.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use postal_valid, postal_format from postal_codes (Quillo 0.6).
postal_valid(code: str, country: str) → boolor an errorWhether code is a postal code of country (alpha-2, "US", "GB"...), spaces, dashes and case ignored. It checks the form, not that the code is in use.
postal_format(code: str, country: str) → stror an errorcode written as the country's post office writes it ("sw1a1aa" is "SW1A 1AA" in GB, "12345-6789" stays so in the US), or an error saying it is not a postal code there.
postal_check(code: str, country: str) → anyor an errorWhat a form needs for one country: { valid, formatted, example }, formatted null when it is not valid. An error for a country without rules.
postal_countries() → listThe countries with rules, as alpha-2 codes.
Get it
$ quillo install postal_codes # postal_codes.qwer into this folder$ quillo install postal_codes --example # and its example, postal_codes.qweOr with curl:
$ curl -O https://quillolang.org/plugins/files/postal_codes.qwer$ curl -O https://quillolang.org/plugins/files/postal_codes.qweThen, in your program, all of it or only what you call:
use postal_codes.qweruse postal_valid, postal_format from postal_codes // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
regex = { version = "1", default-features = false, features = ["std", "unicode-perl"] }Example
// An address form that checks the postal code for the chosen country and writes it the post office's way.//// quillo run postal_codes.qwe then open http://localhost:8080/check?code=sw1a1aa&country=GBuse "postal_codes.qwer"route GET /check ? { code: str, country: str } = postal_check(code, country)?route GET /examples = [ { code: "sw1a1aa", country: "GB" }, { code: "1012js", country: "NL" }, { code: "k1a0b1", country: "CA" }, { code: "1000001", country: "JP" }, { code: "205000001", country: "US" }, { code: "00950", country: "PL" }, { code: "1050", country: "LV" }, { code: "1234", country: "DE" },] | map((a) => { country: a.country, typed: a.code, shown: postal_format(a.code, a.country) ?? "invalid" })route POST /address { let a = request.json ?? {} if !postal_valid(a.postal_code ?? "", a.country ?? "")? { reply 422 { error: "Please check the postal code." } } { ok: true, postal_code: postal_format(a.postal_code, a.country)? }}$ quillo run postal_codes.qwe$ curl 'localhost:8080/check?code=sw1a1aa&country=GB'$ curl 'localhost:8080/examples'Source
//! Postal codes checked and written the way each country's post office writes them, for 45 countries: "sw1a1aa" is "SW1A 1AA" in the UK, "1234ab" is "1234 AB" in the Netherlands, "1000001" is "100-0001" in Japan.//! version: 1.0.0//!//! [dependencies]//! regex = { version = "1", default-features = false, features = ["std", "unicode-perl"] }use regex::Regex;use serde_json::{json, Value};use std::collections::HashMap;use std::sync::OnceLock;/// Each country: the code with spaces and dashes taken out, as a pattern; how it is written (`#` is the next character of the code; a pattern per length); an example.const RULES: &[(&str, &str, &[&str], &str)] = &[ ("AR", r"[A-Z]\d{4}[A-Z]{3}|\d{4}", &["#", "#"], "C1425DKF"), ("AT", r"[1-9]\d{3}", &["####"], "1010"), ("AU", r"\d{4}", &["####"], "2000"), ("BE", r"[1-9]\d{3}", &["####"], "1000"), ("BG", r"\d{4}", &["####"], "1000"), ("BR", r"\d{8}", &["#####-###"], "01310-100"), ("CA", r"[ABCEGHJ-NPRSTVXY]\d[ABCEGHJ-NPRSTV-Z]\d[ABCEGHJ-NPRSTV-Z]\d", &["### ###"], "K1A 0B1"), ("CH", r"[1-9]\d{3}", &["####"], "8001"), ("CN", r"\d{6}", &["######"], "100000"), ("CZ", r"[1-7]\d{4}", &["### ##"], "110 00"), ("DE", r"\d{5}", &["#####"], "10115"), ("DK", r"[1-9]\d{3}", &["####"], "1050"), ("EE", r"\d{5}", &["#####"], "10111"), ("ES", r"(0[1-9]|[1-4]\d|5[0-2])\d{3}", &["#####"], "28013"), ("FI", r"\d{5}", &["#####"], "00100"), ("FR", r"\d{5}", &["#####"], "75008"), ("GB", r"GIR0AA|[A-PR-UWYZ](\d{1,2}|[A-HK-Y]\d{1,2}|\d[A-HJKPS-UW]|[A-HK-Y]\d[ABEHMNPRV-Y])\d[ABD-HJLNP-UW-Z]{2}", &["@ ###"], "SW1A 1AA"), ("GR", r"[1-8]\d{4}", &["### ##"], "105 57"), ("HR", r"[1-5]\d{4}", &["#####"], "10000"), ("HU", r"[1-9]\d{3}", &["####"], "1051"), ("IE", r"([AC-FHKNPRTV-Y]\d{2}|D6W)[0-9AC-FHKNPRTV-Y]{4}", &["### ####"], "D02 X285"), ("IL", r"\d{7}", &["#######"], "6100000"), ("IN", r"[1-9]\d{5}", &["######"], "110001"), ("IS", r"\d{3}", &["###"], "101"), ("IT", r"\d{5}", &["#####"], "00184"), ("JP", r"\d{7}", &["###-####"], "100-0001"), ("KR", r"\d{5}", &["#####"], "03187"), ("LT", r"(LT)?\d{5}", &["LT-#####"], "LT-01100"), ("LU", r"\d{4}", &["####"], "1009"), ("LV", r"(LV)?\d{4}", &["LV-####"], "LV-1050"), ("MX", r"\d{5}", &["#####"], "06600"), ("NL", r"[1-9]\d{3}(?:[A-RT-Z][A-Z]|S[BCE-RT-Z])", &["#### ##"], "1012 JS"), ("NO", r"\d{4}", &["####"], "0150"), ("NZ", r"\d{4}", &["####"], "6011"), ("PL", r"\d{5}", &["##-###"], "00-950"), ("PT", r"[1-9]\d{6}", &["####-###"], "1100-148"), ("RO", r"\d{6}", &["######"], "010011"), ("RU", r"\d{6}", &["######"], "101000"), ("SE", r"[1-9]\d{4}", &["### ##"], "111 22"), ("SG", r"\d{6}", &["######"], "018956"), ("SI", r"[1-9]\d{3}", &["####"], "1000"), ("SK", r"[089]\d{4}", &["### ##"], "811 01"), ("TR", r"(0[1-9]|[1-7]\d|8[01])\d{3}", &["#####"], "34000"), ("US", r"\d{5}(\d{4})?", &["#####", "#####-####"], "20500"), ("ZA", r"\d{4}", &["####"], "8001"),];fn rules() -> &'static HashMap<&'static str, (Regex, &'static [&'static str], &'static str)> { static R: OnceLock<HashMap<&str, (Regex, &[&str], &str)>> = OnceLock::new(); R.get_or_init(|| RULES.iter().filter_map(|(c, p, f, e)| Regex::new(&format!("^(?:{})$", p)).ok().map(|r| (*c, (r, *f, *e)))).collect())}fn compact(code: &str) -> String { code.chars().filter(|c| c.is_ascii_alphanumeric()).collect::<String>().to_uppercase()}/// Writes the compact code by a layout: `#` is the next character, `@` the characters before the last three.fn layout(c: &str, f: &str) -> String { let c = c.trim_start_matches("LT").trim_start_matches("LV"); if f == "#" { return c.to_string(); } let mut out = String::new(); let mut it = c.chars(); for p in f.chars() { match p { '#' => out.extend(it.next()), '@' => out.extend(it.by_ref().take(c.len().saturating_sub(3))), other => out.push(other), } } out.extend(it); out}fn get(country: &str) -> Result<&'static (Regex, &'static [&'static str], &'static str), String> { let cc = country.trim().to_uppercase(); let cc = if cc == "UK" { "GB".to_string() } else { cc }; rules().get(cc.as_str()).ok_or_else(|| format!("no postal code rules for {:?}; see postal_countries()", country))}/// Whether `code` is a postal code of `country` (alpha-2, "US", "GB"...), spaces, dashes and case ignored. It checks the form, not that the code is in use.pub fn postal_valid(code: &str, country: &str) -> Result<bool, String> { let c = compact(code); Ok(c.len() <= 12 && get(country)?.0.is_match(&c))}/// `code` written as the country's post office writes it ("sw1a1aa" is "SW1A 1AA" in GB, "12345-6789" stays so in the US), or an error saying it is not a postal code there.pub fn postal_format(code: &str, country: &str) -> Result<String, String> { let c = compact(code); let (re, formats, example) = get(country)?; if c.len() > 12 || !re.is_match(&c) { return Err(format!("{:?} is not a postal code in {}: they look like {}", code, country.to_uppercase(), example)); } let bare = c.trim_start_matches("LT").trim_start_matches("LV"); // The US has two layouts, by length; others one. let f = if formats.len() > 1 && bare.len() > 5 { formats[1] } else { formats[0] }; Ok(layout(&c, f))}/// What a form needs for one country: `{ valid, formatted, example }`, `formatted` null when it is not valid. An error for a country without rules.pub fn postal_check(code: &str, country: &str) -> Result<Value, String> { let (_, _, example) = get(country)?; let f = postal_format(code, country).ok(); Ok(json!({ "valid": f.is_some(), "formatted": f, "example": example }))}/// The countries with rules, as alpha-2 codes.pub fn postal_countries() -> Vec<String> { RULES.iter().map(|r| r.0.to_string()).collect()}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 downloadsCountries countries.qwer · 1.0.0ISO 3166 countries for forms and addresses: alpha-2, alpha-3 and numeric codes, names in several languages, calling codes, flags, continents, currencies and EU membership.9 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 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 downloadsVINs vin.qwer · 1.0.0Vehicle identification numbers checked and decoded: the check digit, the maker's region, country and name, the model year, plant and serial.9 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 downloadsThread 0 posts
On Postal codes: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.