from .validation_bic_types import BicCheck def _invalid(reason: str) -> BicCheck: return BicCheck( valid=False, normalised=None, institution=None, country=None, location=None, branch=None, reason=reason ) def _is_upper(ch: str) -> bool: return "A" <= ch <= "Z" def validate_bic(value: str) -> BicCheck: """Check the format of a BIC and split it into institution, country, location and branch. Well formed is not registered: only SWIFT's directory knows whether the code is assigned. Validators answer rather than raise. """ if not isinstance(value, str): return _invalid("empty") # Folded by hand: str.upper() is Unicode-aware and Rust's ASCII fold is not. bic = "".join(chr(ord(ch) - 32) if "a" <= ch <= "z" else ch for ch in value if ch != " ") if not bic: return _invalid("empty") if any(not (_is_upper(ch) or "0" <= ch <= "9") for ch in bic): return _invalid("bad-character") if len(bic) != 8 and len(bic) != 11: return _invalid("bad-length") if not _is_upper(bic[4]) or not _is_upper(bic[5]): return _invalid("bad-country") return BicCheck( valid=True, normalised=bic, institution=bic[0:4], country=bic[4:6], location=bic[6:8], # An 8-character BIC is the primary office, which the 11-character form spells XXX. branch=bic[8:11] if len(bic) == 11 else "XXX", reason=None, )