Coverage for src / lexigram / ai / relay / gateway / errors.py: 91%
22 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-08 23:08 +0800
« prev ^ index » next coverage.py v7.13.5, created at 2026-08-08 23:08 +0800
1"""Gateway error mapping helpers for the relay gateway service.
3Converts engine-level ``RelayError`` values into gateway-level
4``RelayGatewayError`` values and re-attaches request ids to registry and
5upstream errors that were produced without one.
6"""
8from __future__ import annotations
10from lexigram.contracts.ai.exceptions import RelayError, RelayErrorCode
11from lexigram.contracts.ai.governance import RelayBillingError
12from lexigram.contracts.ai.relay.gateway import (
13 RelayGatewayError,
14 RelayGatewayErrorCode,
15)
17__all__ = [
18 "auth_denied",
19 "billing_error_to_gateway",
20 "conversion_error_to_gateway",
21 "with_request_id",
22]
25def conversion_error_to_gateway(
26 error: RelayError, request_id: str
27) -> RelayGatewayError:
28 """Map a converter engine error to a gateway error.
30 Args:
31 error: The ``RelayError`` returned by the converter engine. Its
32 message is a domain message and is safe to propagate.
33 request_id: Caller-supplied request id stamped on the result.
35 Returns:
36 A ``RelayGatewayError`` classified from the engine error code:
37 malformed/unsupported payloads map to ``INVALID_REQUEST`` (400);
38 every other engine failure maps to ``CONVERSION_FAILED`` (500).
39 The error is never retryable.
40 """
41 if error.code in {
42 RelayErrorCode.MALFORMED_PAYLOAD.value,
43 RelayErrorCode.UNSUPPORTED_FORMAT.value,
44 RelayErrorCode.UNSUPPORTED_FEATURE.value,
45 }:
46 code = RelayGatewayErrorCode.INVALID_REQUEST
47 status_code = 400
48 else:
49 code = RelayGatewayErrorCode.CONVERSION_FAILED
50 status_code = 500
51 return RelayGatewayError(
52 code=code,
53 message=error.message,
54 status_code=status_code,
55 request_id=request_id,
56 retryable=False,
57 )
60def auth_denied(request_id: str) -> RelayGatewayError:
61 """Build the gateway error for an authorization denial.
63 Args:
64 request_id: Caller-supplied request id stamped on the result.
66 Returns:
67 An ``AUTH_DENIED`` gateway error (403, never retryable).
68 """
69 return RelayGatewayError(
70 code=RelayGatewayErrorCode.AUTH_DENIED,
71 message="authorization denied",
72 status_code=403,
73 request_id=request_id,
74 retryable=False,
75 )
78def billing_error_to_gateway(
79 error: RelayBillingError, request_id: str
80) -> RelayGatewayError:
81 """Map a billing admission error to a gateway error.
83 Args:
84 error: The ``RelayBillingError`` returned by the billing
85 pipeline. Its message is redaction-safe and propagated.
86 request_id: Caller-supplied request id stamped on the result.
88 Returns:
89 A ``RelayGatewayError`` classified from the billing error code:
90 quota denials map to ``QUOTA_EXCEEDED`` (429, retryable); every
91 other billing failure maps to ``BILLING_FAILED`` (500, never
92 retryable).
93 """
94 if error.code == "quota_exhausted":
95 return RelayGatewayError(
96 code=RelayGatewayErrorCode.QUOTA_EXCEEDED,
97 message=error.message,
98 status_code=429,
99 request_id=request_id,
100 retryable=True,
101 )
102 return RelayGatewayError(
103 code=RelayGatewayErrorCode.BILLING_FAILED,
104 message=error.message,
105 status_code=500,
106 request_id=request_id,
107 retryable=False,
108 )
111def with_request_id(error: RelayGatewayError, request_id: str) -> RelayGatewayError:
112 """Return *error* with the given request id when it has none.
114 Errors produced by the channel registry carry an empty request id;
115 this attaches the caller's id without disturbing errors that already
116 carry one (the adapter always stamps its own).
118 Args:
119 error: The gateway error to normalize.
120 request_id: Request id to attach when *error* carries none.
122 Returns:
123 *error* unchanged when its request id is non-empty, otherwise a
124 new ``RelayGatewayError`` copying code, message, status code,
125 and retryability with the given request id.
126 """
127 if error.request_id:
128 return error
129 return RelayGatewayError(
130 code=error.code,
131 message=error.message,
132 status_code=error.status_code,
133 request_id=request_id,
134 retryable=error.retryable,
135 )