use super::funejson::Value; fn invalid(reason: &str) -> HsCodeCheck { HsCodeCheck { valid: false, normalised: None, kind: None, chapter: None, heading: None, subheading: None, reason: Some(reason.to_string()), } } /// Check the format of a 6-digit HS, 8-digit CN or 10-digit TARIC/UK /// commodity code. Validators answer rather than panic: a malformed code is /// the answer "no". pub fn validate_hs_code(value: &str) -> HsCodeCheck { let mut digits = String::new(); for ch in value.chars() { if ch == ' ' || ch == '.' { continue; } if !ch.is_ascii_digit() { return invalid("bad-character"); } digits.push(ch); } if digits.is_empty() { return invalid("empty"); } let kind = match digits.len() { 6 => "hs", 8 => "cn", 10 => "taric", _ => return invalid("bad-length"), }; // Text, not a number: chapter "01" must keep its zero. let chapter = &digits[0..2]; if chapter == "00" { return invalid("bad-chapter"); } if chapter == "77" { return invalid("reserved-chapter"); } // 98 and 99 are left to national tariffs, so only a national-length code may use them. if (chapter == "98" || chapter == "99") && kind == "hs" { return invalid("national-chapter"); } if &digits[2..4] == "00" { return invalid("bad-heading"); } HsCodeCheck { valid: true, chapter: Some(chapter.to_string()), heading: Some(digits[0..4].to_string()), subheading: Some(digits[0..6].to_string()), kind: Some(kind.to_string()), normalised: Some(digits.clone()), reason: None, } } pub fn hs_code_check_to_value(result: &HsCodeCheck) -> 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)), ("chapter", text(&result.chapter)), ("heading", text(&result.heading)), ("subheading", text(&result.subheading)), ("reason", text(&result.reason)), ]) } pub fn fune_vector(args: &[Value]) -> Value { hs_code_check_to_value(&validate_hs_code(args[0].as_str())) }