debugger/setup/
verifier.rs1use crate::common::{Error, Result};
6use std::path::Path;
7use std::process::Stdio;
8use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
9use tokio::process::{Child, Command};
10use tokio::time::{timeout, Duration};
11
12#[derive(Debug, Clone)]
14pub struct VerifyResult {
15 pub success: bool,
17 pub capabilities: Option<DapCapabilities>,
19 pub error: Option<String>,
21}
22
23#[derive(Debug, Clone, Default)]
25pub struct DapCapabilities {
26 pub supports_configuration_done_request: bool,
27 pub supports_function_breakpoints: bool,
28 pub supports_conditional_breakpoints: bool,
29 pub supports_evaluate_for_hovers: bool,
30}
31
32pub async fn verify_dap_adapter(
34 path: &Path,
35 args: &[String],
36) -> Result<VerifyResult> {
37 let mut child = spawn_adapter(path, args).await?;
39
40 let init_result = timeout(Duration::from_secs(5), send_initialize(&mut child)).await;
42
43 let _ = child.kill().await;
45
46 match init_result {
47 Ok(Ok(caps)) => Ok(VerifyResult {
48 success: true,
49 capabilities: Some(caps),
50 error: None,
51 }),
52 Ok(Err(e)) => Ok(VerifyResult {
53 success: false,
54 capabilities: None,
55 error: Some(e.to_string()),
56 }),
57 Err(_) => Ok(VerifyResult {
58 success: false,
59 capabilities: None,
60 error: Some("Timeout waiting for adapter response".to_string()),
61 }),
62 }
63}
64
65async fn spawn_adapter(path: &Path, args: &[String]) -> Result<Child> {
67 let child = Command::new(path)
68 .args(args)
69 .stdin(Stdio::piped())
70 .stdout(Stdio::piped())
71 .stderr(Stdio::piped()) .spawn()
73 .map_err(|e| Error::Internal(format!("Failed to spawn adapter: {}", e)))?;
74
75 Ok(child)
76}
77
78async fn send_initialize(child: &mut Child) -> Result<DapCapabilities> {
80 let stdin = child.stdin.as_mut().ok_or_else(|| {
81 Error::Internal("Failed to get stdin".to_string())
82 })?;
83 let stdout = child.stdout.as_mut().ok_or_else(|| {
84 Error::Internal("Failed to get stdout".to_string())
85 })?;
86
87 let request = serde_json::json!({
89 "seq": 1,
90 "type": "request",
91 "command": "initialize",
92 "arguments": {
93 "clientID": "debugger-cli",
94 "clientName": "debugger-cli",
95 "adapterID": "test",
96 "pathFormat": "path",
97 "linesStartAt1": true,
98 "columnsStartAt1": true,
99 "supportsRunInTerminalRequest": false
100 }
101 });
102
103 let body = serde_json::to_string(&request)?;
105 let header = format!("Content-Length: {}\r\n\r\n", body.len());
106 stdin.write_all(header.as_bytes()).await?;
107 stdin.write_all(body.as_bytes()).await?;
108 stdin.flush().await?;
109
110 let mut reader = BufReader::new(stdout);
112
113 let mut content_length: Option<usize> = None;
115 loop {
116 let mut header_line = String::new();
117 reader.read_line(&mut header_line).await?;
118 let trimmed = header_line.trim();
119
120 if trimmed.is_empty() {
122 break;
123 }
124
125 if let Some(len_str) = trimmed.strip_prefix("Content-Length:") {
127 content_length = len_str.trim().parse().ok();
128 }
129 }
131
132 let content_length = content_length
133 .ok_or_else(|| Error::Internal("Missing Content-Length in DAP response".to_string()))?;
134
135 let mut body = vec![0u8; content_length];
137 tokio::io::AsyncReadExt::read_exact(&mut reader, &mut body).await?;
138
139 let response: serde_json::Value = serde_json::from_slice(&body)?;
141
142 if response.get("success").and_then(|v| v.as_bool()) != Some(true) {
144 let message = response
145 .get("message")
146 .and_then(|v| v.as_str())
147 .unwrap_or("Unknown error");
148 return Err(Error::Internal(format!("Initialize failed: {}", message)));
149 }
150
151 let body = response.get("body").cloned().unwrap_or_default();
153 let caps = DapCapabilities {
154 supports_configuration_done_request: body
155 .get("supportsConfigurationDoneRequest")
156 .and_then(|v| v.as_bool())
157 .unwrap_or(false),
158 supports_function_breakpoints: body
159 .get("supportsFunctionBreakpoints")
160 .and_then(|v| v.as_bool())
161 .unwrap_or(false),
162 supports_conditional_breakpoints: body
163 .get("supportsConditionalBreakpoints")
164 .and_then(|v| v.as_bool())
165 .unwrap_or(false),
166 supports_evaluate_for_hovers: body
167 .get("supportsEvaluateForHovers")
168 .and_then(|v| v.as_bool())
169 .unwrap_or(false),
170 };
171
172 Ok(caps)
173}
174
175pub async fn verify_executable(path: &Path, version_arg: Option<&str>) -> Result<VerifyResult> {
177 let arg = version_arg.unwrap_or("--version");
178
179 let output = tokio::process::Command::new(path)
180 .arg(arg)
181 .output()
182 .await
183 .map_err(|e| Error::Internal(format!("Failed to run {}: {}", path.display(), e)))?;
184
185 if output.status.success() {
186 Ok(VerifyResult {
187 success: true,
188 capabilities: None,
189 error: None,
190 })
191 } else {
192 Ok(VerifyResult {
193 success: false,
194 capabilities: None,
195 error: Some(format!(
196 "Exit code: {}",
197 output.status.code().unwrap_or(-1)
198 )),
199 })
200 }
201}