Skip to main content

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}