(status: int, code: str)
| 164 | |
| 165 | |
| 166 | def _resolve(status: int, code: str) -> "tuple[type[E2AError], bool]": |
| 167 | # 1. Idempotency codes are the most specific — they win over everything. |
| 168 | if code in _IDEMPOTENCY_CODES: |
| 169 | return E2AIdempotencyError, (code == _IDEMPOTENCY_RETRYABLE) |
| 170 | # 2. Code-first: a known stable code selects the class regardless of status. |
| 171 | if code: |
| 172 | if code in _CODE_MAP: |
| 173 | return _CODE_MAP[code] |
| 174 | # Conventional suffixes the server uses across many resources. |
| 175 | if code.endswith("_not_found"): |
| 176 | return E2ANotFoundError, False |
| 177 | if code.endswith("_exists"): |
| 178 | return E2AConflictError, False |
| 179 | # 3. Fall back to the HTTP status bucket for unknown/empty codes. |
| 180 | by_status: "dict[int, tuple[type[E2AError], bool]]" = { |
| 181 | # Every 400 is a client/validation error — maps the many 400 codes |
| 182 | # (confirmation_required, too_many_recipients, invalid_domain, …) to the |
| 183 | # validation family instead of degrading to the bare base error. |
| 184 | 400: (E2AValidationError, False), |
| 185 | 401: (E2AAuthError, False), |
| 186 | 403: (E2APermissionError, False), |
| 187 | 404: (E2ANotFoundError, False), |
| 188 | 409: (E2AConflictError, False), |
| 189 | 422: (E2AValidationError, False), |
| 190 | 429: (E2ARateLimitError, True), |
| 191 | } |
| 192 | if status in by_status: |
| 193 | return by_status[status] |
| 194 | if is_retryable_status(status): |
| 195 | return E2AServerError, True |
| 196 | return E2AError, False |
| 197 | |
| 198 | |
| 199 | def _header_get(headers: Optional[Mapping[str, str]], name: str) -> Optional[str]: |
no test coverage detected