debugger/common/
error.rs

1//! Error types for the debugger CLI
2//!
3//! Error messages are designed to be clear and actionable for LLM agents,
4//! with hints on how to resolve common issues.
5
6use std::io;
7use thiserror::Error;
8
9/// Result type alias using our Error type
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// Main error type for the debugger CLI
13#[derive(Error, Debug)]
14pub enum Error {
15    // === Daemon/Connection Errors ===
16    #[error("Daemon not running. Start a session with 'debugger start <program>'")]
17    DaemonNotRunning,
18
19    #[error("Failed to spawn daemon: timed out waiting for socket after {0} seconds")]
20    DaemonSpawnTimeout(u64),
21
22    #[error("Failed to connect to daemon: {0}")]
23    DaemonConnectionFailed(#[source] io::Error),
24
25    #[error("Daemon communication error: {0}")]
26    DaemonCommunication(String),
27
28    // === Session Errors ===
29    #[error("No debug session active. Use 'debugger start <program>' or 'debugger attach <pid>' first")]
30    SessionNotActive,
31
32    #[error("Debug session already active. Use 'debugger stop' first to end current session")]
33    SessionAlreadyActive,
34
35    #[error("Session terminated unexpectedly: {0}")]
36    SessionTerminated(String),
37
38    #[error("Program has exited with code {0}")]
39    ProgramExited(i32),
40
41    // === Adapter Errors ===
42    #[error("Debug adapter '{name}' not found. Searched: {searched}")]
43    AdapterNotFound { name: String, searched: String },
44
45    #[error("Debug adapter failed to start: {0}")]
46    AdapterStartFailed(String),
47
48    #[error("Debug adapter crashed unexpectedly")]
49    AdapterCrashed,
50
51    #[error("Debug adapter returned error: {0}")]
52    AdapterError(String),
53
54    // === DAP Protocol Errors ===
55    #[error("DAP protocol error: {0}")]
56    DapProtocol(String),
57
58    #[error("DAP request '{command}' failed: {message}")]
59    DapRequestFailed { command: String, message: String },
60
61    #[error("DAP initialization failed: {0}")]
62    DapInitFailed(String),
63
64    // === Breakpoint Errors ===
65    #[error("Invalid breakpoint location: {0}")]
66    InvalidLocation(String),
67
68    #[error("Breakpoint {id} not found")]
69    BreakpointNotFound { id: u32 },
70
71    #[error("Failed to set breakpoint at {location}: {reason}")]
72    BreakpointFailed { location: String, reason: String },
73
74    // === Execution Errors ===
75    #[error("Cannot {action} while program is {state}")]
76    InvalidState { action: String, state: String },
77
78    #[error("Thread {0} not found")]
79    ThreadNotFound(i64),
80
81    #[error("Frame {0} not found")]
82    FrameNotFound(usize),
83
84    // === Timeout Errors ===
85    #[error("Operation timed out after {0} seconds")]
86    Timeout(u64),
87
88    #[error("Await timed out after {0} seconds. Program may still be running - use 'debugger status' to check")]
89    AwaitTimeout(u64),
90
91    // === Configuration Errors ===
92    #[error("Configuration error: {0}")]
93    Config(String),
94
95    #[error("Invalid configuration file: {0}")]
96    ConfigParse(String),
97
98    // === IO Errors ===
99    #[error("IO error: {0}")]
100    Io(#[from] io::Error),
101
102    #[error("Failed to read file '{path}': {error}")]
103    FileRead { path: String, error: String },
104
105    // === Serialization Errors ===
106    #[error("JSON error: {0}")]
107    Json(#[from] serde_json::Error),
108
109    // === Test Errors ===
110    #[error("Test assertion failed: {0}")]
111    TestAssertion(String),
112
113    // === Internal Errors ===
114    #[error("Internal error: {0}")]
115    Internal(String),
116}
117
118impl Error {
119    /// Create an adapter not found error with search paths
120    pub fn adapter_not_found(name: &str, paths: &[&str]) -> Self {
121        Self::AdapterNotFound {
122            name: name.to_string(),
123            searched: paths.join(", "),
124        }
125    }
126
127    /// Create a DAP request failed error
128    pub fn dap_request_failed(command: &str, message: &str) -> Self {
129        Self::DapRequestFailed {
130            command: command.to_string(),
131            message: message.to_string(),
132        }
133    }
134
135    /// Create an invalid state error
136    pub fn invalid_state(action: &str, state: &str) -> Self {
137        Self::InvalidState {
138            action: action.to_string(),
139            state: state.to_string(),
140        }
141    }
142
143    /// Create a breakpoint failed error
144    pub fn breakpoint_failed(location: &str, reason: &str) -> Self {
145        Self::BreakpointFailed {
146            location: location.to_string(),
147            reason: reason.to_string(),
148        }
149    }
150}
151
152/// IPC-serializable error for daemon responses
153#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
154pub struct IpcError {
155    pub code: String,
156    pub message: String,
157}
158
159impl From<&Error> for IpcError {
160    fn from(e: &Error) -> Self {
161        let code = match e {
162            Error::DaemonNotRunning => "DAEMON_NOT_RUNNING",
163            Error::SessionNotActive => "SESSION_NOT_ACTIVE",
164            Error::SessionAlreadyActive => "SESSION_ALREADY_ACTIVE",
165            Error::AdapterNotFound { .. } => "ADAPTER_NOT_FOUND",
166            Error::InvalidLocation(_) => "INVALID_LOCATION",
167            Error::BreakpointNotFound { .. } => "BREAKPOINT_NOT_FOUND",
168            Error::InvalidState { .. } => "INVALID_STATE",
169            Error::ThreadNotFound(_) => "THREAD_NOT_FOUND",
170            Error::FrameNotFound(_) => "FRAME_NOT_FOUND",
171            Error::Timeout(_) | Error::AwaitTimeout(_) => "TIMEOUT",
172            Error::ProgramExited(_) => "PROGRAM_EXITED",
173            Error::DapRequestFailed { .. } => "DAP_REQUEST_FAILED",
174            _ => "INTERNAL_ERROR",
175        }
176        .to_string();
177
178        Self {
179            code,
180            message: e.to_string(),
181        }
182    }
183}
184
185impl From<IpcError> for Error {
186    fn from(e: IpcError) -> Self {
187        // Map IPC errors back to our error types where possible
188        match e.code.as_str() {
189            "SESSION_NOT_ACTIVE" => Error::SessionNotActive,
190            "SESSION_ALREADY_ACTIVE" => Error::SessionAlreadyActive,
191            "TIMEOUT" => Error::Timeout(0),
192            _ => Error::DaemonCommunication(e.message),
193        }
194    }
195}