Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
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<char> = 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");
}
// Weights run 3, 1, 3, 1 ... leftwards from the digit next to the check
// digit. Counting from the right 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 mut total: u32 = 0;
let mut weight: u32 = 3;
for ch in chars[..chars.len() - 1].iter().rev() {
total += (*ch as u32 - 48) * weight;
weight = if weight == 3 { 1 } else { 3 };
}
let check = (10 - total % 10) % 10;
if check != chars[chars.len() - 1] as u32 - 48 {
return invalid("bad-check-digit");
}
let digits: String = chars.iter().collect();
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<String>| 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()))
}