1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
use crate::secondary_validation::{Validator, validate_mod11_weighted_checksum};
pub struct UkTrnChecksum;
/// From [HMRC UtrReferenceChecker](https://github.com/hmrc/reference-checker/blob/main/src/main/scala/uk/gov/hmrc/referencechecker/ReferenceChecker.scala#L103)
const WEIGHTS: &[u32; 9] = &[6, 7, 8, 9, 10, 5, 4, 3, 2];
const REMAINDER_LOOKUP: [u32; 11] = [2, 1, 9, 8, 7, 6, 5, 4, 3, 2, 1];
/// Length of the UTR (used for both Self Assessment and Corporation Tax).
const UTR_DIGIT_LENGTH: usize = 10;
/// Length when a 3-digit prefix precedes the 10-digit UTR; we validate the last 10 digits.
const PREFIXED_UTR_DIGIT_LENGTH: usize = 13;
impl Validator for UkTrnChecksum {
fn is_valid_match(&self, regex_match: &str) -> bool {
// Self Assessment UTR may have one leading or trailing K; more than one K is invalid.
if regex_match
.chars()
.filter(|c| c.eq_ignore_ascii_case(&'K'))
.count()
> 1
{
return false;
}
let digits: Vec<char> = regex_match.chars().filter(|c| c.is_ascii_digit()).collect();
let ten_digits = match digits.len() {
UTR_DIGIT_LENGTH => digits,
PREFIXED_UTR_DIGIT_LENGTH => digits[3..PREFIXED_UTR_DIGIT_LENGTH].to_vec(), // 3-digit prefix + 10-digit UTR
_ => return false,
};
// UTR has check digit at index 0 and data at 1..10; validate_mod11_weighted_checksum
// expects data first then check, so we reorder to avoid duplicating the algorithm.
let data_then_check: String = ten_digits[1..UTR_DIGIT_LENGTH]
.iter()
.chain(ten_digits[0..1].iter())
.collect();
validate_mod11_weighted_checksum(&data_then_check, WEIGHTS, |remainder| {
Some(REMAINDER_LOOKUP[remainder as usize])
})
}
}
#[cfg(test)]
mod test {
use crate::secondary_validation::*;
#[test]
fn test_valid_uk_trn() {
let validator = UkTrnChecksum;
// Valid numbers generated from https://generator.avris.it/
// Manual addition of k
let valid_numbers = vec![
"1123456789",
"112 345 678 9",
"6898201056K",
"0006452194352",
"K1232611912725",
"4491783771k",
];
for number in valid_numbers {
assert!(validator.is_valid_match(number), "expected valid: {number}");
}
}
#[test]
fn test_invalid_uk_trn() {
let validator = UkTrnChecksum;
let invalid_numbers = vec![
"112345678", // 9 digits
"11234567890", // 11 digits
"112345678901", // 12 digits
"11234567890123", // 14 digits
"3419210344", // wrong checksum
"K3366215443k", // more than one K
];
for number in invalid_numbers {
assert!(
!validator.is_valid_match(number),
"expected invalid: {number}"
);
}
}
}