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    /// The API returned a structured error response.
42    #[error(transparent)]
43    ApiError(#[from] error::ResponseError),
44
45    /// Failed to upgrade the request to a WebSocket. Used by the
46    /// `send_streaming_ws` path before any frames have flowed.
47    #[error("websocket upgrade failed: {0}")]
48    WsConnect(#[from] tokio_tungstenite::tungstenite::Error),
49
50    /// Failed to serialize the notify request body to JSON.
51    #[error("notify serialize: {0}")]
52    NotifySerialize(serde_json::Error),
53
54    /// Failed to write the notify frame to the WebSocket sink.
55    #[error("notify send: {0}")]
56    NotifySend(tokio_tungstenite::tungstenite::Error),
57
58    /// The WebSocket closed before the matching `client_response`
59    /// arrived. Either the server hung up or the demux task exited.
60    #[error("notify channel closed before response arrived")]
61    NotifyChannelClosed,
62
63    /// The server replied to a notify with `client_response::Error`.
64    /// The most common cause is the notify's `response_id` not
65    /// matching any agent completion this WS produced.
66    #[error("notify rejected: code={code} message={message}")]
67    NotifyRejected {
68        code: u16,
69        message: serde_json::Value,
70    },
71}
72
73impl error::StatusError for HttpError {
74    fn status(&self) -> u16 {
75        match self {
76            HttpError::DeserializationError(_) => 500,
77            HttpError::BadStatus { code, .. } => code.as_u16(),
78            HttpError::StreamError(reqwest_eventsource::Error::Transport(
79                e,
80            )) => e.status().map(|s| s.as_u16()).unwrap_or(500),
81            HttpError::StreamError(
82                reqwest_eventsource::Error::InvalidStatusCode(code, _),
83            ) => code.as_u16(),
84            HttpError::StreamError(_) => 500,
85            HttpError::RequestError(e) => {
86                e.status().map(|s| s.as_u16()).unwrap_or(500)
87            }
88            HttpError::StreamingRequestError(_) => 500,
89            HttpError::HttpError(e) => {
90                e.status().map(|s| s.as_u16()).unwrap_or(500)
91            }
92            HttpError::ApiError(e) => e.status(),
93            HttpError::WsConnect(_) => 500,
94            HttpError::NotifySerialize(_) => 500,
95            HttpError::NotifySend(_) => 500,
96            HttpError::NotifyChannelClosed => 500,
97            HttpError::NotifyRejected { code, .. } => *code,
98        }
99    }
100
101    fn message(&self) -> Option<serde_json::Value> {
102        Some(serde_json::json!({
103            "kind": "objectiveai_client",
104            "error": match self {
105                HttpError::DeserializationError(e) => serde_json::json!({
106                    "kind": "deserialization",
107                    "error": e.to_string(),
108                }),
109                HttpError::BadStatus { body, .. } => serde_json::json!({
110                    "kind": "bad_status",
111                    "error": body,
112                }),
113                HttpError::StreamError(e) => serde_json::json!({
114                    "kind": "stream_error",
115                    "error": e.to_string(),
116                }),
117                HttpError::RequestError(e) => serde_json::json!({
118                    "kind": "request_error",
119                    "error": e.to_string(),
120                }),
121                HttpError::StreamingRequestError(e) => serde_json::json!({
122                    "kind": "streaming_request_error",
123                    "error": e.to_string(),
124                }),
125                HttpError::HttpError(e) => serde_json::json!({
126                    "kind": "http_error",
127                    "error": e.to_string(),
128                }),
129                HttpError::ApiError(e) => serde_json::json!({
130                    "kind": "api_error",
131                    "error": e.message(),
132                }),
133                HttpError::WsConnect(e) => serde_json::json!({
134                    "kind": "ws_connect",
135                    "error": e.to_string(),
136                }),
137                HttpError::NotifySerialize(e) => serde_json::json!({
138                    "kind": "notify_serialize",
139                    "error": e.to_string(),
140                }),
141                HttpError::NotifySend(e) => serde_json::json!({
142                    "kind": "notify_send",
143                    "error": e.to_string(),
144                }),
145                HttpError::NotifyChannelClosed => serde_json::json!({
146                    "kind": "notify_channel_closed",
147                }),
148                HttpError::NotifyRejected { code, message } => serde_json::json!({
149                    "kind": "notify_rejected",
150                    "code": code,
151                    "message": message,
152                }),
153            }
154        }))
155    }
156}