use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum ClaudeSDKError {
#[error("Claude Code CLI not found: {message}")]
CLINotFound {
message: String,
},
#[error("CLI connection error: {message}")]
CLIConnection {
message: String,
#[source]
source: Option<io::Error>,
},
#[error("Process error (exit code {exit_code:?}): {message}")]
Process {
exit_code: Option<i32>,
message: String,
stderr: Option<String>,
},
#[error("JSON decode error: {message}")]
JSONDecode {
message: String,
raw_data: Option<String>,
buffer_content: Option<String>,
#[source]
source: Option<serde_json::Error>,
},
#[error("Message parse error: {message}")]
MessageParse {
message: String,
raw_message: Option<serde_json::Value>,
},
#[error("Configuration error: {message}")]
Configuration {
message: String,
},
#[error("Control protocol error: {message}")]
ControlProtocol {
message: String,
request_id: Option<String>,
},
#[error("Operation interrupted")]
Interrupted,
#[error("Operation timed out after {duration_ms}ms")]
Timeout {
duration_ms: u64,
},
#[error("CLI version {found} is below minimum required version {required}")]
VersionMismatch {
found: String,
required: String,
},
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Channel error: {message}")]
Channel {
message: String,
},
#[error("Internal error: {message}")]
Internal {
message: String,
},
}
impl ClaudeSDKError {
pub fn cli_not_found(message: impl Into<String>) -> Self {
Self::CLINotFound {
message: message.into(),
}
}
pub fn cli_connection(message: impl Into<String>) -> Self {
Self::CLIConnection {
message: message.into(),
source: None,
}
}
pub fn cli_connection_with_source(message: impl Into<String>, source: io::Error) -> Self {
Self::CLIConnection {
message: message.into(),
source: Some(source),
}
}
pub fn process(exit_code: Option<i32>, message: impl Into<String>) -> Self {
Self::Process {
exit_code,
message: message.into(),
stderr: None,
}
}
pub fn process_with_stderr(
exit_code: Option<i32>,
message: impl Into<String>,
stderr: impl Into<String>,
) -> Self {
Self::Process {
exit_code,
message: message.into(),
stderr: Some(stderr.into()),
}
}
pub fn json_decode(message: impl Into<String>) -> Self {
Self::JSONDecode {
message: message.into(),
raw_data: None,
buffer_content: None,
source: None,
}
}
pub fn json_decode_with_context(
message: impl Into<String>,
raw_data: Option<String>,
buffer_content: Option<String>,
source: serde_json::Error,
) -> Self {
Self::JSONDecode {
message: message.into(),
raw_data,
buffer_content,
source: Some(source),
}
}
pub fn message_parse(message: impl Into<String>) -> Self {
Self::MessageParse {
message: message.into(),
raw_message: None,
}
}
pub fn message_parse_with_raw(
message: impl Into<String>,
raw_message: serde_json::Value,
) -> Self {
Self::MessageParse {
message: message.into(),
raw_message: Some(raw_message),
}
}
pub fn configuration(message: impl Into<String>) -> Self {
Self::Configuration {
message: message.into(),
}
}
pub fn control_protocol(message: impl Into<String>) -> Self {
Self::ControlProtocol {
message: message.into(),
request_id: None,
}
}
pub fn control_protocol_with_id(
message: impl Into<String>,
request_id: impl Into<String>,
) -> Self {
Self::ControlProtocol {
message: message.into(),
request_id: Some(request_id.into()),
}
}
pub fn timeout(duration_ms: u64) -> Self {
Self::Timeout { duration_ms }
}
pub fn version_mismatch(found: impl Into<String>, required: impl Into<String>) -> Self {
Self::VersionMismatch {
found: found.into(),
required: required.into(),
}
}
pub fn channel(message: impl Into<String>) -> Self {
Self::Channel {
message: message.into(),
}
}
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal {
message: message.into(),
}
}
pub fn is_cli_not_found(&self) -> bool {
matches!(self, Self::CLINotFound { .. })
}
pub fn is_recoverable(&self) -> bool {
matches!(
self,
Self::CLIConnection { .. } | Self::Timeout { .. } | Self::Channel { .. }
)
}
}
pub type Result<T> = std::result::Result<T, ClaudeSDKError>;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_error_display() {
let err = ClaudeSDKError::cli_not_found("claude not in PATH");
assert!(err.to_string().contains("claude not in PATH"));
}
#[test]
fn test_process_error_with_exit_code() {
let err = ClaudeSDKError::process(Some(1), "command failed");
assert!(err.to_string().contains("exit code"));
assert!(err.to_string().contains("1"));
}
#[test]
fn test_is_recoverable() {
assert!(ClaudeSDKError::timeout(1000).is_recoverable());
assert!(!ClaudeSDKError::cli_not_found("not found").is_recoverable());
}
}