use super::funejson::Value; use super::validation_gs1_check_digit::is_gs1_check_digit_valid; fn invalid(reason: &str) -> GtinCheck { GtinCheck { valid: false, normalised: None, kind: None, gtin14: None, reason: Some(reason.to_string()), } } /// Check an EAN-8, UPC-A, EAN-13 or GTIN-14 against the GS1 mod-10 check digit. /// /// A pass means the number was typed correctly, not that GS1 issued it. /// Validators answer rather than panic: a malformed value is the answer "no". pub fn validate_gtin(value: &str) -> GtinCheck { // Barcodes are printed in groups, so spaces and hyphens are ignored anywhere. let chars: Vec = value.chars().filter(|ch| *ch != ' ' && *ch != '-').collect(); if chars.is_empty() { return invalid("empty"); } if chars.iter().any(|ch| !ch.is_ascii_digit()) { return invalid("bad-character"); } let kind = match chars.len() { 8 => "ean-8", 12 => "upc-a", 13 => "ean-13", 14 => "gtin-14", _ => return invalid("bad-length"), }; // Zero everywhere satisfies the arithmetic and has never been allocated. if chars.iter().all(|ch| *ch == '0') { return invalid("all-zero"); } // The GS1 mod-10 check, weighted from the right, which is what makes one // rule serve all four lengths (EAN-13's first digit gets 1, EAN-8's and // UPC-A's get 3). let digits: String = chars.iter().collect(); if !is_gs1_check_digit_valid(&digits) { return invalid("bad-check-digit"); } GtinCheck { valid: true, gtin14: Some(format!("{:0>14}", digits)), normalised: Some(digits), kind: Some(kind.to_string()), reason: None, } } /// Object keys are camelCase to match the shared vectors. pub fn gtin_check_to_value(result: &GtinCheck) -> Value { let text = |field: &Option| match field { Some(s) => Value::str(s), None => Value::Null, }; Value::obj(vec![ ("valid", Value::Bool(result.valid)), ("normalised", text(&result.normalised)), ("kind", text(&result.kind)), ("gtin14", text(&result.gtin14)), ("reason", text(&result.reason)), ]) } pub fn fune_vector(args: &[Value]) -> Value { // A non-string argument arrives here as an empty string, which is exactly // the answer TypeScript and Python give for a non-string: empty. gtin_check_to_value(&validate_gtin(args[0].as_str())) }