1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, OpencodeError>;
7
8#[derive(Debug, Error)]
10pub enum OpencodeError {
11 #[error("HTTP error {status}: {message}")]
13 Http {
14 status: u16,
16 name: Option<String>,
18 message: String,
20 data: Option<serde_json::Value>,
22 },
23
24 #[error("Network error: {0}")]
26 Network(String),
27
28 #[error("SSE error: {0}")]
30 Sse(String),
31
32 #[error("JSON error: {0}")]
34 Json(#[from] serde_json::Error),
35
36 #[error("URL error: {0}")]
38 Url(#[from] url::ParseError),
39
40 #[error("Failed to spawn server: {message}")]
42 SpawnServer {
43 message: String,
45 },
46
47 #[error("Server not ready within {timeout_ms}ms")]
49 ServerTimeout {
50 timeout_ms: u64,
52 },
53
54 #[error("Process error: {0}")]
56 Process(String),
57
58 #[error("Invalid configuration: {0}")]
60 InvalidConfig(String),
61
62 #[error("IO error: {0}")]
64 Io(#[from] std::io::Error),
65
66 #[error("Stream closed unexpectedly")]
68 StreamClosed,
69
70 #[error("Session not found: {0}")]
72 SessionNotFound(String),
73
74 #[error("Internal state error: {0}")]
76 State(String),
77}
78
79#[derive(Debug, Clone)]
81pub struct HttpErrorBody {
82 pub name: Option<String>,
84 pub message: Option<String>,
86 pub data: Option<serde_json::Value>,
88}
89
90impl HttpErrorBody {
91 pub fn from_json(v: serde_json::Value) -> Self {
93 Self {
94 name: v
95 .get("name")
96 .and_then(|x| x.as_str())
97 .map(|s| s.to_string()),
98 message: v
99 .get("message")
100 .and_then(|x| x.as_str())
101 .map(|s| s.to_string()),
102 data: v.get("data").cloned(),
103 }
104 }
105}
106
107impl OpencodeError {
108 pub fn http(status: u16, body_text: &str) -> Self {
110 let parsed: Option<serde_json::Value> = serde_json::from_str(body_text).ok();
112 let info = parsed.map(HttpErrorBody::from_json);
113
114 Self::Http {
115 status,
116 name: info.as_ref().and_then(|i| i.name.clone()),
117 message: info
118 .as_ref()
119 .and_then(|i| i.message.clone())
120 .unwrap_or_else(|| format!("HTTP {}", status)),
121 data: info.and_then(|i| i.data),
122 }
123 }
124
125 pub fn is_not_found(&self) -> bool {
127 matches!(self, Self::Http { status: 404, .. })
128 }
129
130 pub fn is_validation_error(&self) -> bool {
132 matches!(self, Self::Http { status: 400, .. })
133 }
134
135 pub fn is_server_error(&self) -> bool {
137 matches!(self, Self::Http { status, .. } if *status >= 500)
138 }
139
140 pub fn error_name(&self) -> Option<&str> {
142 match self {
143 Self::Http { name, .. } => name.as_deref(),
144 _ => None,
145 }
146 }
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn test_http_error_from_named_error() {
155 let body = r#"{"name":"NotFound","message":"Session not found","data":{"id":"123"}}"#;
156 let err = OpencodeError::http(404, body);
157
158 match err {
159 OpencodeError::Http {
160 status,
161 name,
162 message,
163 data,
164 } => {
165 assert_eq!(status, 404);
166 assert_eq!(name, Some("NotFound".to_string()));
167 assert_eq!(message, "Session not found");
168 assert!(data.is_some());
169 }
170 _ => panic!("Expected Http error"),
171 }
172 }
173
174 #[test]
175 fn test_http_error_from_plain_text() {
176 let err = OpencodeError::http(500, "Internal Server Error");
177
178 match err {
179 OpencodeError::Http {
180 status,
181 name,
182 message,
183 ..
184 } => {
185 assert_eq!(status, 500);
186 assert!(name.is_none());
187 assert_eq!(message, "HTTP 500");
188 }
189 _ => panic!("Expected Http error"),
190 }
191 }
192
193 #[test]
194 fn test_is_not_found() {
195 let err = OpencodeError::http(404, "{}");
196 assert!(err.is_not_found());
197
198 let err = OpencodeError::http(200, "{}");
199 assert!(!err.is_not_found());
200 }
201
202 #[test]
203 fn test_is_validation_error() {
204 let err = OpencodeError::http(400, r#"{"name":"ValidationError"}"#);
205 assert!(err.is_validation_error());
206 assert_eq!(err.error_name(), Some("ValidationError"));
207 }
208
209 #[test]
210 fn test_is_server_error() {
211 let err = OpencodeError::http(500, "{}");
212 assert!(err.is_server_error());
213
214 let err = OpencodeError::http(503, "{}");
215 assert!(err.is_server_error());
216
217 let err = OpencodeError::http(400, "{}");
218 assert!(!err.is_server_error());
219 }
220}