1use thiserror::Error;
2
3#[derive(Error, Debug)]
5pub enum DapError {
6 #[error("Debug adapter spawn failed for {session_id}: {reason}")]
8 SpawnFailed { session_id: String, reason: String },
9 #[error("Debug adapter crashed: {session_id}")]
11 Crashed { session_id: String },
12 #[error("Invalid state transition: {from_state} -> {to_state}")]
14 InvalidTransition { from_state: String, to_state: String },
15 #[error("DAP request timed out after {timeout_secs}s")]
17 RequestTimeout { timeout_secs: u64 },
18 #[error("No debug adapter found for session: {0}")]
20 NotRegistered(String),
21 #[error("DAP I/O error: {0}")]
23 Io(String),
24 #[error("DAP protocol error: {0}")]
26 Protocol(String),
27}
28
29impl From<DapError> for String {
30 fn from(err: DapError) -> Self {
31 err.to_string()
32 }
33}
34
35#[cfg(test)]
36mod tests {
37 use super::*;
38
39 #[test]
40 fn test_dap_error_spawn_failed_display() {
41 let err = DapError::SpawnFailed {
42 session_id: "sess-1".to_string(),
43 reason: "command not found".to_string(),
44 };
45 let msg = err.to_string();
46 assert!(msg.contains("spawn failed"), "Display should contain 'spawn failed'");
47 assert!(msg.contains("sess-1"), "Display should contain session id");
48 assert!(msg.contains("command not found"), "Display should contain reason");
49 }
50
51 #[test]
52 fn test_dap_error_crashed_display() {
53 let err = DapError::Crashed { session_id: "sess-2".to_string() };
54 let msg = err.to_string();
55 assert!(msg.contains("crashed"), "Display should contain 'crashed'");
56 assert!(msg.contains("sess-2"), "Display should contain session id");
57 }
58
59 #[test]
60 fn test_dap_error_invalid_transition_display() {
61 let err = DapError::InvalidTransition {
62 from_state: "Stopped".to_string(),
63 to_state: "Running".to_string(),
64 };
65 let msg = err.to_string();
66 assert!(msg.contains("Invalid state transition"), "Display should contain transition label");
67 assert!(msg.contains("Stopped"), "Display should contain from_state");
68 assert!(msg.contains("Running"), "Display should contain to_state");
69 }
70
71 #[test]
72 fn test_dap_error_request_timeout_display() {
73 let err = DapError::RequestTimeout { timeout_secs: 30 };
74 let msg = err.to_string();
75 assert!(msg.contains("timed out"), "Display should contain 'timed out'");
76 assert!(msg.contains("30"), "Display should contain timeout duration");
77 }
78
79 #[test]
80 fn test_dap_error_not_registered_display() {
81 let err = DapError::NotRegistered("sess-3".to_string());
82 let msg = err.to_string();
83 assert!(msg.contains("No debug adapter found"), "Display should contain 'No debug adapter found'");
84 assert!(msg.contains("sess-3"), "Display should contain session id");
85 }
86
87 #[test]
88 fn test_dap_error_io_display() {
89 let err = DapError::Io("connection reset".to_string());
90 let msg = err.to_string();
91 assert!(msg.contains("I/O error"), "Display should contain 'I/O error'");
92 assert!(msg.contains("connection reset"), "Display should contain the message");
93 }
94
95 #[test]
96 fn test_dap_error_protocol_display() {
97 let err = DapError::Protocol("malformed header".to_string());
98 let msg = err.to_string();
99 assert!(msg.contains("protocol error"), "Display should contain 'protocol error'");
100 assert!(msg.contains("malformed header"), "Display should contain the message");
101 }
102
103 #[test]
104 fn test_dap_error_into_string() {
105 let err = DapError::SpawnFailed {
106 session_id: "s".to_string(),
107 reason: "r".to_string(),
108 };
109 let s: String = err.into();
110 assert!(s.contains("spawn failed"), "Into<String> should produce the Display output");
111 }
112
113 #[test]
114 fn test_dap_error_debug_format() {
115 let err = DapError::Crashed { session_id: "s".to_string() };
116 let debug = format!("{:?}", err);
117 assert!(debug.contains("Crashed"), "Debug format should contain variant name");
118 }
119}