use super::funejson::Value; fn invalid(reason: &str) -> LeiCheck { LeiCheck { valid: false, normalised: None, lou_prefix: None, reason: Some(reason.to_string()), } } /// ISO 7064 MOD 97-10 over the whole LEI, letters read as A = 10 ... Z = 35. /// /// The expansion is up to 40 digits, past any 64-bit integer, so the remainder /// is carried one character at a time. A letter contributes two digits and so /// multiplies the running remainder by 100; the largest intermediate is /// 96 * 100 + 35. fn mod97(lei: &[char]) -> u32 { let mut remainder: u32 = 0; for ch in lei { if ch.is_ascii_digit() { remainder = (remainder * 10 + (*ch as u32 - 48)) % 97; } else { remainder = (remainder * 100 + (*ch as u32 - 55)) % 97; } } remainder } /// Check a Legal Entity Identifier's format and ISO 17442 check digits. /// /// Well formed is not issued: only GLEIF says whether an LEI is current. /// Validators answer rather than panic. pub fn validate_lei(value: &str) -> LeiCheck { let lei: Vec = value .chars() .filter(|ch| *ch != ' ') .map(|ch| ch.to_ascii_uppercase()) .collect(); if lei.is_empty() { return invalid("empty"); } if lei.iter().any(|ch| !(ch.is_ascii_digit() || ch.is_ascii_uppercase())) { return invalid("bad-character"); } if lei.len() != 20 { return invalid("bad-length"); } if !lei[18].is_ascii_digit() || !lei[19].is_ascii_digit() { return invalid("bad-format"); } // The remainder must be 1, not 0: the check digits are chosen so that it is. if mod97(&lei) != 1 { return invalid("bad-check-digit"); } LeiCheck { valid: true, normalised: Some(lei.iter().collect()), lou_prefix: Some(lei[..4].iter().collect()), reason: None, } } /// Object keys are camelCase to match the shared vectors. pub fn lei_check_to_value(result: &LeiCheck) -> 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)), ("louPrefix", text(&result.lou_prefix)), ("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. lei_check_to_value(&validate_lei(args[0].as_str())) }