1use std::backtrace::{Backtrace, BacktraceStatus};
4use std::borrow::{Borrow, Cow};
5use std::fmt;
6use std::time::Duration;
7
8use crate::types::SessionId;
9
10pub type Result<T> = std::result::Result<T, Error>;
12
13#[derive(Debug)]
21pub(crate) enum Repr<T: fmt::Debug> {
22 Simple(T),
23 SimpleMessage(T, Cow<'static, str>),
24 Custom(Custom<T>),
25 }
27
28#[derive(Debug)]
30pub(crate) struct Custom<T: fmt::Debug> {
31 pub(crate) kind: T,
32 pub(crate) error: Box<dyn std::error::Error + Send + Sync>,
33}
34
35#[derive(Clone, Debug, PartialEq, Eq)]
39#[non_exhaustive]
40pub enum ProtocolErrorKind {
41 MissingContentLength,
43
44 InvalidContentLength(String),
46
47 RequestCancelled,
49
50 CliStartupTimeout,
52
53 CliStartupFailed,
55
56 VersionMismatch {
58 server: u32,
60 min: u32,
62 max: u32,
64 },
65
66 VersionChanged {
68 previous: u32,
70 current: u32,
72 },
73}
74
75impl fmt::Display for ProtocolErrorKind {
76 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
77 match self {
78 ProtocolErrorKind::MissingContentLength => {
79 write!(f, "missing Content-Length header")
80 }
81 ProtocolErrorKind::InvalidContentLength(v) => {
82 write!(f, "invalid Content-Length value: \"{v}\"")
83 }
84 ProtocolErrorKind::RequestCancelled => write!(f, "request cancelled"),
85 ProtocolErrorKind::CliStartupTimeout => {
86 write!(f, "timed out waiting for CLI to report listening port")
87 }
88 ProtocolErrorKind::CliStartupFailed => {
89 write!(f, "CLI exited before reporting listening port")
90 }
91 ProtocolErrorKind::VersionMismatch { server, min, max } => {
92 write!(
93 f,
94 "version mismatch: server={server}, supported={min}\u{2013}{max}"
95 )
96 }
97 ProtocolErrorKind::VersionChanged { previous, current } => {
98 write!(f, "version changed: was {previous}, now {current}")
99 }
100 }
101 }
102}
103
104#[derive(Clone, Debug, PartialEq, Eq)]
108#[non_exhaustive]
109pub enum SessionErrorKind {
110 NotFound(SessionId),
112
113 AgentError,
115
116 Timeout(Duration),
118
119 SendWhileWaiting,
121
122 EventLoopClosed,
124
125 ElicitationNotSupported,
128
129 SessionFsProviderRequired,
134
135 InvalidSessionFsConfig,
138
139 SessionIdMismatch {
141 requested: SessionId,
143 returned: SessionId,
145 },
146}
147
148impl fmt::Display for SessionErrorKind {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 match self {
151 SessionErrorKind::NotFound(id) => write!(f, "session not found: {id}"),
152 SessionErrorKind::AgentError => write!(f, "agent error"),
153 SessionErrorKind::Timeout(d) => write!(f, "timed out after {d:?}"),
154 SessionErrorKind::SendWhileWaiting => {
155 write!(f, "cannot send while send_and_wait is in flight")
156 }
157 SessionErrorKind::EventLoopClosed => {
158 write!(f, "event loop closed before session reached idle")
159 }
160 SessionErrorKind::ElicitationNotSupported => write!(
161 f,
162 "elicitation not supported by host \
163 \u{2014} check session.capabilities().ui.elicitation first"
164 ),
165 SessionErrorKind::SessionFsProviderRequired => write!(
166 f,
167 "session was created on a client with session_fs configured \
168 but no SessionFsProvider was supplied"
169 ),
170 SessionErrorKind::InvalidSessionFsConfig => {
171 write!(f, "invalid SessionFsConfig")
172 }
173 SessionErrorKind::SessionIdMismatch {
174 requested,
175 returned,
176 } => write!(
177 f,
178 "CLI returned session ID {returned} after SDK registered {requested}"
179 ),
180 }
181 }
182}
183
184#[derive(Clone, Debug, PartialEq, Eq)]
188#[non_exhaustive]
189pub enum ErrorKind {
190 Protocol(ProtocolErrorKind),
192 Rpc {
194 code: i32,
196 },
197 Session(SessionErrorKind),
199 Io,
201 Json,
203 BinaryNotFound {
205 name: String,
207 hint: Option<String>,
209 },
210 InvalidConfig,
212}
213
214impl fmt::Display for ErrorKind {
215 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
216 match self {
217 ErrorKind::Protocol(k) => write!(f, "{k}"),
218 ErrorKind::Rpc { code } => write!(f, "RPC error {code}"),
219 ErrorKind::Session(k) => write!(f, "{k}"),
220 ErrorKind::Io => write!(f, "I/O error"),
221 ErrorKind::Json => write!(f, "JSON error"),
222 ErrorKind::BinaryNotFound {
223 name,
224 hint: Some(h),
225 } => {
226 write!(f, "binary not found: {name} ({h})")
227 }
228 ErrorKind::BinaryNotFound { name, hint: None } => {
229 write!(f, "binary not found: {name}")
230 }
231 ErrorKind::InvalidConfig => write!(f, "invalid configuration"),
232 }
233 }
234}
235
236pub struct Error {
238 repr: Repr<ErrorKind>,
239 backtrace: Option<Box<Backtrace>>,
242}
243
244impl Error {
245 pub(crate) fn new<E>(kind: ErrorKind, error: E) -> Self
247 where
248 E: Into<Box<dyn std::error::Error + Send + Sync>>,
249 {
250 Self {
251 repr: Repr::Custom(Custom {
252 kind,
253 error: error.into(),
254 }),
255 backtrace: capture_backtrace(),
256 }
257 }
258
259 pub fn kind(&self) -> &ErrorKind {
261 match &self.repr {
262 Repr::Simple(kind)
263 | Repr::SimpleMessage(kind, ..)
264 | Repr::Custom(Custom { kind, .. }) => kind,
265 }
266 }
267
268 pub fn message(&self) -> Option<&str> {
270 match &self.repr {
271 Repr::SimpleMessage(_, message) => Some(message.borrow()),
272 _ => None,
273 }
274 }
275
276 #[must_use]
278 pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
279 where
280 C: Into<Cow<'static, str>>,
281 {
282 Self {
283 repr: Repr::SimpleMessage(kind, message.into()),
284 backtrace: capture_backtrace(),
285 }
286 }
287
288 pub fn is_transport_failure(&self) -> bool {
292 matches!(self.kind(), ErrorKind::Io)
293 || matches!(
294 self.kind(),
295 ErrorKind::Protocol(ProtocolErrorKind::RequestCancelled)
296 )
297 }
298
299 pub fn rpc_code(&self) -> Option<i32> {
301 match self.kind() {
302 ErrorKind::Rpc { code } => Some(*code),
303 _ => None,
304 }
305 }
306}
307
308impl fmt::Display for Error {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 match &self.repr {
311 Repr::Simple(kind) => write!(f, "{kind}"),
312 Repr::SimpleMessage(kind, message) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
313 write!(f, "{kind}: {message}")
314 }
315 Repr::SimpleMessage(_, message) => write!(f, "{message}"),
316 Repr::Custom(Custom { kind, error }) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
317 write!(f, "{kind}: {error}")
318 }
319 Repr::Custom(Custom { error, .. }) => write!(f, "{error}"),
320 }
321 }
322}
323
324impl fmt::Debug for Error {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 let mut dbg = f.debug_struct("Error");
327 dbg.field("context", &self.repr);
328 if let Some(backtrace) = &self.backtrace {
329 return dbg.field("backtrace", backtrace).finish();
330 }
331 dbg.finish_non_exhaustive()
332 }
333}
334
335impl std::error::Error for Error {
336 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
337 match &self.repr {
338 Repr::Custom(Custom { error, .. }) => Some(&**error),
339 _ => None,
340 }
341 }
342}
343
344impl From<ErrorKind> for Error {
345 fn from(kind: ErrorKind) -> Self {
346 Self {
347 repr: Repr::Simple(kind),
348 backtrace: capture_backtrace(),
349 }
350 }
351}
352
353impl From<ProtocolErrorKind> for Error {
354 fn from(kind: ProtocolErrorKind) -> Self {
355 Self::from(ErrorKind::Protocol(kind))
356 }
357}
358
359impl From<SessionErrorKind> for Error {
360 fn from(kind: SessionErrorKind) -> Self {
361 Self::from(ErrorKind::Session(kind))
362 }
363}
364
365impl From<std::io::Error> for Error {
366 fn from(error: std::io::Error) -> Self {
367 Self::new(ErrorKind::Io, error)
368 }
369}
370
371impl From<serde_json::Error> for Error {
372 fn from(error: serde_json::Error) -> Self {
373 Self::new(ErrorKind::Json, error)
374 }
375}
376
377#[inline(always)]
378fn capture_backtrace() -> Option<Box<Backtrace>> {
379 let backtrace = Backtrace::capture();
380 if backtrace.status() == BacktraceStatus::Captured {
381 Some(Box::new(backtrace))
382 } else {
383 None
384 }
385}
386
387#[derive(Debug)]
399pub struct StopErrors(pub(crate) Vec<Error>);
400
401impl StopErrors {
402 pub fn errors(&self) -> &[Error] {
405 &self.0
406 }
407
408 pub fn into_errors(self) -> Vec<Error> {
410 self.0
411 }
412}
413
414impl fmt::Display for StopErrors {
415 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
416 match self.0.as_slice() {
417 [] => write!(f, "stop completed with no errors"),
418 [only] => write!(f, "stop failed: {only}"),
419 [first, rest @ ..] => write!(
420 f,
421 "stop failed with {n} errors; first: {first}",
422 n = 1 + rest.len(),
423 ),
424 }
425 }
426}
427
428impl std::error::Error for StopErrors {
429 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
430 self.0
431 .first()
432 .map(|e| e as &(dyn std::error::Error + 'static))
433 }
434}