1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::fmt;
/// The primary error type for the OpenModex SDK.
#[derive(Debug, thiserror::Error)]
pub enum Error {
/// An error returned by the OpenModex API.
#[error("{0}")]
Api(#[from] ApiError),
/// An HTTP transport error.
#[error("openmodex: request failed: {0}")]
Http(#[from] reqwest::Error),
/// JSON serialization or deserialization error.
#[error("openmodex: json error: {0}")]
Json(#[from] serde_json::Error),
/// The API key was not provided.
#[error("openmodex: API key is required (pass to OpenModex::new or set OPENMODEX_API_KEY)")]
MissingApiKey,
/// All fallback models failed.
#[error("openmodex: all fallback models failed")]
AllFallbacksFailed,
/// An error that occurred during SSE stream parsing.
#[error("openmodex: stream error: {0}")]
Stream(String),
/// Request timed out.
#[error("openmodex: request timed out")]
Timeout,
}
/// An error returned by the OpenModex API with status code and structured detail.
#[derive(Debug, Clone)]
pub struct ApiError {
/// HTTP status code.
pub status_code: u16,
/// Machine-readable error code from the API.
pub code: Option<String>,
/// Error type classification from the API.
pub error_type: Option<String>,
/// Human-readable error message.
pub message: String,
}
impl fmt::Display for ApiError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match &self.code {
Some(code) => write!(f, "openmodex: {} {}: {}", self.status_code, code, self.message),
None => write!(f, "openmodex: {}: {}", self.status_code, self.message),
}
}
}
impl std::error::Error for ApiError {}
impl ApiError {
/// Returns `true` if this is a rate limit (429) error.
pub fn is_rate_limited(&self) -> bool {
self.status_code == 429
}
/// Returns `true` if this is an authentication (401) error.
pub fn is_auth_error(&self) -> bool {
self.status_code == 401
}
/// Returns `true` if this is a not-found (404) error.
pub fn is_not_found(&self) -> bool {
self.status_code == 404
}
/// Returns `true` if this is a server (5xx) error.
pub fn is_server_error(&self) -> bool {
self.status_code >= 500
}
}
impl Error {
/// Returns the inner [`ApiError`] if this is an API error variant.
pub fn as_api_error(&self) -> Option<&ApiError> {
match self {
Error::Api(e) => Some(e),
_ => None,
}
}
/// Returns `true` if this is a rate limit (429) error.
pub fn is_rate_limited(&self) -> bool {
self.as_api_error().map_or(false, |e| e.is_rate_limited())
}
/// Returns `true` if this is an authentication (401) error.
pub fn is_auth_error(&self) -> bool {
self.as_api_error().map_or(false, |e| e.is_auth_error())
}
/// Returns `true` if this is a not-found (404) error.
pub fn is_not_found(&self) -> bool {
self.as_api_error().map_or(false, |e| e.is_not_found())
}
/// Returns `true` if this is a server error (5xx).
pub fn is_server_error(&self) -> bool {
self.as_api_error().map_or(false, |e| e.is_server_error())
}
/// Returns `true` if the error is retryable (429, 5xx, or network error).
pub fn should_retry(&self) -> bool {
match self {
Error::Api(e) => {
e.status_code == 429
|| e.status_code == 500
|| e.status_code == 502
|| e.status_code == 503
|| e.status_code == 504
}
Error::Http(_) | Error::Timeout => true,
_ => false,
}
}
/// Returns `true` if the error should trigger a fallback to the next model.
pub(crate) fn should_fallback(&self) -> bool {
match self {
Error::Api(e) => e.status_code >= 500 || e.status_code == 429,
Error::Http(_) | Error::Timeout => true,
_ => false,
}
}
}