Skip to main content

objectiveai_sdk/http/
error.rs

1//! HTTP error types.
2
3use crate::error;
4
5/// Errors that can occur during HTTP operations.
6#[derive(thiserror::Error, Debug)]
7pub enum HttpError {
8    /// Failed to deserialize the response body.
9    ///
10    /// Includes path information to help identify which field caused the error.
11    #[error("deserialization error: {0}")]
12    DeserializationError(#[from] serde_path_to_error::Error<serde_json::Error>),
13
14    /// The server returned a non-success HTTP status code.
15    #[error("received bad status code: {code}, body: {body}")]
16    BadStatus {
17        /// The HTTP status code (e.g., 400, 401, 500).
18        code: reqwest::StatusCode,
19        /// Response body, parsed as JSON if possible, otherwise as a string.
20        body: serde_json::Value,
21    },
22
23    /// Error occurred while reading from an SSE stream.
24    #[error("error fetching stream: {0}")]
25    StreamError(#[from] reqwest_eventsource::Error),
26
27    /// Failed to build the HTTP request.
28    #[error("request error: {0}")]
29    RequestError(reqwest::Error),
30
31    /// Failed to establish a streaming connection.
32    ///
33    /// Occurs when the request cannot be cloned for SSE retry logic.
34    #[error("streaming request error: {0}")]
35    StreamingRequestError(#[from] reqwest_eventsource::CannotCloneRequestError),
36
37    /// General HTTP transport error (network, timeout, etc.).
38    #[error("http error: {0}")]
39    HttpError(reqwest::Error),
40
41    /// Two attempts failed (e.g. the GitHub raw + Contents-API fallback).
42    #[error("multiple errors: {0}, {1}")]
43    MultipleErrors(Box<HttpError>, Box<HttpError>),
44
45    /// The API returned a structured error response.
46    #[error(transparent)]
47    ApiError(#[from] error::ResponseError),
48
49    /// Failed to upgrade the request to a WebSocket. Used by the
50    /// `send_streaming_ws` path before any frames have flowed.
51    #[error("websocket upgrade failed: {0}")]
52    WsConnect(#[from] tokio_tungstenite::tungstenite::Error),
53
54    /// Failed to serialize the notify request body to JSON.
55    #[error("notify serialize: {0}")]
56    NotifySerialize(serde_json::Error),
57
58    /// Failed to write the notify frame to the WebSocket sink.
59    #[error("notify send: {0}")]
60    NotifySend(tokio_tungstenite::tungstenite::Error),
61
62    /// The WebSocket closed before the matching `client_response`
63    /// arrived. Either the server hung up or the demux task exited.
64    #[error("notify channel closed before response arrived")]
65    NotifyChannelClosed,
66
67    /// The server replied to a notify with `client_response::Error`.
68    /// The most common cause is the notify's `response_id` not
69    /// matching any agent completion this WS produced.
70    #[error("notify rejected: code={code} message={message}")]
71    NotifyRejected {
72        code: u16,
73        message: serde_json::Value,
74    },
75}
76
77impl error::StatusError for HttpError {
78    fn status(&self) -> u16 {
79        match self {
80            HttpError::DeserializationError(_) => 500,
81            HttpError::BadStatus { code, .. } => code.as_u16(),
82            HttpError::StreamError(reqwest_eventsource::Error::Transport(
83                e,
84            )) => e.status().map(|s| s.as_u16()).unwrap_or(500),
85            HttpError::StreamError(
86                reqwest_eventsource::Error::InvalidStatusCode(code, _),
87            ) => code.as_u16(),
88            HttpError::StreamError(_) => 500,
89            HttpError::RequestError(e) => {
90                e.status().map(|s| s.as_u16()).unwrap_or(500)
91            }
92            HttpError::StreamingRequestError(_) => 500,
93            HttpError::HttpError(e) => {
94                e.status().map(|s| s.as_u16()).unwrap_or(500)
95            }
96            HttpError::MultipleErrors(e1, e2) => {
97                let s2 = e2.status();
98                if s2 != 500 { s2 } else { e1.status() }
99            }
100            HttpError::ApiError(e) => e.status(),
101            HttpError::WsConnect(_) => 500,
102            HttpError::NotifySerialize(_) => 500,
103            HttpError::NotifySend(_) => 500,
104            HttpError::NotifyChannelClosed => 500,
105            HttpError::NotifyRejected { code, .. } => *code,
106        }
107    }
108
109    fn message(&self) -> Option<serde_json::Value> {
110        Some(serde_json::json!({
111            "kind": "objectiveai_client",
112            "error": match self {
113                HttpError::DeserializationError(e) => serde_json::json!({
114                    "kind": "deserialization",
115                    "error": e.to_string(),
116                }),
117                HttpError::BadStatus { body, .. } => serde_json::json!({
118                    "kind": "bad_status",
119                    "error": body,
120                }),
121                HttpError::StreamError(e) => serde_json::json!({
122                    "kind": "stream_error",
123                    "error": e.to_string(),
124                }),
125                HttpError::RequestError(e) => serde_json::json!({
126                    "kind": "request_error",
127                    "error": e.to_string(),
128                }),
129                HttpError::StreamingRequestError(e) => serde_json::json!({
130                    "kind": "streaming_request_error",
131                    "error": e.to_string(),
132                }),
133                HttpError::HttpError(e) => serde_json::json!({
134                    "kind": "http_error",
135                    "error": e.to_string(),
136                }),
137                HttpError::MultipleErrors(e1, e2) => serde_json::json!({
138                    "kind": "multiple",
139                    "error_1": e1.message(),
140                    "error_2": e2.message(),
141                }),
142                HttpError::ApiError(e) => serde_json::json!({
143                    "kind": "api_error",
144                    "error": e.message(),
145                }),
146                HttpError::WsConnect(e) => serde_json::json!({
147                    "kind": "ws_connect",
148                    "error": e.to_string(),
149                }),
150                HttpError::NotifySerialize(e) => serde_json::json!({
151                    "kind": "notify_serialize",
152                    "error": e.to_string(),
153                }),
154                HttpError::NotifySend(e) => serde_json::json!({
155                    "kind": "notify_send",
156                    "error": e.to_string(),
157                }),
158                HttpError::NotifyChannelClosed => serde_json::json!({
159                    "kind": "notify_channel_closed",
160                }),
161                HttpError::NotifyRejected { code, message } => serde_json::json!({
162                    "kind": "notify_rejected",
163                    "code": code,
164                    "message": message,
165                }),
166            }
167        }))
168    }
169}