use crate::error::{Severity, ValidationError};
use crate::rules::Rule;
pub struct BicRule;
impl Rule for BicRule {
fn id(&self) -> &'static str {
"BIC_CHECK"
}
fn validate(&self, value: &str, path: &str) -> Vec<ValidationError> {
match validate_bic(value) {
Ok(()) => vec![],
Err(msg) => vec![ValidationError::new(
path,
Severity::Error,
"BIC_CHECK",
msg,
)],
}
}
}
fn validate_bic(bic: &str) -> Result<(), String> {
let len = bic.len();
if len != 8 && len != 11 {
return Err(format!(
"BIC must be 8 or 11 characters, got {len}: `{bic}`"
));
}
let bytes = bic.as_bytes();
for (i, &b) in bytes[..4].iter().enumerate() {
if !b.is_ascii_uppercase() {
return Err(format!(
"BIC institution code (chars 1-4) must be uppercase letters; \
char {} is `{}`",
i + 1,
char::from(b)
));
}
}
for (i, &b) in bytes[4..6].iter().enumerate() {
if !b.is_ascii_uppercase() {
return Err(format!(
"BIC country code (chars 5-6) must be uppercase letters; \
char {} is `{}`",
i + 5,
char::from(b)
));
}
}
for (i, &b) in bytes[6..8].iter().enumerate() {
if !b.is_ascii_uppercase() && !b.is_ascii_digit() {
return Err(format!(
"BIC location code (chars 7-8) must be uppercase alphanumeric; \
char {} is `{}`",
i + 7,
char::from(b)
));
}
}
if len == 11 {
for (i, &b) in bytes[8..11].iter().enumerate() {
if !b.is_ascii_uppercase() && !b.is_ascii_digit() {
return Err(format!(
"BIC branch code (chars 9-11) must be uppercase alphanumeric; \
char {} is `{}`",
i + 9,
char::from(b)
));
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rules::Rule;
const VALID_BICS: &[&str] = &[
"AAAAGB2L", "AAAAGB2LXXX", "DEUTDEDB", "DEUTDEDBFRA", "BOFAUS3N", "BOFAUS3NXXX", "CHASUS33", "CHASUS33XXX", ];
const INVALID_BICS: &[&str] = &[
"AAAA", "AAAAGB2LXXXXX", "1AAAGB2L", "AAAA1B2L", "AAAAgb2L", "AAAAGB2l", "", "AAAA GB2L", ];
#[test]
fn valid_bics_pass() {
let rule = BicRule;
for bic in VALID_BICS {
let errors = rule.validate(bic, "/test");
assert!(
errors.is_empty(),
"Expected no errors for valid BIC `{bic}`, got: {errors:?}"
);
}
}
#[test]
fn invalid_bics_fail() {
let rule = BicRule;
for bic in INVALID_BICS {
let errors = rule.validate(bic, "/test");
assert!(
!errors.is_empty(),
"Expected errors for invalid BIC `{bic}`"
);
}
}
#[test]
fn error_has_correct_rule_id_and_path() {
let rule = BicRule;
let errors = rule.validate("INVALID", "/some/path");
assert_eq!(errors[0].rule_id, "BIC_CHECK");
assert_eq!(errors[0].path, "/some/path");
}
#[test]
fn rule_id_is_bic_check() {
assert_eq!(BicRule.id(), "BIC_CHECK");
}
}