use std::io;
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("Daemon not running. Start a session with 'debugger start <program>'")]
DaemonNotRunning,
#[error("Failed to spawn daemon: timed out waiting for socket after {0} seconds")]
DaemonSpawnTimeout(u64),
#[error("Failed to connect to daemon: {0}")]
DaemonConnectionFailed(#[source] io::Error),
#[error("Daemon communication error: {0}")]
DaemonCommunication(String),
#[error("No debug session active. Use 'debugger start <program>' or 'debugger attach <pid>' first")]
SessionNotActive,
#[error("Debug session already active. Use 'debugger stop' first to end current session")]
SessionAlreadyActive,
#[error("Session terminated unexpectedly: {0}")]
SessionTerminated(String),
#[error("Program has exited with code {0}")]
ProgramExited(i32),
#[error("Debug adapter '{name}' not found. Searched: {searched}")]
AdapterNotFound { name: String, searched: String },
#[error("Debug adapter failed to start: {0}")]
AdapterStartFailed(String),
#[error("Debug adapter crashed unexpectedly")]
AdapterCrashed,
#[error("Debug adapter returned error: {0}")]
AdapterError(String),
#[error("DAP protocol error: {0}")]
DapProtocol(String),
#[error("DAP request '{command}' failed: {message}")]
DapRequestFailed { command: String, message: String },
#[error("DAP initialization failed: {0}")]
DapInitFailed(String),
#[error("Invalid breakpoint location: {0}")]
InvalidLocation(String),
#[error("Breakpoint {id} not found")]
BreakpointNotFound { id: u32 },
#[error("Failed to set breakpoint at {location}: {reason}")]
BreakpointFailed { location: String, reason: String },
#[error("Cannot {action} while program is {state}")]
InvalidState { action: String, state: String },
#[error("Thread {0} not found")]
ThreadNotFound(i64),
#[error("Frame {0} not found")]
FrameNotFound(usize),
#[error("Operation timed out after {0} seconds")]
Timeout(u64),
#[error("Await timed out after {0} seconds. Program may still be running - use 'debugger status' to check")]
AwaitTimeout(u64),
#[error("Configuration error: {0}")]
Config(String),
#[error("Invalid configuration file: {0}")]
ConfigParse(String),
#[error("IO error: {0}")]
Io(#[from] io::Error),
#[error("Failed to read file '{path}': {error}")]
FileRead { path: String, error: String },
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("Test assertion failed: {0}")]
TestAssertion(String),
#[error("Internal error: {0}")]
Internal(String),
}
impl Error {
pub fn adapter_not_found(name: &str, paths: &[&str]) -> Self {
Self::AdapterNotFound {
name: name.to_string(),
searched: paths.join(", "),
}
}
pub fn dap_request_failed(command: &str, message: &str) -> Self {
Self::DapRequestFailed {
command: command.to_string(),
message: message.to_string(),
}
}
pub fn invalid_state(action: &str, state: &str) -> Self {
Self::InvalidState {
action: action.to_string(),
state: state.to_string(),
}
}
pub fn breakpoint_failed(location: &str, reason: &str) -> Self {
Self::BreakpointFailed {
location: location.to_string(),
reason: reason.to_string(),
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct IpcError {
pub code: String,
pub message: String,
}
impl From<&Error> for IpcError {
fn from(e: &Error) -> Self {
let code = match e {
Error::DaemonNotRunning => "DAEMON_NOT_RUNNING",
Error::SessionNotActive => "SESSION_NOT_ACTIVE",
Error::SessionAlreadyActive => "SESSION_ALREADY_ACTIVE",
Error::AdapterNotFound { .. } => "ADAPTER_NOT_FOUND",
Error::InvalidLocation(_) => "INVALID_LOCATION",
Error::BreakpointNotFound { .. } => "BREAKPOINT_NOT_FOUND",
Error::InvalidState { .. } => "INVALID_STATE",
Error::ThreadNotFound(_) => "THREAD_NOT_FOUND",
Error::FrameNotFound(_) => "FRAME_NOT_FOUND",
Error::Timeout(_) | Error::AwaitTimeout(_) => "TIMEOUT",
Error::ProgramExited(_) => "PROGRAM_EXITED",
Error::DapRequestFailed { .. } => "DAP_REQUEST_FAILED",
_ => "INTERNAL_ERROR",
}
.to_string();
Self {
code,
message: e.to_string(),
}
}
}
impl From<IpcError> for Error {
fn from(e: IpcError) -> Self {
match e.code.as_str() {
"SESSION_NOT_ACTIVE" => Error::SessionNotActive,
"SESSION_ALREADY_ACTIVE" => Error::SessionAlreadyActive,
"TIMEOUT" => Error::Timeout(0),
_ => Error::DaemonCommunication(e.message),
}
}
}