claude_code_sdk/
errors.rs

1//! Error types for Claude SDK.
2
3use thiserror::Error;
4use tracing::error;
5
6/// Base error type for all Claude SDK errors
7#[derive(Error, Debug)]
8pub enum ClaudeSDKError {
9    #[error("CLI connection error: {0}")]
10    CLIConnection(#[from] CLIConnectionError),
11    
12    #[error("CLI not found: {0}")]
13    CLINotFound(#[from] CLINotFoundError),
14    
15    #[error("Process error: {0}")]
16    Process(#[from] ProcessError),
17    
18    #[error("JSON decode error: {0}")]
19    CLIJSONDecode(#[from] CLIJSONDecodeError),
20    
21    #[error("IO error: {0}")]
22    Io(#[from] std::io::Error),
23    
24    #[error("Other error: {0}")]
25    Other(String),
26}
27
28/// Raised when unable to connect to Claude Code
29#[derive(Error, Debug)]
30#[error("Unable to connect to Claude Code: {message}")]
31pub struct CLIConnectionError {
32    pub message: String,
33}
34
35impl CLIConnectionError {
36    pub fn new(message: impl Into<String>) -> Self {
37        let message_str = message.into();
38        error!(message = %message_str, "CLI connection error occurred");
39        Self {
40            message: message_str,
41        }
42    }
43}
44
45/// Raised when Claude Code is not found or not installed
46#[derive(Error, Debug)]
47#[error("Claude Code not found: {message}")]
48pub struct CLINotFoundError {
49    pub message: String,
50    pub cli_path: Option<String>,
51}
52
53impl CLINotFoundError {
54    pub fn new(message: impl Into<String>) -> Self {
55        let message_str = message.into();
56        error!(message = %message_str, "Claude CLI not found");
57        Self {
58            message: message_str,
59            cli_path: None,
60        }
61    }
62    
63    pub fn with_path(message: impl Into<String>, cli_path: impl Into<String>) -> Self {
64        let cli_path_string = cli_path.into();
65        let message_str = message.into();
66        error!(
67            message = %message_str,
68            cli_path = %cli_path_string,
69            "Claude CLI not found at specified path"
70        );
71        Self {
72            message: format!("{}: {}", message_str, cli_path_string),
73            cli_path: Some(cli_path_string),
74        }
75    }
76}
77
78/// Raised when the CLI process fails
79#[derive(Error, Debug)]
80pub struct ProcessError {
81    pub message: String,
82    pub exit_code: Option<i32>,
83    pub stderr: Option<String>,
84}
85
86impl ProcessError {
87    pub fn new(message: impl Into<String>) -> Self {
88        let message_str = message.into();
89        error!(message = %message_str, "Process error occurred");
90        Self {
91            message: message_str,
92            exit_code: None,
93            stderr: None,
94        }
95    }
96    
97    pub fn with_exit_code(message: impl Into<String>, exit_code: i32) -> Self {
98        let message_str = message.into();
99        error!(
100            message = %message_str,
101            exit_code = exit_code,
102            "Process error with exit code"
103        );
104        Self {
105            message: message_str,
106            exit_code: Some(exit_code),
107            stderr: None,
108        }
109    }
110    
111    pub fn with_stderr(message: impl Into<String>, exit_code: Option<i32>, stderr: impl Into<String>) -> Self {
112        let message_str = message.into();
113        let stderr_str = stderr.into();
114        error!(
115            message = %message_str,
116            exit_code = exit_code,
117            stderr_preview = %stderr_str.chars().take(200).collect::<String>(),
118            "Process error with stderr output"
119        );
120        Self {
121            message: message_str,
122            exit_code,
123            stderr: Some(stderr_str),
124        }
125    }
126}
127
128impl std::fmt::Display for ProcessError {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        write!(f, "{}", self.message)?;
131        
132        if let Some(exit_code) = self.exit_code {
133            write!(f, " (exit code: {})", exit_code)?;
134        }
135        
136        if let Some(stderr) = &self.stderr {
137            write!(f, "\nError output: {}", stderr)?;
138        }
139        
140        Ok(())
141    }
142}
143
144/// Raised when unable to decode JSON from CLI output
145#[derive(Error, Debug)]
146pub struct CLIJSONDecodeError {
147    pub line: String,
148    pub original_error: serde_json::Error,
149}
150
151impl CLIJSONDecodeError {
152    pub fn new(line: impl Into<String>, original_error: serde_json::Error) -> Self {
153        let line_str = line.into();
154        error!(
155            line_preview = %line_str.chars().take(100).collect::<String>(),
156            parse_error = %original_error,
157            "Failed to decode JSON from CLI output"
158        );
159        Self {
160            line: line_str,
161            original_error,
162        }
163    }
164}
165
166impl std::fmt::Display for CLIJSONDecodeError {
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        let line_preview = if self.line.len() > 100 {
169            format!("{}...", &self.line[..100])
170        } else {
171            self.line.clone()
172        };
173        
174        write!(f, "Failed to decode JSON: {}", line_preview)
175    }
176}