Functional Weave
Code in TypeScript

validation.bic@1.0.0

impl/rust.rs

2,426 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) -> BicCheck {
    BicCheck {
        valid: false,
        normalised: None,
        institution: None,
        country: None,
        location: None,
        branch: None,
        reason: Some(reason.to_string()),
    }
}

/// Check the format of a BIC and split it into institution, country, location
/// and branch.
///
/// Well formed is not registered: only SWIFT's directory knows whether the
/// code is assigned. Validators answer rather than panic.
pub fn validate_bic(value: &str) -> BicCheck {
    let bic: Vec<char> = value
        .chars()
        .filter(|ch| *ch != ' ')
        .map(|ch| ch.to_ascii_uppercase())
        .collect();
    if bic.is_empty() {
        return invalid("empty");
    }
    if bic.iter().any(|ch| !(ch.is_ascii_uppercase() || ch.is_ascii_digit())) {
        return invalid("bad-character");
    }
    if bic.len() != 8 && bic.len() != 11 {
        return invalid("bad-length");
    }
    if !bic[4].is_ascii_uppercase() || !bic[5].is_ascii_uppercase() {
        return invalid("bad-country");
    }

    let part = |from: usize, to: usize| -> Option<String> { Some(bic[from..to].iter().collect()) };
    BicCheck {
        valid: true,
        normalised: Some(bic.iter().collect()),
        institution: part(0, 4),
        country: part(4, 6),
        location: part(6, 8),
        // An 8-character BIC is the primary office, which the 11-character form spells XXX.
        branch: if bic.len() == 11 { part(8, 11) } else { Some("XXX".to_string()) },
        reason: None,
    }
}

/// Object keys are camelCase to match the shared vectors.
pub fn bic_check_to_value(result: &BicCheck) -> 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)),
        ("institution", text(&result.institution)),
        ("country", text(&result.country)),
        ("location", text(&result.location)),
        ("branch", text(&result.branch)),
        ("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.
    bic_check_to_value(&validate_bic(args[0].as_str()))
}