Functional Weave
Code in Rust

logistics.hs-code-validate@1.0.0

impl/rust.rs

2,428 bytes · the Rust implementation · view raw

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) -> 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<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)),
        ("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()))
}