Functional Weave
Code in Python

validation.bic

Check the format of a SWIFT/BIC code (8 or 11 characters) and split it into its parts.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 15 tests, run in TypeScript, Python and Rust.

What it does

Checks that a Business Identifier Code - the "SWIFT code" of a bank - is well formed, and splits it into its four parts. `DEUTDEFF500` is Deutsche Bank (`DEUT`), Germany (`DE`), Frankfurt (`FF`), branch `500`.

FORMAT, as the ISO 20022 payment messages validate it (the 2014 edition of ISO 9362, pattern `[A-Z0-9]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?`):

For example

  • validate_bic(DEUTDEFF) → valid true, normalised DEUTDEFF, institution DEUT, country DE, location FF, branch XXX, reason — an 8-character BIC is the primary office, branch XXX
  • validate_bic(DEUTDEFF500) → valid true, normalised DEUTDEFF500, institution DEUT, country DE, location FF, branch 500, reason — an 11-character BIC with a branch
  • validate_bic(NEDSZAJJXXX) → valid true, normalised NEDSZAJJXXX, institution NEDS, country ZA, location JJ, branch XXX, reason — the explicit primary office keeps its 11 characters

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def validate_bic(value: str) -> BicCheck
valuestringa BIC in any case, optionally spaced
returnsBicCheck

The type it declares, generated into your project

@dataclass(frozen=True)
class BicCheck:
    """The result of checking a BIC. Every string is null when valid is false; reason is null when it is true."""

    valid: bool
    #: upper case, no spaces, 8 or 11 characters as given
    normalised: Optional[str]
    #: the four-character business party prefix, e.g. DEUT
    institution: Optional[str]
    #: the two-letter country code, e.g. DE
    country: Optional[str]
    #: the two-character location code, e.g. FF
    location: Optional[str]
    #: the three-character branch code; XXX (the primary office) for an 8-character BIC
    branch: Optional[str]
    #: empty, bad-character, bad-length or bad-country
    reason: Optional[str]

Your code names it in one line, in the file that uses it

from fune.validation.bic import validate_bic  # validation.bic@^1
impl/python.py · 44 lines · open · raw
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,
    )

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add validation.bic
Download for Python validation.bic-1.0.0-python.fune · 10,619 bytes sha256 8abd4f9a4d5a193c9720538804b0aab6c00f5d23fcfcda6d5293c1b646903f2b

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./validation.bic-1.0.0-python.fune, or fetch it from a terminal with fune pull validation.bic@1.0.0:python.

The whole function, every language, is one file too: validation.bic-1.0.0.fune, 14,777 bytes, sha256 2a573b0bd05ab318b74e3aea6ddc5e1de63a1a838cce8211f58259a5ee40f76b. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before validation.bic

after — your function gets the result and the arguments, and returns the final result.

# fune: after validation.bic

replace — it requires no other capability, so there is no dependency to replace.

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show validation.bic --steps.

# fune: step validation.bic after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
an 8-character BIC is the primary office, branch XXX DEUTDEFF → valid true, normalised DEUTDEFF, institution DEUT, country DE, location FF, branch XXX, reason —
an 11-character BIC with a branch DEUTDEFF500 → valid true, normalised DEUTDEFF500, institution DEUT, country DE, location FF, branch 500, reason —
the explicit primary office keeps its 11 characters NEDSZAJJXXX → valid true, normalised NEDSZAJJXXX, institution NEDS, country ZA, location JJ, branch XXX, reason —
a UK BIC with a digit in the location NWBKGB2L → valid true, normalised NWBKGB2L, institution NWBK, country GB, location 2L, branch XXX, reason —
lower case and spaces are normalised nwbk gb 2l → valid true, normalised NWBKGB2L, institution NWBK, country GB, location 2L, branch XXX, reason —
a lettered branch code DSBACNBXSHA → valid true, normalised DSBACNBXSHA, institution DSBA, country CN, location BX, branch SHA, reason —
digits in the institution prefix are allowed since ISO 9362:2014 1234GB2L → valid true, normalised 1234GB2L, institution 1234, country GB, location 2L, branch XXX, reason —
a digit in the country code is refused NWBK9B2L → valid false, normalised —, institution —, country —, location —, branch —, reason bad-country
seven characters is too short NWBKGB2 → valid false, normalised —, institution —, country —, location —, branch —, reason bad-length
nine characters is neither 8 nor 11 NWBKGB2L1 → valid false, normalised —, institution —, country —, location —, branch —, reason bad-length
Show the other 5 tests
CaseArgumentsExpected
twelve characters, a SWIFTNet terminal address, is not a BIC DEUTDEFFA500 → valid false, normalised —, institution —, country —, location —, branch —, reason bad-length
hyphens are not accepted separators NWBK-GB-2L → valid false, normalised —, institution —, country —, location —, branch —, reason bad-character
an accented letter is a bad character DEUTDÉFF → valid false, normalised —, institution —, country —, location —, branch —, reason bad-character
the empty string → valid false, normalised —, institution —, country —, location —, branch —, reason empty
a non-string is empty, not an exception 12,345,678 → valid false, normalised —, institution —, country —, location —, branch —, reason empty

More from the author

- 4 letters or digits: the business party prefix (institution). Every BIC issued so far uses letters here, but ISO 9362:2014 allows digits, and rejecting them would make this stricter than the payment messages it guards. - 2 letters: the country, an ISO 3166-1 alpha-2 code (plus `XK`, which SWIFT assigned to Kosovo). - 2 letters or digits: the location. A `0` in the second place conventionally marks a test BIC; this capability does not flag it, and it is still a well-formed code. - 3 letters or digits, optional: the branch. An 8-character BIC means the primary office and is the same as the 11-character form ending `XXX`, so `branch` is `XXX` for both. `normalised` keeps the length the caller gave, because some systems require one form or the other.

A WELL-FORMED BIC IS NOT A REGISTERED ONE. Only SWIFT's BIC directory can say that the code is assigned and still active. The country letters are checked to be letters, not looked up in the ISO 3166 list, which would need data and releases of its own; pair this with a country check if that matters.

ACCEPTED INPUT: letters in either case (folded to upper case) and digits, with ASCII spaces ignored anywhere ("DEUT DE FF"). Hyphens and other punctuation are refused.

| reason | meaning | |-----------------|------------------------------------------------------| | `empty` | nothing but spaces, or not a string at all | | `bad-character` | anything other than A-Z, a-z, 0-9 and spaces | | `bad-length` | not 8 or 11 characters | | `bad-country` | characters 5 and 6 are not both letters |

The checks run in that order; the first failure is the reason. Validators answer rather than throw.

Not enforced: SWIFT's operational conventions for particular characters in the location and branch codes, which are directory policy rather than format and are not in the ISO 20022 pattern.

Sources: ISO 9362:2014 / 2022 "Banking - Banking telecommunication messages - Business identifier code (BIC)"; the ISO 20022 BICFIDec2014Identifier and AnyBICDec2014Identifier patterns (https://www.iso20022.org); SWIFT, "Business Identifier Code (BIC)" (https://www.swift.com/standards/data-standards/bic-business-identifier-code).

Files

PathBytes
README.md2,694
impl/python.py1,468
impl/rust.rs2,426
impl/typescript.ts1,529
vectors.json3,393