Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-relay-gateway/src/lexigram/ai/relay/gateway/errors.py: 42%

24 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 07:19 +0800

1"""Gateway error mapping helpers for the relay gateway service. 

2 

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""" 

7 

8from __future__ import annotations 

9 

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) 

16 

17__all__ = [ 

18 "auth_denied", 

19 "billing_error_to_gateway", 

20 "conversion_error_to_gateway", 

21 "unexpected_error", 

22 "with_request_id", 

23] 

24 

25 

26def conversion_error_to_gateway( 

27 error: RelayError, request_id: str 

28) -> RelayGatewayError: 

29 """Map a converter engine error to a gateway error. 

30 

31 Args: 

32 error: The ``RelayError`` returned by the converter engine. Its 

33 message is a domain message and is safe to propagate. 

34 request_id: Caller-supplied request id stamped on the result. 

35 

36 Returns: 

37 A ``RelayGatewayError`` classified from the engine error code: 

38 malformed/unsupported payloads map to ``INVALID_REQUEST`` (400); 

39 every other engine failure maps to ``CONVERSION_FAILED`` (500). 

40 The error is never retryable. 

41 """ 

42 if error.code in { 

43 RelayErrorCode.MALFORMED_PAYLOAD.value, 

44 RelayErrorCode.UNSUPPORTED_FORMAT.value, 

45 RelayErrorCode.UNSUPPORTED_FEATURE.value, 

46 }: 

47 code = RelayGatewayErrorCode.INVALID_REQUEST 

48 status_code = 400 

49 else: 

50 code = RelayGatewayErrorCode.CONVERSION_FAILED 

51 status_code = 500 

52 return RelayGatewayError( 

53 code=code, 

54 message=error.message, 

55 status_code=status_code, 

56 request_id=request_id, 

57 retryable=False, 

58 ) 

59 

60 

61def auth_denied(request_id: str) -> RelayGatewayError: 

62 """Build the gateway error for an authorization denial. 

63 

64 Args: 

65 request_id: Caller-supplied request id stamped on the result. 

66 

67 Returns: 

68 An ``AUTH_DENIED`` gateway error (403, never retryable). 

69 """ 

70 return RelayGatewayError( 

71 code=RelayGatewayErrorCode.AUTH_DENIED, 

72 message="authorization denied", 

73 status_code=403, 

74 request_id=request_id, 

75 retryable=False, 

76 ) 

77 

78 

79def billing_error_to_gateway( 

80 error: RelayBillingError, request_id: str 

81) -> RelayGatewayError: 

82 """Map a billing admission error to a gateway error. 

83 

84 Args: 

85 error: The ``RelayBillingError`` returned by the billing 

86 pipeline. Its message is redaction-safe and propagated. 

87 request_id: Caller-supplied request id stamped on the result. 

88 

89 Returns: 

90 A ``RelayGatewayError`` classified from the billing error code: 

91 quota denials map to ``QUOTA_EXCEEDED`` (429, retryable); every 

92 other billing failure maps to ``BILLING_FAILED`` (500, never 

93 retryable). 

94 """ 

95 if error.code == "quota_exhausted": 

96 return RelayGatewayError( 

97 code=RelayGatewayErrorCode.QUOTA_EXCEEDED, 

98 message=error.message, 

99 status_code=429, 

100 request_id=request_id, 

101 retryable=True, 

102 ) 

103 return RelayGatewayError( 

104 code=RelayGatewayErrorCode.BILLING_FAILED, 

105 message=error.message, 

106 status_code=500, 

107 request_id=request_id, 

108 retryable=False, 

109 ) 

110 

111 

112def with_request_id(error: RelayGatewayError, request_id: str) -> RelayGatewayError: 

113 """Return *error* with the given request id when it has none. 

114 

115 Errors produced by the channel registry carry an empty request id; 

116 this attaches the caller's id without disturbing errors that already 

117 carry one (the adapter always stamps its own). 

118 

119 Args: 

120 error: The gateway error to normalize. 

121 request_id: Request id to attach when *error* carries none. 

122 

123 Returns: 

124 *error* unchanged when its request id is non-empty, otherwise a 

125 new ``RelayGatewayError`` copying code, message, status code, 

126 and retryability with the given request id. 

127 """ 

128 if error.request_id: 

129 return error 

130 return RelayGatewayError( 

131 code=error.code, 

132 message=error.message, 

133 status_code=error.status_code, 

134 request_id=request_id, 

135 retryable=error.retryable, 

136 ) 

137 

138 

139def unexpected_error(request_id: str) -> RelayGatewayError: 

140 """Build the generic error for unexpected dependency failures. 

141 

142 Args: 

143 request_id: The gateway request identifier. 

144 

145 Returns: 

146 A non-retryable ``CONVERSION_FAILED`` (500) gateway error. 

147 """ 

148 return RelayGatewayError( 

149 code=RelayGatewayErrorCode.CONVERSION_FAILED, 

150 message="Unexpected relay gateway failure", 

151 status_code=500, 

152 request_id=request_id, 

153 retryable=False, 

154 )