1use std::io;
7use thiserror::Error;
8
9pub type Result<T> = std::result::Result<T, Error>;
11
12#[derive(Error, Debug)]
14pub enum Error {
15 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[error("Configuration error: {0}")]
93 Config(String),
94
95 #[error("Invalid configuration file: {0}")]
96 ConfigParse(String),
97
98 #[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 #[error("JSON error: {0}")]
107 Json(#[from] serde_json::Error),
108
109 #[error("Test assertion failed: {0}")]
111 TestAssertion(String),
112
113 #[error("Internal error: {0}")]
115 Internal(String),
116}
117
118impl Error {
119 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 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 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 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#[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 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}