1use crate::common::{parse_listen_address, Error, Result};
6use std::path::Path;
7use std::process::Stdio;
8use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
9use tokio::net::TcpStream;
10use tokio::process::{Child, Command};
11use tokio::time::{timeout, Duration};
12
13#[derive(Debug, Clone)]
15pub struct VerifyResult {
16 pub success: bool,
18 pub capabilities: Option<DapCapabilities>,
20 pub error: Option<String>,
22}
23
24#[derive(Debug, Clone, Default)]
26pub struct DapCapabilities {
27 pub supports_configuration_done_request: bool,
28 pub supports_function_breakpoints: bool,
29 pub supports_conditional_breakpoints: bool,
30 pub supports_evaluate_for_hovers: bool,
31}
32
33pub async fn verify_dap_adapter(
35 path: &Path,
36 args: &[String],
37) -> Result<VerifyResult> {
38 let mut child = spawn_adapter(path, args).await?;
40
41 let init_result = timeout(Duration::from_secs(5), send_initialize(&mut child)).await;
43
44 let _ = child.kill().await;
46
47 match init_result {
48 Ok(Ok(caps)) => Ok(VerifyResult {
49 success: true,
50 capabilities: Some(caps),
51 error: None,
52 }),
53 Ok(Err(e)) => Ok(VerifyResult {
54 success: false,
55 capabilities: None,
56 error: Some(e.to_string()),
57 }),
58 Err(_) => Ok(VerifyResult {
59 success: false,
60 capabilities: None,
61 error: Some("Timeout waiting for adapter response".to_string()),
62 }),
63 }
64}
65
66pub async fn verify_dap_adapter_tcp(
69 path: &Path,
70 args: &[String],
71) -> Result<VerifyResult> {
72 let mut cmd = Command::new(path);
74 cmd.args(args)
75 .arg("--listen=127.0.0.1:0")
76 .stdin(Stdio::null())
77 .stdout(Stdio::piped())
78 .stderr(Stdio::piped());
79
80 let mut child = cmd.spawn().map_err(|e| {
81 Error::Internal(format!("Failed to spawn adapter: {}", e))
82 })?;
83
84 let stdout = child.stdout.take().ok_or_else(|| {
86 Error::Internal("Failed to get adapter stdout".to_string())
87 })?;
88
89 let mut stdout_reader = BufReader::new(stdout);
90 let mut line = String::new();
91
92 let listen_result = timeout(Duration::from_secs(10), async {
94 loop {
95 line.clear();
96 let bytes_read = stdout_reader.read_line(&mut line).await.map_err(|e| {
97 Error::Internal(format!("Failed to read adapter output: {}", e))
98 })?;
99
100 if bytes_read == 0 {
101 return Err(Error::Internal(
102 "Adapter exited before outputting listen address".to_string(),
103 ));
104 }
105
106 if let Some(addr) = parse_listen_address(&line) {
108 return Ok(addr);
109 }
110 }
111 })
112 .await;
113
114 let addr = match listen_result {
115 Ok(Ok(addr)) => addr,
116 Ok(Err(e)) => {
117 let _ = child.kill().await;
118 return Ok(VerifyResult {
119 success: false,
120 capabilities: None,
121 error: Some(e.to_string()),
122 });
123 }
124 Err(_) => {
125 let _ = child.kill().await;
126 return Ok(VerifyResult {
127 success: false,
128 capabilities: None,
129 error: Some("Timeout waiting for adapter to start listening".to_string()),
130 });
131 }
132 };
133
134 let stream = match TcpStream::connect(&addr).await {
136 Ok(s) => s,
137 Err(e) => {
138 let _ = child.kill().await;
139 return Ok(VerifyResult {
140 success: false,
141 capabilities: None,
142 error: Some(format!("Failed to connect to {}: {}", addr, e)),
143 });
144 }
145 };
146
147 let init_result = timeout(Duration::from_secs(5), send_initialize_tcp(stream)).await;
149
150 let _ = child.kill().await;
152
153 match init_result {
154 Ok(Ok(caps)) => Ok(VerifyResult {
155 success: true,
156 capabilities: Some(caps),
157 error: None,
158 }),
159 Ok(Err(e)) => Ok(VerifyResult {
160 success: false,
161 capabilities: None,
162 error: Some(e.to_string()),
163 }),
164 Err(_) => Ok(VerifyResult {
165 success: false,
166 capabilities: None,
167 error: Some("Timeout waiting for adapter response".to_string()),
168 }),
169 }
170}
171
172async fn spawn_adapter(path: &Path, args: &[String]) -> Result<Child> {
174 let child = Command::new(path)
175 .args(args)
176 .stdin(Stdio::piped())
177 .stdout(Stdio::piped())
178 .stderr(Stdio::piped()) .spawn()
180 .map_err(|e| Error::Internal(format!("Failed to spawn adapter: {}", e)))?;
181
182 Ok(child)
183}
184
185fn build_initialize_request() -> serde_json::Value {
187 serde_json::json!({
188 "seq": 1,
189 "type": "request",
190 "command": "initialize",
191 "arguments": {
192 "clientID": "debugger-cli",
193 "clientName": "debugger-cli",
194 "adapterID": "test",
195 "pathFormat": "path",
196 "linesStartAt1": true,
197 "columnsStartAt1": true,
198 "supportsRunInTerminalRequest": false
199 }
200 })
201}
202
203fn parse_initialize_response(response: &serde_json::Value) -> Result<DapCapabilities> {
205 if response.get("success").and_then(|v| v.as_bool()) != Some(true) {
207 let message = response
208 .get("message")
209 .and_then(|v| v.as_str())
210 .unwrap_or("Unknown error");
211 return Err(Error::Internal(format!("Initialize failed: {}", message)));
212 }
213
214 let body = response.get("body").cloned().unwrap_or_default();
216 Ok(DapCapabilities {
217 supports_configuration_done_request: body
218 .get("supportsConfigurationDoneRequest")
219 .and_then(|v| v.as_bool())
220 .unwrap_or(false),
221 supports_function_breakpoints: body
222 .get("supportsFunctionBreakpoints")
223 .and_then(|v| v.as_bool())
224 .unwrap_or(false),
225 supports_conditional_breakpoints: body
226 .get("supportsConditionalBreakpoints")
227 .and_then(|v| v.as_bool())
228 .unwrap_or(false),
229 supports_evaluate_for_hovers: body
230 .get("supportsEvaluateForHovers")
231 .and_then(|v| v.as_bool())
232 .unwrap_or(false),
233 })
234}
235
236async fn send_dap_message<W, R>(writer: &mut W, reader: &mut BufReader<R>, request: &serde_json::Value) -> Result<serde_json::Value>
238where
239 W: AsyncWriteExt + Unpin,
240 R: tokio::io::AsyncRead + Unpin,
241{
242 let body = serde_json::to_string(request)?;
244 let header = format!("Content-Length: {}\r\n\r\n", body.len());
245 writer.write_all(header.as_bytes()).await?;
246 writer.write_all(body.as_bytes()).await?;
247 writer.flush().await?;
248
249 let mut content_length: Option<usize> = None;
251 loop {
252 let mut header_line = String::new();
253 reader.read_line(&mut header_line).await?;
254 let trimmed = header_line.trim();
255
256 if trimmed.is_empty() {
258 break;
259 }
260
261 if let Some(len_str) = trimmed.strip_prefix("Content-Length:") {
263 content_length = len_str.trim().parse().ok();
264 }
265 }
267
268 let content_length = content_length
269 .ok_or_else(|| Error::Internal("Missing Content-Length in DAP response".to_string()))?;
270
271 let mut body = vec![0u8; content_length];
273 reader.read_exact(&mut body).await?;
274
275 let response: serde_json::Value = serde_json::from_slice(&body)?;
277 Ok(response)
278}
279
280async fn send_initialize(child: &mut Child) -> Result<DapCapabilities> {
282 let stdin = child.stdin.as_mut().ok_or_else(|| {
283 Error::Internal("Failed to get stdin".to_string())
284 })?;
285 let stdout = child.stdout.as_mut().ok_or_else(|| {
286 Error::Internal("Failed to get stdout".to_string())
287 })?;
288
289 let request = build_initialize_request();
290 let mut reader = BufReader::new(stdout);
291 let response = send_dap_message(stdin, &mut reader, &request).await?;
292 parse_initialize_response(&response)
293}
294
295async fn send_initialize_tcp(stream: TcpStream) -> Result<DapCapabilities> {
297 let (read_half, mut write_half) = tokio::io::split(stream);
298
299 let request = build_initialize_request();
300 let mut reader = BufReader::new(read_half);
301 let response = send_dap_message(&mut write_half, &mut reader, &request).await?;
302 parse_initialize_response(&response)
303}
304
305pub async fn verify_executable(path: &Path, version_arg: Option<&str>) -> Result<VerifyResult> {
307 let arg = version_arg.unwrap_or("--version");
308
309 let output = tokio::process::Command::new(path)
310 .arg(arg)
311 .output()
312 .await
313 .map_err(|e| Error::Internal(format!("Failed to run {}: {}", path.display(), e)))?;
314
315 if output.status.success() {
316 Ok(VerifyResult {
317 success: true,
318 capabilities: None,
319 error: None,
320 })
321 } else {
322 Ok(VerifyResult {
323 success: false,
324 capabilities: None,
325 error: Some(format!(
326 "Exit code: {}",
327 output.status.code().unwrap_or(-1)
328 )),
329 })
330 }
331}