Skip to main content

openai_rust/
error.rs

1use std::{collections::BTreeMap, error::Error, fmt};
2
3/// Top-level shared runtime error.
4#[derive(Debug)]
5pub struct OpenAIError {
6    /// Error classification.
7    pub kind: ErrorKind,
8    /// Human-readable message.
9    pub message: String,
10    response: Option<Box<ErrorResponseContext>>,
11    source: Option<Box<dyn Error + Send + Sync>>,
12}
13
14impl Clone for OpenAIError {
15    fn clone(&self) -> Self {
16        Self {
17            kind: self.kind.clone(),
18            message: self.message.clone(),
19            response: self.response.clone(),
20            source: None,
21        }
22    }
23}
24
25impl OpenAIError {
26    /// Creates a new error with the given classification.
27    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
28        Self {
29            kind,
30            message: message.into(),
31            response: None,
32            source: None,
33        }
34    }
35
36    /// Attaches response metadata to the error.
37    pub fn with_response_metadata(
38        mut self,
39        status_code: u16,
40        headers: BTreeMap<String, String>,
41        request_id: Option<String>,
42    ) -> Self {
43        let mut response = self
44            .response
45            .take()
46            .unwrap_or_else(|| Box::new(ErrorResponseContext::default()));
47        response.status_code = Some(status_code);
48        response.headers = headers;
49        response.request_id = request_id;
50        self.response = Some(response);
51        self
52    }
53
54    /// Attaches a parsed API error payload.
55    pub fn with_api_error(mut self, api_error: ApiErrorPayload) -> Self {
56        let mut response = self
57            .response
58            .take()
59            .unwrap_or_else(|| Box::new(ErrorResponseContext::default()));
60        response.api_error = Some(api_error);
61        self.response = Some(response);
62        self
63    }
64
65    /// Attaches a source error.
66    pub fn with_source<E>(mut self, source: E) -> Self
67    where
68        E: Error + Send + Sync + 'static,
69    {
70        self.source = Some(Box::new(source));
71        self
72    }
73
74    /// Returns the HTTP status code when available.
75    pub fn status_code(&self) -> Option<u16> {
76        self.response
77            .as_ref()
78            .and_then(|response| response.status_code)
79    }
80
81    /// Returns the surfaced request id when available.
82    pub fn request_id(&self) -> Option<&str> {
83        self.response
84            .as_ref()
85            .and_then(|response| response.request_id.as_deref())
86    }
87
88    /// Returns a lower-cased response header by name.
89    pub fn header(&self, name: &str) -> Option<&str> {
90        self.response
91            .as_ref()
92            .and_then(|response| response.headers.get(&name.to_ascii_lowercase()))
93            .map(String::as_str)
94    }
95
96    /// Returns the parsed API error payload when available.
97    pub fn api_error(&self) -> Option<&ApiErrorPayload> {
98        self.response
99            .as_ref()
100            .and_then(|response| response.api_error.as_ref())
101    }
102
103    /// Returns the underlying source error when available.
104    pub fn source(&self) -> Option<&(dyn Error + 'static)> {
105        self.source
106            .as_deref()
107            .map(|source| source as &(dyn Error + 'static))
108    }
109}
110
111#[derive(Clone, Debug, Default)]
112struct ErrorResponseContext {
113    status_code: Option<u16>,
114    headers: BTreeMap<String, String>,
115    request_id: Option<String>,
116    api_error: Option<ApiErrorPayload>,
117}
118
119impl fmt::Display for OpenAIError {
120    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121        write!(f, "{}", self.message)
122    }
123}
124
125impl Error for OpenAIError {
126    fn source(&self) -> Option<&(dyn Error + 'static)> {
127        self.source()
128    }
129}
130
131/// Shared top-level error classifications.
132#[derive(Clone, Debug, Eq, PartialEq)]
133pub enum ErrorKind {
134    /// Client-side configuration or validation failure.
135    Configuration,
136    /// Client-side request validation failure.
137    Validation,
138    /// Webhook signature validation failure.
139    WebhookSignature,
140    /// Transport-level failure.
141    Transport,
142    /// API-status failure.
143    Api(ApiErrorKind),
144    /// Response parsing or validation failure.
145    Parse,
146    /// Timeout failure.
147    Timeout,
148}
149
150/// Typed API-status classifications.
151#[derive(Clone, Copy, Debug, Eq, PartialEq)]
152pub enum ApiErrorKind {
153    BadRequest,
154    Authentication,
155    PermissionDenied,
156    NotFound,
157    Conflict,
158    UnprocessableEntity,
159    RateLimit,
160    Server,
161    Other(u16),
162}
163
164/// Parsed API error payload from the platform response body.
165#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize)]
166pub struct ApiErrorPayload {
167    pub message: String,
168    #[serde(rename = "type")]
169    pub error_type: Option<String>,
170    pub code: Option<String>,
171    pub param: Option<String>,
172}