rust_analyzer_cli/daemon/
server.rs1use crate::daemon::state::AppState;
2use crate::lsp::types::*;
3use axum::{
4 Json, Router,
5 extract::{State, rejection::JsonRejection},
6 http::StatusCode,
7 routing::{get, post},
8};
9use std::net::SocketAddr;
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12use tracing::info;
13
14pub async fn start_daemon_server(workspace_root: PathBuf, port: u16) -> anyhow::Result<()> {
15 start_daemon_server_with_output(workspace_root, port, false).await
16}
17
18pub async fn start_daemon_server_with_output(
19 workspace_root: PathBuf,
20 port: u16,
21 json_output: bool,
22) -> anyhow::Result<()> {
23 let state = Arc::new(AppState::new(workspace_root));
24
25 let app = Router::new()
26 .route("/status", get(handle_status))
27 .route("/refresh", post(handle_refresh))
28 .route("/api/symbol", post(handle_symbol))
29 .route("/api/outline", post(handle_outline))
30 .route("/api/definition", post(handle_definition))
31 .route("/api/body", post(handle_body))
32 .route("/api/hover", post(handle_hover))
33 .route("/api/references", post(handle_references))
34 .route("/api/calls", post(handle_calls))
35 .route("/api/relations", post(handle_relations))
36 .with_state(state.clone());
37
38 let addr = SocketAddr::from(([127, 0, 0, 1], port));
39 let listener = tokio::net::TcpListener::bind(addr).await?;
40 let actual_addr = listener.local_addr()?;
41 info!(
42 "rust-analyzer-cli daemon listening on http://{}",
43 actual_addr
44 );
45 if json_output {
46 println!(
47 "{}",
48 daemon_started_payload(actual_addr, &state.workspace_root)
49 );
50 } else {
51 println!(
52 "rust-analyzer-cli daemon started on http://{}\nWorkspace: {}\nKeep this process running while issuing query commands.",
53 actual_addr,
54 state.workspace_root.display()
55 );
56 }
57
58 tokio::select! {
59 result = axum::serve(listener, app) => {
60 result?;
61 }
62 _ = tokio::signal::ctrl_c() => {
63 if let Some(client) = state.lsp_client.read().await.clone() {
64 client.shutdown().await;
65 }
66 }
67 }
68 Ok(())
69}
70
71fn daemon_started_payload(address: SocketAddr, workspace_root: &Path) -> serde_json::Value {
72 serde_json::json!({
73 "success": true,
74 "message": "rust-analyzer-cli daemon started",
75 "address": format!("http://{}", address),
76 "workspace": workspace_root.to_string_lossy(),
77 })
78}
79
80async fn handle_status(
81 State(state): State<Arc<AppState>>,
82) -> Result<Json<DaemonStatusResponse>, (StatusCode, String)> {
83 let client_opt = state.lsp_client.read().await;
84 let error = state.last_error.read().await.clone();
85 let (state_value, pid) = match client_opt.as_ref() {
86 Some(c) => (DaemonState::Ready, Some(c.process_id)),
87 None if error.is_some() => (DaemonState::Failed, None),
88 None => (DaemonState::Starting, None),
89 };
90
91 Ok(Json(DaemonStatusResponse {
92 state: state_value,
93 workspace_root: state.workspace_root.to_string_lossy().to_string(),
94 process_id: pid,
95 error,
96 }))
97}
98
99async fn handle_refresh(
100 State(state): State<Arc<AppState>>,
101) -> Result<Json<serde_json::Value>, (StatusCode, String)> {
102 state
103 .refresh_lsp()
104 .await
105 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
106 Ok(Json(
107 serde_json::json!({ "success": true, "message": "LSP session refreshed" }),
108 ))
109}
110
111async fn handle_symbol(
112 State(state): State<Arc<AppState>>,
113 Json(req): Json<SymbolQueryRequest>,
114) -> Result<Json<Vec<SymbolItem>>, (StatusCode, String)> {
115 let client = state
116 .get_or_start_lsp()
117 .await
118 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
119
120 let items = client
121 .query_symbol(
122 &req.name,
123 &req.kind,
124 req.exact,
125 req.include_body,
126 req.max_lines,
127 )
128 .await
129 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
130
131 Ok(Json(items))
132}
133
134async fn handle_outline(
135 State(state): State<Arc<AppState>>,
136 Json(req): Json<OutlineQueryRequest>,
137) -> Result<Json<Vec<OutlineItem>>, (StatusCode, String)> {
138 let client = state
139 .get_or_start_lsp()
140 .await
141 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
142
143 let items = client
144 .query_outline(&req.file, req.include_body, req.max_lines)
145 .await
146 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
147
148 Ok(Json(items))
149}
150
151async fn handle_definition(
152 State(state): State<Arc<AppState>>,
153 Json(req): Json<DefinitionQueryRequest>,
154) -> Result<Json<Vec<DefinitionItem>>, (StatusCode, String)> {
155 let client = state
156 .get_or_start_lsp()
157 .await
158 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
159
160 let items = client
161 .query_definition(
162 &req.file,
163 req.line,
164 req.col,
165 req.include_body,
166 req.max_lines,
167 )
168 .await
169 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
170
171 Ok(Json(items))
172}
173
174async fn handle_body(
175 State(state): State<Arc<AppState>>,
176 Json(req): Json<BodyQueryRequest>,
177) -> Result<Json<BodyItem>, (StatusCode, String)> {
178 let client = state
179 .get_or_start_lsp()
180 .await
181 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
182
183 let item = client
184 .query_body(&req.file, req.line, req.col, req.max_lines)
185 .await
186 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
187
188 Ok(Json(item))
189}
190
191async fn handle_hover(
192 State(state): State<Arc<AppState>>,
193 Json(req): Json<HoverQueryRequest>,
194) -> Result<Json<Option<HoverItem>>, (StatusCode, String)> {
195 let client = state
196 .get_or_start_lsp()
197 .await
198 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
199
200 let item = client
201 .query_hover(&req.file, req.line, req.col)
202 .await
203 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
204
205 Ok(Json(item))
206}
207
208async fn handle_references(
209 State(state): State<Arc<AppState>>,
210 request: Result<Json<ReferenceQueryRequest>, JsonRejection>,
211) -> Result<Json<Vec<ReferenceItem>>, (StatusCode, String)> {
212 let Json(req) = request.map_err(|error| invalid_json_request("references", error))?;
213 let client = state
214 .get_or_start_lsp()
215 .await
216 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
217
218 let items = client
219 .query_references(&req.file, req.line, req.col)
220 .await
221 .map_err(query_error)?;
222
223 Ok(Json(items))
224}
225
226async fn handle_calls(
227 State(state): State<Arc<AppState>>,
228 request: Result<Json<CallQueryRequest>, JsonRejection>,
229) -> Result<Json<Vec<CallItem>>, (StatusCode, String)> {
230 let Json(req) = request.map_err(|error| invalid_json_request("calls", error))?;
231 let client = state
232 .get_or_start_lsp()
233 .await
234 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
235
236 let items = client
237 .query_calls(&req.file, req.line, req.col, req.direction, req.depth)
238 .await
239 .map_err(query_error)?;
240
241 Ok(Json(items))
242}
243
244async fn handle_relations(
245 State(state): State<Arc<AppState>>,
246 request: Result<Json<RelationQueryRequest>, JsonRejection>,
247) -> Result<Json<Vec<RelationItem>>, (StatusCode, String)> {
248 let Json(req) = request.map_err(|error| invalid_json_request("relations", error))?;
249 let client = state
250 .get_or_start_lsp()
251 .await
252 .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
253
254 let items = client
255 .query_relations(&req.file, req.line, req.col, req.mode)
256 .await
257 .map_err(query_error)?;
258
259 Ok(Json(items))
260}
261
262fn invalid_json_request(operation: &str, error: JsonRejection) -> (StatusCode, String) {
263 (
264 StatusCode::BAD_REQUEST,
265 format!("Invalid {operation} request: {error}"),
266 )
267}
268
269fn query_error(error: anyhow::Error) -> (StatusCode, String) {
270 let message = error.to_string();
271 if message.contains("-32601") {
272 return (
273 StatusCode::NOT_IMPLEMENTED,
274 format!("rust-analyzer does not support this navigation capability: {message}"),
275 );
276 }
277 if message.starts_with("Invalid ")
278 || message.contains("must be at least")
279 || message.contains("outside workspace")
280 || message.contains("Failed to resolve Rust source file")
281 {
282 return (StatusCode::BAD_REQUEST, message);
283 }
284 (StatusCode::INTERNAL_SERVER_ERROR, message)
285}
286
287#[cfg(test)]
288mod tests {
289 use super::*;
290
291 #[test]
292 fn test_daemon_started_payload_is_machine_readable() {
293 let payload = daemon_started_payload(
294 SocketAddr::from(([127, 0, 0, 1], 60094)),
295 Path::new("workspace"),
296 );
297 let encoded = serde_json::to_string(&payload).unwrap();
298 let decoded: serde_json::Value = serde_json::from_str(&encoded).unwrap();
299 assert_eq!(decoded["success"], true);
300 assert_eq!(decoded["address"], "http://127.0.0.1:60094");
301 }
302}