ai_lib_rust/error_code.rs
1//! V2 标准错误码:定义 13 个规范错误码及其重试/回退语义。
2//!
3//! AI-Protocol V2 standard error codes.
4//!
5//! This module defines the canonical error code system from the AI-Protocol V2
6//! specification (see `ai-protocol/schemas/v2/error-codes.yaml`). Each runtime
7//! implements these codes for consistent error handling across providers.
8//!
9//! ## Error Code Categories
10//!
11//! | Prefix | Category | Description |
12//! |--------|-------------|--------------------------------|
13//! | E1xxx | client | Request-side errors |
14//! | E2xxx | rate | Rate limit and quota errors |
15//! | E3xxx | server | Provider-side errors |
16//! | E4xxx | operational | Lifecycle and state conflicts |
17//! | E9xxx | unknown | Catch-all / unclassified |
18//!
19//! ## Example
20//!
21//! ```rust
22//! use ai_lib_rust::error_code::StandardErrorCode;
23//!
24//! let code = StandardErrorCode::from_error_class("rate_limited");
25//! assert_eq!(code.code(), "E2001");
26//! assert!(code.retryable());
27//! assert_eq!(code.category(), "rate");
28//! ```
29
30use std::fmt;
31
32/// Standard AI-Protocol V2 error code.
33///
34/// Each variant corresponds to a canonical error from the specification,
35/// with associated metadata: code string, name, retryable, and fallbackable flags.
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
37pub enum StandardErrorCode {
38 /// E1001: Malformed request, invalid parameters, or missing required fields
39 InvalidRequest,
40 /// E1002: Invalid, expired, or missing API key
41 Authentication,
42 /// E1003: Valid credentials but insufficient permissions
43 PermissionDenied,
44 /// E1004: Requested model, endpoint, or resource does not exist
45 NotFound,
46 /// E1005: Input exceeds context window or API payload size limit
47 RequestTooLarge,
48 /// E2001: Request rate limit exceeded
49 RateLimited,
50 /// E2002: Account usage quota or billing limit reached
51 QuotaExhausted,
52 /// E3001: Internal server error on provider side
53 ServerError,
54 /// E3002: Provider service temporarily overloaded
55 Overloaded,
56 /// E3003: Request timed out before response received
57 Timeout,
58 /// E4001: State conflict (e.g., concurrent modification)
59 Conflict,
60 /// E4002: Request was cancelled by the client
61 Cancelled,
62 /// E9999: Error could not be classified
63 Unknown,
64}
65
66impl StandardErrorCode {
67 /// Returns the canonical code string (e.g., `"E1001"`).
68 #[inline]
69 pub fn code(&self) -> &'static str {
70 match self {
71 Self::InvalidRequest => "E1001",
72 Self::Authentication => "E1002",
73 Self::PermissionDenied => "E1003",
74 Self::NotFound => "E1004",
75 Self::RequestTooLarge => "E1005",
76 Self::RateLimited => "E2001",
77 Self::QuotaExhausted => "E2002",
78 Self::ServerError => "E3001",
79 Self::Overloaded => "E3002",
80 Self::Timeout => "E3003",
81 Self::Conflict => "E4001",
82 Self::Cancelled => "E4002",
83 Self::Unknown => "E9999",
84 }
85 }
86
87 /// Returns the standard name (e.g., `"invalid_request"`).
88 #[inline]
89 pub fn name(&self) -> &'static str {
90 match self {
91 Self::InvalidRequest => "invalid_request",
92 Self::Authentication => "authentication",
93 Self::PermissionDenied => "permission_denied",
94 Self::NotFound => "not_found",
95 Self::RequestTooLarge => "request_too_large",
96 Self::RateLimited => "rate_limited",
97 Self::QuotaExhausted => "quota_exhausted",
98 Self::ServerError => "server_error",
99 Self::Overloaded => "overloaded",
100 Self::Timeout => "timeout",
101 Self::Conflict => "conflict",
102 Self::Cancelled => "cancelled",
103 Self::Unknown => "unknown",
104 }
105 }
106
107 /// Returns whether this error is retryable by default.
108 #[inline]
109 pub fn retryable(&self) -> bool {
110 matches!(
111 self,
112 Self::RateLimited
113 | Self::ServerError
114 | Self::Overloaded
115 | Self::Timeout
116 | Self::Conflict
117 )
118 }
119
120 /// Returns whether this error should trigger a fallback to another provider.
121 #[inline]
122 pub fn fallbackable(&self) -> bool {
123 matches!(
124 self,
125 Self::Authentication
126 | Self::RateLimited
127 | Self::QuotaExhausted
128 | Self::ServerError
129 | Self::Overloaded
130 | Self::Timeout
131 )
132 }
133
134 /// Returns the category: `"client"`, `"rate"`, `"server"`, `"operational"`, or `"unknown"`.
135 #[inline]
136 pub fn category(&self) -> &'static str {
137 match self {
138 Self::InvalidRequest
139 | Self::Authentication
140 | Self::PermissionDenied
141 | Self::NotFound
142 | Self::RequestTooLarge => "client",
143 Self::RateLimited | Self::QuotaExhausted => "rate",
144 Self::ServerError | Self::Overloaded | Self::Timeout => "server",
145 Self::Conflict | Self::Cancelled => "operational",
146 Self::Unknown => "unknown",
147 }
148 }
149
150 /// Maps a provider error code/type string to the corresponding `StandardErrorCode`.
151 ///
152 /// Supports both standard names (e.g., `"invalid_request"`) and provider-specific
153 /// aliases such as `"invalid_api_key"`, `"context_length_exceeded"`, `"overloaded_error"`.
154 pub fn from_provider_code(provider_code: &str) -> Option<Self> {
155 let code = match provider_code {
156 "invalid_request" | "invalid_request_error" => Self::InvalidRequest,
157 "authentication" | "authorized_error" | "invalid_api_key" | "authentication_error" => {
158 Self::Authentication
159 }
160 "permission_denied" | "permission_error" => Self::PermissionDenied,
161 "not_found" | "model_not_found" => Self::NotFound,
162 "request_too_large" | "context_length_exceeded" => Self::RequestTooLarge,
163 "rate_limited" | "rate_limit_exceeded" => Self::RateLimited,
164 "quota_exhausted" | "insufficient_quota" => Self::QuotaExhausted,
165 "server_error" => Self::ServerError,
166 "overloaded" | "overloaded_error" => Self::Overloaded,
167 "timeout" => Self::Timeout,
168 "conflict" => Self::Conflict,
169 "cancelled" => Self::Cancelled,
170 _ => return None,
171 };
172 Some(code)
173 }
174
175 /// Maps an error class name string to the corresponding `StandardErrorCode`.
176 ///
177 /// The class name should match the standard names (e.g., `"invalid_request"`).
178 /// Aliases such as `"authorized_error"` (→ authentication) are supported.
179 /// Unknown class names map to `StandardErrorCode::Unknown`.
180 pub fn from_error_class(error_class: &str) -> Self {
181 Self::from_provider_code(error_class).unwrap_or(Self::Unknown)
182 }
183
184 /// Maps an HTTP status code to the most likely `StandardErrorCode`.
185 ///
186 /// Multiple status codes can map to the same error (e.g., 429 → rate_limited).
187 /// Status codes without a standard mapping return `StandardErrorCode::Unknown`.
188 pub fn from_http_status(status: u16) -> Self {
189 match status {
190 400 => Self::InvalidRequest,
191 401 => Self::Authentication,
192 403 => Self::PermissionDenied,
193 404 => Self::NotFound,
194 408 => Self::Timeout,
195 409 => Self::Conflict,
196 413 => Self::RequestTooLarge,
197 429 => Self::RateLimited, // Could also be QuotaExhausted; default to rate_limited
198 500 => Self::ServerError,
199 503 => Self::Overloaded,
200 504 => Self::Timeout,
201 529 => Self::Overloaded, // Anthropic overloaded; non-standard but commonly used
202 _ => Self::Unknown,
203 }
204 }
205}
206
207impl fmt::Display for StandardErrorCode {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 write!(f, "{}", self.code())
210 }
211}