claude-agent-sdk-rust/src/errors.rs
Kayos 184b0a786a port message, option, and error types
- Errors: single enum mirroring the Python SDK's exception hierarchy
  (CliNotFound, CliConnection, Process, JsonDecode, MessageParse). Each
  Python class becomes a variant; the Process variant carries exit code +
  optional captured stderr.

- Options: ClaudeAgentOptions as a plain struct with a builder. Covers the
  full subprocess-arg surface (system_prompt with String / PresetAppend /
  File forms, tools / allowed / disallowed, permission_mode, model and
  fallback, cwd / cli_path / settings, env, extra_args, effort, output_format,
  mcp_servers, fork_session, include_partial_messages, etc.). Deferred
  fields documented inline.

- Messages: ContentBlock enum (Text / Thinking / ToolUse / ToolResult /
  ServerToolUse / ServerToolResult / Unknown) with serde tag dispatch and
  an Unknown fallthrough for forward compatibility. Top-level Message enum
  uses a hand-written Deserialize that dispatches on the "type" tag so
  unrecognised top-level frames land in Message::Unknown with the raw JSON
  preserved (matches the Python SDK's "skip unknown" behaviour, but lets
  Rust callers introspect).

Unit tests cover roundtrip for the common shapes (assistant text +
thinking + tool_use, user with both string and structured content, result,
system, unknown top-level).
2026-05-14 08:03:24 -07:00

110 lines
4 KiB
Rust

//! Error types for the Claude Agent SDK.
//!
//! Mirrors the exception hierarchy in the upstream Python SDK
//! (`claude_agent_sdk._errors`):
//!
//! - [`Error::CliNotFound`] — `CLINotFoundError`
//! - [`Error::CliConnection`] — `CLIConnectionError`
//! - [`Error::Process`] — `ProcessError`
//! - [`Error::JsonDecode`] — `CLIJSONDecodeError`
//! - [`Error::MessageParse`] — `MessageParseError`
//!
//! All variants inherit from `ClaudeSDKError` in Python; in Rust we expose a
//! single `Error` enum and rely on pattern matching (or [`Error::kind`]) to
//! distinguish them.
use thiserror::Error;
/// All errors surfaced by the SDK.
///
/// The Python SDK uses an exception hierarchy rooted at `ClaudeSDKError`. We
/// flatten that into a single enum because Rust pattern matching is more
/// ergonomic than `try`/`except` chains, and because most callers only care
/// about a small subset (e.g. "did the CLI not exist?" vs everything else).
#[derive(Debug, Error)]
pub enum Error {
/// The `claude` CLI binary could not be located on `PATH` or in any of the
/// fallback search locations, and no explicit `cli_path` was supplied via
/// [`crate::ClaudeAgentOptions::cli_path`].
///
/// Mirrors Python's `CLINotFoundError`.
#[error("{0}")]
CliNotFound(String),
/// Generic connection / transport problem with the CLI subprocess —
/// failed to spawn, stdin write failed, transport not ready, etc.
///
/// Mirrors Python's `CLIConnectionError`.
#[error("{0}")]
CliConnection(String),
/// The CLI subprocess exited with a non-zero status before yielding a
/// `ResultMessage`.
///
/// Mirrors Python's `ProcessError`.
#[error("{message}{exit_code}{stderr}",
exit_code = match exit_code { Some(c) => format!(" (exit code: {c})"), None => String::new() },
stderr = match stderr { Some(s) if !s.is_empty() => format!("\nError output: {s}"), _ => String::new() },
)]
Process {
/// Human-readable description (e.g. `"Command failed"`).
message: String,
/// Subprocess exit code, when known.
exit_code: Option<i32>,
/// Captured stderr output, when stderr was piped via
/// [`crate::ClaudeAgentOptions::capture_stderr`].
stderr: Option<String>,
},
/// A line on the CLI's stdout could not be decoded as JSON even after
/// buffering up to `max_buffer_size` bytes.
///
/// Mirrors Python's `CLIJSONDecodeError`.
#[error("Failed to decode JSON: {line_preview}...")]
JsonDecode {
/// First ~100 chars of the offending line, included in the message.
line_preview: String,
/// The underlying serde error.
#[source]
source: serde_json::Error,
},
/// A JSON message from the CLI was decoded but did not match any known
/// shape, or was missing a required field.
///
/// Mirrors Python's `MessageParseError`.
#[error("Message parse error: {message}")]
MessageParse {
/// Human-readable description.
message: String,
/// The raw JSON value the parser was looking at, if available.
data: Option<serde_json::Value>,
},
/// Underlying I/O failure (rare — most I/O is surfaced as
/// [`Error::CliConnection`]).
#[error("io error: {0}")]
Io(#[from] std::io::Error),
/// Invalid configuration (e.g. mutually exclusive options).
#[error("invalid configuration: {0}")]
Config(String),
}
impl Error {
/// Construct a [`Error::CliConnection`] from any displayable value.
pub(crate) fn conn(msg: impl Into<String>) -> Self {
Self::CliConnection(msg.into())
}
/// Construct a [`Error::MessageParse`] with raw data attached.
pub(crate) fn parse_with_data(msg: impl Into<String>, data: serde_json::Value) -> Self {
Self::MessageParse {
message: msg.into(),
data: Some(data),
}
}
}
/// Convenience alias for `Result<T, claude_agent_sdk::Error>`.
pub type Result<T> = std::result::Result<T, Error>;