mx20022_validate/rules/
bic.rs1use crate::error::{Severity, ValidationError};
15use crate::rules::Rule;
16
17pub struct BicRule;
36
37impl Rule for BicRule {
38 fn id(&self) -> &'static str {
39 "BIC_CHECK"
40 }
41
42 fn validate(&self, value: &str, path: &str) -> Vec<ValidationError> {
43 match validate_bic(value) {
44 Ok(()) => vec![],
45 Err(msg) => vec![ValidationError::new(
46 path,
47 Severity::Error,
48 "BIC_CHECK",
49 msg,
50 )],
51 }
52 }
53}
54
55fn validate_bic(bic: &str) -> Result<(), String> {
56 let len = bic.len();
57 if len != 8 && len != 11 {
58 return Err(format!(
59 "BIC must be 8 or 11 characters, got {len}: `{bic}`"
60 ));
61 }
62
63 let bytes = bic.as_bytes();
64
65 for (i, &b) in bytes[..4].iter().enumerate() {
67 if !b.is_ascii_uppercase() {
68 return Err(format!(
69 "BIC institution code (chars 1-4) must be uppercase letters; \
70 char {} is `{}`",
71 i + 1,
72 char::from(b)
73 ));
74 }
75 }
76
77 for (i, &b) in bytes[4..6].iter().enumerate() {
79 if !b.is_ascii_uppercase() {
80 return Err(format!(
81 "BIC country code (chars 5-6) must be uppercase letters; \
82 char {} is `{}`",
83 i + 5,
84 char::from(b)
85 ));
86 }
87 }
88
89 for (i, &b) in bytes[6..8].iter().enumerate() {
91 if !b.is_ascii_uppercase() && !b.is_ascii_digit() {
92 return Err(format!(
93 "BIC location code (chars 7-8) must be uppercase alphanumeric; \
94 char {} is `{}`",
95 i + 7,
96 char::from(b)
97 ));
98 }
99 }
100
101 if len == 11 {
103 for (i, &b) in bytes[8..11].iter().enumerate() {
104 if !b.is_ascii_uppercase() && !b.is_ascii_digit() {
105 return Err(format!(
106 "BIC branch code (chars 9-11) must be uppercase alphanumeric; \
107 char {} is `{}`",
108 i + 9,
109 char::from(b)
110 ));
111 }
112 }
113 }
114
115 Ok(())
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121 use crate::rules::Rule;
122
123 const VALID_BICS: &[&str] = &[
124 "AAAAGB2L", "AAAAGB2LXXX", "DEUTDEDB", "DEUTDEDBFRA", "BOFAUS3N", "BOFAUS3NXXX", "CHASUS33", "CHASUS33XXX", ];
133
134 const INVALID_BICS: &[&str] = &[
135 "AAAA", "AAAAGB2LXXXXX", "1AAAGB2L", "AAAA1B2L", "AAAAgb2L", "AAAAGB2l", "", "AAAA GB2L", ];
144
145 #[test]
146 fn valid_bics_pass() {
147 let rule = BicRule;
148 for bic in VALID_BICS {
149 let errors = rule.validate(bic, "/test");
150 assert!(
151 errors.is_empty(),
152 "Expected no errors for valid BIC `{bic}`, got: {errors:?}"
153 );
154 }
155 }
156
157 #[test]
158 fn invalid_bics_fail() {
159 let rule = BicRule;
160 for bic in INVALID_BICS {
161 let errors = rule.validate(bic, "/test");
162 assert!(
163 !errors.is_empty(),
164 "Expected errors for invalid BIC `{bic}`"
165 );
166 }
167 }
168
169 #[test]
170 fn error_has_correct_rule_id_and_path() {
171 let rule = BicRule;
172 let errors = rule.validate("INVALID", "/some/path");
173 assert_eq!(errors[0].rule_id, "BIC_CHECK");
174 assert_eq!(errors[0].path, "/some/path");
175 }
176
177 #[test]
178 fn rule_id_is_bic_check() {
179 assert_eq!(BicRule.id(), "BIC_CHECK");
180 }
181}