Skip to content

Error taxonomy

Every error raised by the domain, services, and adapters is a subclass of StructuredAddressError. Each carries a stable, machine-readable code and an optional safe context dictionary, so a wrapping transport (MCP / REST / LSP) can catch at the boundary and return an {"error": {...}} payload rather than leaking a traceback.

class StructuredAddressError(Exception):
    code: str = "SAF_ERROR"

    def __init__(self, message: str, *, context: dict | None = None) -> None:
        ...
        self.message = message
        self.context = context or {}

Distinguish these typed errors — invalid input, an unknown policy, an unresolvable finding — from a Pydantic ValidationError, which is what CanonicalAddress raises when a field invariant is violated (an over-length element, more than seven AdrLines, a non-two-letter country).

The hierarchy

StructuredAddressError                (SAF_ERROR)
├── InputError                        (SAF_INPUT)
│   ├── MalformedXMLError             (SAF_MALFORMED_XML)
│   ├── UnsupportedMessageTypeError   (SAF_UNSUPPORTED_MESSAGE)
│   ├── AddressNotFoundError          (SAF_ADDRESS_NOT_FOUND)
│   └── InvalidAddressError           (SAF_INVALID_ADDRESS)
├── PolicyError                       (SAF_POLICY)
│   ├── UnknownPolicyError            (SAF_UNKNOWN_POLICY)
│   └── PolicyConflictError           (SAF_POLICY_CONFLICT)
├── RemediationError                  (SAF_REMEDIATION)
│   ├── UnresolvableFindingError      (SAF_UNRESOLVABLE)
│   └── PatchApplicationError         (SAF_PATCH_FAILED)
└── EntitlementError                  (SAF_ENTITLEMENT)
    ├── PackNotLicensedError          (SAF_PACK_NOT_LICENSED)
    └── PackIntegrityError            (SAF_PACK_INTEGRITY)

Codes

Code Exception Meaning
SAF_ERROR StructuredAddressError Base class for every error the package raises
SAF_INPUT InputError The caller's input was malformed or unsupported
SAF_MALFORMED_XML MalformedXMLError The document could not be parsed as XML
SAF_UNSUPPORTED_MESSAGE UnsupportedMessageTypeError Not a recognised ISO 20022 message type
SAF_ADDRESS_NOT_FOUND AddressNotFoundError No postal address at the requested location
SAF_INVALID_ADDRESS InvalidAddressError The address violates a canonical-model invariant
SAF_POLICY PolicyError Base for policy-resolution failures
SAF_UNKNOWN_POLICY UnknownPolicyError The requested policy id is not registered
SAF_POLICY_CONFLICT PolicyConflictError Two policies claim the same id in the registry
SAF_REMEDIATION RemediationError Base for remediation failures
SAF_UNRESOLVABLE UnresolvableFindingError No heuristic can resolve a finding into a patch
SAF_PATCH_FAILED PatchApplicationError A patch could not be applied to the target document
SAF_ENTITLEMENT EntitlementError Base for premium-pack entitlement failures
SAF_PACK_NOT_LICENSED PackNotLicensedError The caller is not entitled to the requested pack
SAF_PACK_INTEGRITY PackIntegrityError A pack failed its signature or version integrity check

Handling pattern

Catch the base class at the transport boundary and serialise:

from structured_address_fix.errors import StructuredAddressError

try:
    report = assess(message_xml, policy=policy_id)
except StructuredAddressError as exc:
    return {"error": {"code": exc.code, "message": exc.message,
                      "context": exc.context}}

The context dictionary is deliberately safe to serialise — it carries structured, non-sensitive detail (the offending policy id, the location that had no address) rather than raw internal state.