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
/// Weights for the second to tenth digits; the first digit is the check digit.
/// From HMRC's own reference checker (hmrc/domain, UtrReferenceChecker).
const WEIGHTS: [i64; 9] = [6, 7, 8, 9, 10, 5, 4, 3, 2];
/// The check digit for each remainder of the weighted sum modulo 11. It is
/// 11 - remainder except that remainders 0 and 1 give 2 and 1, so a check
/// digit is always a single digit and no reference is impossible.
const CHECK_DIGITS: [i64; 11] = [2, 1, 9, 8, 7, 6, 5, 4, 3, 2, 1];
fn invalid(reason: &str) -> UkUtr {
UkUtr {
valid: false,
normalised: None,
reason: Some(reason.to_string()),
}
}
/// Check a Unique Taxpayer Reference (Self Assessment or Corporation Tax).
///
/// A pass means the reference is well formed, not that HMRC issued it to
/// this taxpayer. Checks run in a fixed order (characters, length, check
/// digit) so every language gives the same first reason.
pub fn validate_uk_utr(value: &str) -> UkUtr {
let mut compact: Vec<char> = value.chars().filter(|ch| *ch != ' ').collect();
if compact.is_empty() {
return invalid("empty");
}
// Self Assessment shows the UTR with a K after it and some letters put
// it in front; one K at either end is stripped, as HMRC's checker does.
if compact[0] == 'K' || compact[0] == 'k' {
compact.remove(0);
} else if compact[compact.len() - 1] == 'K' || compact[compact.len() - 1] == 'k' {
compact.pop();
}
let mut digits: Vec<i64> = Vec::new();
for ch in &compact {
if !ch.is_ascii_digit() {
return invalid("bad-character");
}
digits.push(*ch as i64 - 48);
}
if digits.len() == 13 {
return invalid("thirteen-digits");
}
if digits.len() != 10 {
return invalid("bad-length");
}
let total: i64 = digits[1..].iter().zip(WEIGHTS.iter()).map(|(d, w)| d * w).sum();
if CHECK_DIGITS[(total % 11) as usize] != digits[0] {
return invalid("bad-check-digit");
}
UkUtr {
valid: true,
normalised: Some(compact.iter().collect()),
reason: None,
}
}
/// Object keys are camelCase to match the shared vectors, and so that a
/// capability building on this one can reuse the same shape.
pub fn uk_utr_to_value(result: &UkUtr) -> 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)),
("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.
uk_utr_to_value(&validate_uk_utr(args[0].as_str()))
}