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 InvalidProtocolVersion {
68 server: i64,
70 },
71
72 VersionChanged {
74 previous: u32,
76 current: u32,
78 },
79}
80
81impl fmt::Display for ProtocolErrorKind {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 match self {
84 ProtocolErrorKind::MissingContentLength => {
85 write!(f, "missing Content-Length header")
86 }
87 ProtocolErrorKind::InvalidContentLength(v) => {
88 write!(f, "invalid Content-Length value: \"{v}\"")
89 }
90 ProtocolErrorKind::RequestCancelled => write!(f, "request cancelled"),
91 ProtocolErrorKind::CliStartupTimeout => {
92 write!(f, "timed out waiting for CLI to report listening port")
93 }
94 ProtocolErrorKind::CliStartupFailed => {
95 write!(f, "CLI exited before reporting listening port")
96 }
97 ProtocolErrorKind::VersionMismatch { server, min, max } => {
98 write!(
99 f,
100 "version mismatch: server={server}, supported={min}\u{2013}{max}"
101 )
102 }
103 ProtocolErrorKind::InvalidProtocolVersion { server } => {
104 write!(f, "invalid protocol version: server={server}")
105 }
106 ProtocolErrorKind::VersionChanged { previous, current } => {
107 write!(f, "version changed: was {previous}, now {current}")
108 }
109 }
110 }
111}
112
113#[derive(Clone, Debug, PartialEq, Eq)]
117#[non_exhaustive]
118pub enum SessionErrorKind {
119 NotFound(SessionId),
121
122 AgentError,
124
125 Timeout(Duration),
127
128 SendWhileWaiting,
130
131 EventLoopClosed,
133
134 ElicitationNotSupported,
137
138 SessionFsProviderRequired,
143
144 InvalidSessionFsConfig,
147
148 SessionIdMismatch {
150 requested: SessionId,
152 returned: SessionId,
154 },
155}
156
157impl fmt::Display for SessionErrorKind {
158 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159 match self {
160 SessionErrorKind::NotFound(id) => write!(f, "session not found: {id}"),
161 SessionErrorKind::AgentError => write!(f, "agent error"),
162 SessionErrorKind::Timeout(d) => write!(f, "timed out after {d:?}"),
163 SessionErrorKind::SendWhileWaiting => {
164 write!(f, "cannot send while send_and_wait is in flight")
165 }
166 SessionErrorKind::EventLoopClosed => {
167 write!(f, "event loop closed before session reached idle")
168 }
169 SessionErrorKind::ElicitationNotSupported => write!(
170 f,
171 "elicitation not supported by host \
172 \u{2014} check session.capabilities().ui.elicitation first"
173 ),
174 SessionErrorKind::SessionFsProviderRequired => write!(
175 f,
176 "session was created on a client with session_fs configured \
177 but no SessionFsProvider was supplied"
178 ),
179 SessionErrorKind::InvalidSessionFsConfig => {
180 write!(f, "invalid SessionFsConfig")
181 }
182 SessionErrorKind::SessionIdMismatch {
183 requested,
184 returned,
185 } => write!(
186 f,
187 "CLI returned session ID {returned} after SDK registered {requested}"
188 ),
189 }
190 }
191}
192
193#[derive(Clone, Debug, PartialEq, Eq)]
197#[non_exhaustive]
198pub enum ErrorKind {
199 Protocol(ProtocolErrorKind),
201 Rpc {
203 code: i32,
205 },
206 Session(SessionErrorKind),
208 Io,
210 Json,
212 BinaryNotFound {
214 name: String,
216 hint: Option<String>,
218 },
219 InvalidConfig,
221}
222
223impl fmt::Display for ErrorKind {
224 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225 match self {
226 ErrorKind::Protocol(k) => write!(f, "{k}"),
227 ErrorKind::Rpc { code } => write!(f, "RPC error {code}"),
228 ErrorKind::Session(k) => write!(f, "{k}"),
229 ErrorKind::Io => write!(f, "I/O error"),
230 ErrorKind::Json => write!(f, "JSON error"),
231 ErrorKind::BinaryNotFound {
232 name,
233 hint: Some(h),
234 } => {
235 write!(f, "binary not found: {name} ({h})")
236 }
237 ErrorKind::BinaryNotFound { name, hint: None } => {
238 write!(f, "binary not found: {name}")
239 }
240 ErrorKind::InvalidConfig => write!(f, "invalid configuration"),
241 }
242 }
243}
244
245pub struct Error {
247 repr: Repr<ErrorKind>,
248 backtrace: Option<Box<Backtrace>>,
251}
252
253impl Error {
254 pub(crate) fn new<E>(kind: ErrorKind, error: E) -> Self
256 where
257 E: Into<Box<dyn std::error::Error + Send + Sync>>,
258 {
259 Self {
260 repr: Repr::Custom(Custom {
261 kind,
262 error: error.into(),
263 }),
264 backtrace: capture_backtrace(),
265 }
266 }
267
268 pub fn kind(&self) -> &ErrorKind {
270 match &self.repr {
271 Repr::Simple(kind)
272 | Repr::SimpleMessage(kind, ..)
273 | Repr::Custom(Custom { kind, .. }) => kind,
274 }
275 }
276
277 pub fn message(&self) -> Option<&str> {
279 match &self.repr {
280 Repr::SimpleMessage(_, message) => Some(message.borrow()),
281 _ => None,
282 }
283 }
284
285 #[must_use]
287 pub fn with_message<C>(kind: ErrorKind, message: C) -> Self
288 where
289 C: Into<Cow<'static, str>>,
290 {
291 Self {
292 repr: Repr::SimpleMessage(kind, message.into()),
293 backtrace: capture_backtrace(),
294 }
295 }
296
297 pub fn is_transport_failure(&self) -> bool {
301 matches!(self.kind(), ErrorKind::Io)
302 || matches!(
303 self.kind(),
304 ErrorKind::Protocol(ProtocolErrorKind::RequestCancelled)
305 )
306 }
307
308 pub fn rpc_code(&self) -> Option<i32> {
310 match self.kind() {
311 ErrorKind::Rpc { code } => Some(*code),
312 _ => None,
313 }
314 }
315}
316
317impl fmt::Display for Error {
318 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
319 match &self.repr {
320 Repr::Simple(kind) => write!(f, "{kind}"),
321 Repr::SimpleMessage(kind, message) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
322 write!(f, "{kind}: {message}")
323 }
324 Repr::SimpleMessage(_, message) => write!(f, "{message}"),
325 Repr::Custom(Custom { kind, error }) if matches!(kind, ErrorKind::Rpc { code: _ }) => {
326 write!(f, "{kind}: {error}")
327 }
328 Repr::Custom(Custom { error, .. }) => write!(f, "{error}"),
329 }
330 }
331}
332
333impl fmt::Debug for Error {
334 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335 let mut dbg = f.debug_struct("Error");
336 dbg.field("context", &self.repr);
337 if let Some(backtrace) = &self.backtrace {
338 return dbg.field("backtrace", backtrace).finish();
339 }
340 dbg.finish_non_exhaustive()
341 }
342}
343
344impl std::error::Error for Error {
345 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
346 match &self.repr {
347 Repr::Custom(Custom { error, .. }) => Some(&**error),
348 _ => None,
349 }
350 }
351}
352
353impl From<ErrorKind> for Error {
354 fn from(kind: ErrorKind) -> Self {
355 Self {
356 repr: Repr::Simple(kind),
357 backtrace: capture_backtrace(),
358 }
359 }
360}
361
362impl From<ProtocolErrorKind> for Error {
363 fn from(kind: ProtocolErrorKind) -> Self {
364 Self::from(ErrorKind::Protocol(kind))
365 }
366}
367
368impl From<SessionErrorKind> for Error {
369 fn from(kind: SessionErrorKind) -> Self {
370 Self::from(ErrorKind::Session(kind))
371 }
372}
373
374impl From<std::io::Error> for Error {
375 fn from(error: std::io::Error) -> Self {
376 Self::new(ErrorKind::Io, error)
377 }
378}
379
380impl From<serde_json::Error> for Error {
381 fn from(error: serde_json::Error) -> Self {
382 Self::new(ErrorKind::Json, error)
383 }
384}
385
386#[inline(always)]
387fn capture_backtrace() -> Option<Box<Backtrace>> {
388 let backtrace = Backtrace::capture();
389 if backtrace.status() == BacktraceStatus::Captured {
390 Some(Box::new(backtrace))
391 } else {
392 None
393 }
394}
395
396#[derive(Debug)]
408pub struct StopErrors(pub(crate) Vec<Error>);
409
410impl StopErrors {
411 pub fn errors(&self) -> &[Error] {
414 &self.0
415 }
416
417 pub fn into_errors(self) -> Vec<Error> {
419 self.0
420 }
421}
422
423impl fmt::Display for StopErrors {
424 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
425 match self.0.as_slice() {
426 [] => write!(f, "stop completed with no errors"),
427 [only] => write!(f, "stop failed: {only}"),
428 [first, rest @ ..] => write!(
429 f,
430 "stop failed with {n} errors; first: {first}",
431 n = 1 + rest.len(),
432 ),
433 }
434 }
435}
436
437impl std::error::Error for StopErrors {
438 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
439 self.0
440 .first()
441 .map(|e| e as &(dyn std::error::Error + 'static))
442 }
443}