Functional Weave
Code in Rust

validation.uk-company-number@2.0.1

impl/python.py

2,639 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from .validation_uk_company_number_data import COMPANY_PREFIXES  ← this capability’s own data, compiled from data/company-prefixes.json into the same file by fune build
from .validation_uk_company_number_types import UkCompanyNumber

#: Every company number is eight characters: eight digits, or a two-character
#: prefix and six digits.
_LENGTH = 8


def _invalid(reason: str) -> UkCompanyNumber:
    return UkCompanyNumber(valid=False, normalised=None, prefix=None, jurisdiction=None, company_type=None, reason=reason)


def _is_letter(ch: str) -> bool:
    return "A" <= ch <= "Z"


def _is_digit(ch: str) -> bool:
    return "0" <= ch <= "9"


def validate_uk_company_number(value: str) -> UkCompanyNumber:
    """Check a Companies House company number's shape and prefix.

    There is no check digit, so a pass means the number is shaped like one
    Companies House issues under a prefix it uses, not that the company exists:
    that is a lookup on the register. Checks run in a fixed order (characters,
    shape, length, prefix) so every language gives the same first reason.
    """
    if not isinstance(value, str):
        return _invalid("empty")
    compact = ""
    for ch in value:
        if ch == " ":
            continue
        # ASCII-only upper-casing, so every language folds identically.
        if "a" <= ch <= "z":
            ch = chr(ord(ch) - 32)
        if not _is_letter(ch) and not _is_digit(ch):
            return _invalid("bad-character")
        compact += ch
    if compact == "":
        return _invalid("empty")

    # R0 (pre-partition Northern Ireland) is the one prefix containing a digit.
    if compact.startswith("R0") or (len(compact) >= 2 and _is_letter(compact[0]) and _is_letter(compact[1])):
        prefix = compact[0:2]
    elif _is_letter(compact[0]):
        return _invalid("bad-format")
    else:
        prefix = ""
    digits = compact[len(prefix):]
    if not all(_is_digit(ch) for ch in digits):
        return _invalid("bad-format")
    width = _LENGTH - len(prefix)
    if len(digits) == 0 or len(digits) > width:
        return _invalid("bad-length")
    # Leading zeros are part of the number and are often dropped when typed,
    # so they are put back; a number of nothing but zeros is no company.
    if all(ch == "0" for ch in digits):
        return _invalid("bad-format")

    for row in COMPANY_PREFIXES:
        if row.prefix == prefix:
            return UkCompanyNumber(
                valid=True,
                normalised=prefix + digits.rjust(width, "0"),
                prefix=prefix,
                jurisdiction=row.jurisdiction,
                company_type=row.company_type,
                reason=None,
            )
    return _invalid("unknown-prefix")