Skip to main content

quantum_sdk/
error.rs

1use serde::Deserialize;
2use std::fmt;
3
4/// Result type alias for Quantum AI SDK operations.
5pub type Result<T> = std::result::Result<T, Error>;
6
7/// Error types returned by the Quantum AI SDK.
8#[derive(Debug)]
9pub enum Error {
10    /// The API returned a non-2xx status code.
11    Api(ApiError),
12    /// An HTTP transport error occurred.
13    Http(reqwest::Error),
14    /// A serialization or deserialization error occurred.
15    Json(serde_json::Error),
16    /// A WebSocket error occurred (realtime sessions).
17    WebSocket(tokio_tungstenite::tungstenite::Error),
18}
19
20impl fmt::Display for Error {
21    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22        match self {
23            Error::Api(e) => write!(f, "{e}"),
24            Error::Http(e) => write!(f, "qai: http error: {e}"),
25            Error::Json(e) => write!(f, "qai: json error: {e}"),
26            Error::WebSocket(e) => write!(f, "qai: websocket error: {e}"),
27        }
28    }
29}
30
31impl std::error::Error for Error {
32    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
33        match self {
34            Error::Api(_) => None,
35            Error::Http(e) => Some(e),
36            Error::Json(e) => Some(e),
37            Error::WebSocket(e) => Some(e),
38        }
39    }
40}
41
42impl From<tokio_tungstenite::tungstenite::Error> for Error {
43    fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
44        Error::WebSocket(err)
45    }
46}
47
48impl From<reqwest::Error> for Error {
49    fn from(err: reqwest::Error) -> Self {
50        Error::Http(err)
51    }
52}
53
54impl From<serde_json::Error> for Error {
55    fn from(err: serde_json::Error) -> Self {
56        Error::Json(err)
57    }
58}
59
60/// An error returned by the Quantum AI API (non-2xx response).
61#[derive(Debug, Clone)]
62pub struct ApiError {
63    /// The HTTP status code from the response.
64    pub status_code: u16,
65    /// The error type from the API (e.g. "invalid_request", "rate_limit").
66    pub code: String,
67    /// The human-readable error description.
68    pub message: String,
69    /// The unique request identifier from the X-QAI-Request-Id header.
70    pub request_id: String,
71}
72
73impl fmt::Display for ApiError {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        if self.request_id.is_empty() {
76            write!(
77                f,
78                "qai: {} {}: {}",
79                self.status_code, self.code, self.message
80            )
81        } else {
82            write!(
83                f,
84                "qai: {} {}: {} (request_id={})",
85                self.status_code, self.code, self.message, self.request_id
86            )
87        }
88    }
89}
90
91impl std::error::Error for ApiError {}
92
93impl ApiError {
94    /// Returns true if this is a 429 rate limit response.
95    pub fn is_rate_limit(&self) -> bool {
96        self.status_code == 429
97    }
98
99    /// Returns true if this is a 401 or 403 authentication/authorization failure.
100    pub fn is_auth(&self) -> bool {
101        self.status_code == 401 || self.status_code == 403
102    }
103
104    /// Returns true if this is a 404 not found response.
105    pub fn is_not_found(&self) -> bool {
106        self.status_code == 404
107    }
108}
109
110/// Checks whether an error is a rate limit APIError.
111pub fn is_rate_limit_error(err: &Error) -> bool {
112    matches!(err, Error::Api(e) if e.is_rate_limit())
113}
114
115/// Checks whether an error is an authentication APIError.
116pub fn is_auth_error(err: &Error) -> bool {
117    matches!(err, Error::Api(e) if e.is_auth())
118}
119
120/// Checks whether an error is a not found APIError.
121pub fn is_not_found_error(err: &Error) -> bool {
122    matches!(err, Error::Api(e) if e.is_not_found())
123}
124
125/// Raw API error body envelope for JSON parsing.
126#[derive(Deserialize)]
127pub(crate) struct ApiErrorBody {
128    pub error: ApiErrorInner,
129}
130
131#[derive(Deserialize)]
132pub(crate) struct ApiErrorInner {
133    #[serde(default)]
134    pub message: String,
135    #[serde(default)]
136    pub code: String,
137    #[serde(rename = "type", default)]
138    pub error_type: String,
139}