orchestrata_ultimate.gateway.error_taxonomy
Reference › orchestrata_ultimate › gateway › error_taxonomy
error_taxonomy defines the error schema of SPEC-098 FR-006 and FR-007 for the gateway's JSON-RPC errors; server is its importer. ErrorCategory is a string enum with six values that say what kind of failure occurred: AUTH (not authenticated), AUTHZ (authenticated but not permitted), VAL (malformed or invalid request), RES (resource missing), RATE (throttled) and SYS (server failure). The category complements the numeric JSON-RPC code, which names the protocol rule that was broken. category_for maps a code to its default category and returns SYS for any code outside the reserved table. error_data builds the error.data member: a category, any members listed as top-level lifted out of the details, and a details object that is always present, even if empty, so clients never test for the key. See exceptions and gateway.
At a glance
| Source | src/orchestrata_ultimate/gateway/error_taxonomy.py |
| Lines of code | 106 |
| Classes | 1 |
| Functions | 2 |
| Imports (internal) | none |
| Imported by | orchestrata_ultimate.gateway.server |
| Tests that import it | tests/gateway/test_spec098_api_standards.py, tests/generated_gateway/test_error_taxonomy_generated.py |
Class diagram
Dependency diagram
API
Constants
| Name | Type | Value |
|---|---|---|
_DEFAULT_CATEGORY |
dict[int, ErrorCategory] |
{JSONRPC_PARSE_ERROR: ErrorCategory.VAL, JSONRPC_INVALID_REQUEST: ErrorCategory… |
_TOP_LEVEL_MEMBERS |
frozenset[str] |
frozenset({'correlation_id'}) |
Classes
ErrorCategory (enum)
Bases: StrEnum · Line 32
The SPEC-098 FR-007 categories, verbatim.
| Member | Value |
|---|---|
AUTH |
'AUTH' |
AUTHZ |
'AUTHZ' |
VAL |
'VAL' |
RES |
'RES' |
RATE |
'RATE' |
SYS |
'SYS' |
Functions
| Function | Signature | Summary |
|---|---|---|
category_for |
category_for(code: int) -> ErrorCategory |
Category for a JSON-RPC code. |
error_data |
error_data(code: int, *, category: ErrorCategory | None = None, details: dict[str, Any] | None = None) -> dict[str, Any] |
Build the JSON-RPC error.data member SPEC-098 FR-006/007 require. |
Source
"""SPEC-098 FR-006/FR-007 — the gateway's error schema and code categories.
SPEC-098 specifies a REST API this gateway does not have: its contracts section
is ``GET/POST /api/v{version}/resources``, while the gateway is JSON-RPC over
WebSocket with three HTTP endpoints. Mohamed's ruling (2026-08-09) applies the
FRs that have a structural home here and marks the REST-only ones N/A in the
spec itself.
FR-006 (every error response carries code, message, details) and FR-007 (codes
are categorized AUTH, AUTHZ, VAL, RES, RATE, SYS) DO have a home: JSON-RPC 2.0
error objects already carry ``code`` and ``message`` and permit a ``data``
member. This module supplies the taxonomy that goes in ``data``, so a client
can branch on the CLASS of failure without pattern-matching prose.
The JSON-RPC numeric codes stay exactly as they are — they are protocol-level
and clients already switch on them. The category is additive.
"""
from __future__ import annotations
from enum import StrEnum
from typing import Any
# JSON-RPC 2.0 reserved codes (https://www.jsonrpc.org/specification §5.1).
JSONRPC_PARSE_ERROR = -32700
JSONRPC_INVALID_REQUEST = -32600
JSONRPC_METHOD_NOT_FOUND = -32601
JSONRPC_INVALID_PARAMS = -32602
JSONRPC_INTERNAL_ERROR = -32603
class ErrorCategory(StrEnum):
"""The SPEC-098 FR-007 categories, verbatim.
A category answers "what KIND of thing went wrong" — which is what a
client needs to decide whether to re-authenticate, fix its request, back
off, or give up. The numeric JSON-RPC code answers "which protocol rule
was broken", which is a different question and often too coarse: an
expired token and a malformed parameter are both ``-32600`` shaped
failures to a naive handler, and a client must treat them differently.
"""
AUTH = "AUTH" # not authenticated — missing/expired/invalid credentials
AUTHZ = "AUTHZ" # authenticated, but not permitted
VAL = "VAL" # the request itself is malformed or fails validation
RES = "RES" # the addressed resource does not exist / is gone
RATE = "RATE" # throttled — retry later
SYS = "SYS" # the server failed; nothing the client did
# Default category for each reserved JSON-RPC code. A caller may override, and
# should whenever it knows better: -32600 covers both "your JSON is not a
# request object" (VAL) and "you are not authenticated" (AUTH) in practice.
_DEFAULT_CATEGORY: dict[int, ErrorCategory] = {
JSONRPC_PARSE_ERROR: ErrorCategory.VAL,
JSONRPC_INVALID_REQUEST: ErrorCategory.VAL,
JSONRPC_METHOD_NOT_FOUND: ErrorCategory.RES,
JSONRPC_INVALID_PARAMS: ErrorCategory.VAL,
JSONRPC_INTERNAL_ERROR: ErrorCategory.SYS,
}
def category_for(code: int) -> ErrorCategory:
"""Category for a JSON-RPC *code*.
Anything outside the reserved range is application-defined, and an
application error the server chose to raise is a server-side condition
from the client's point of view — so SYS, not VAL. A caller that knows
the specific category passes it explicitly.
"""
return _DEFAULT_CATEGORY.get(code, ErrorCategory.SYS)
# Members that stay at the ``data`` level rather than being folded into
# ``details``. ``details`` answers "what specifically was wrong with THIS
# request" — the field that failed validation, the id that was not found.
# Tracing metadata is about the request, not about the failure, which is why
# FR-017 carries the HTTP equivalent as a header and not a body field. Burying
# it would also break the ``error.data.correlation_id`` contract the gateway's
# clients already read.
_TOP_LEVEL_MEMBERS: frozenset[str] = frozenset({"correlation_id"})
def error_data(
code: int,
*,
category: ErrorCategory | None = None,
details: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Build the JSON-RPC ``error.data`` member SPEC-098 FR-006/007 require.
``details`` is always present, as an object, even when empty: a client
that reads ``error.data.details`` must not have to branch on whether the
key exists. ``category`` is always one of the six FR-007 values. Members
named in ``_TOP_LEVEL_MEMBERS`` are lifted out of *details* and kept
beside ``category``.
"""
supplied = dict(details or {})
payload: dict[str, Any] = {
"category": str(category or category_for(code)),
}
for member in _TOP_LEVEL_MEMBERS:
if member in supplied:
payload[member] = supplied.pop(member)
payload["details"] = supplied
return payload