Skip to main content

rust_analyzer_cli/daemon/
server.rs

1use 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    request: Result<Json<SymbolQueryRequest>, JsonRejection>,
114) -> Result<Json<Vec<SymbolItem>>, (StatusCode, String)> {
115    let Json(req) = request.map_err(|error| invalid_json_request("symbol", error))?;
116    let client = state
117        .get_or_start_lsp()
118        .await
119        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
120
121    let items = client
122        .query_symbol(
123            &req.name,
124            &req.kind,
125            req.exact,
126            req.include_body,
127            req.max_lines,
128        )
129        .await
130        .map_err(query_error)?;
131
132    Ok(Json(items))
133}
134
135async fn handle_outline(
136    State(state): State<Arc<AppState>>,
137    request: Result<Json<OutlineQueryRequest>, JsonRejection>,
138) -> Result<Json<Vec<OutlineItem>>, (StatusCode, String)> {
139    let Json(req) = request.map_err(|error| invalid_json_request("outline", error))?;
140    let client = state
141        .get_or_start_lsp()
142        .await
143        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
144
145    let items = client
146        .query_outline(&req.file, req.include_body, req.max_lines)
147        .await
148        .map_err(query_error)?;
149
150    Ok(Json(items))
151}
152
153async fn handle_definition(
154    State(state): State<Arc<AppState>>,
155    request: Result<Json<DefinitionQueryRequest>, JsonRejection>,
156) -> Result<Json<Vec<DefinitionItem>>, (StatusCode, String)> {
157    let Json(req) = request.map_err(|error| invalid_json_request("definition", error))?;
158    let client = state
159        .get_or_start_lsp()
160        .await
161        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
162
163    let items = client
164        .query_definition(
165            &req.file,
166            req.line,
167            req.col,
168            req.include_body,
169            req.max_lines,
170        )
171        .await
172        .map_err(query_error)?;
173
174    Ok(Json(items))
175}
176
177async fn handle_body(
178    State(state): State<Arc<AppState>>,
179    request: Result<Json<BodyQueryRequest>, JsonRejection>,
180) -> Result<Json<BodyItem>, (StatusCode, String)> {
181    let Json(req) = request.map_err(|error| invalid_json_request("body", error))?;
182    let client = state
183        .get_or_start_lsp()
184        .await
185        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
186
187    let item = client
188        .query_body(&req.file, req.line, req.col, req.max_lines)
189        .await
190        .map_err(query_error)?;
191
192    Ok(Json(item))
193}
194
195async fn handle_hover(
196    State(state): State<Arc<AppState>>,
197    request: Result<Json<HoverQueryRequest>, JsonRejection>,
198) -> Result<Json<Option<HoverItem>>, (StatusCode, String)> {
199    let Json(req) = request.map_err(|error| invalid_json_request("hover", error))?;
200    let client = state
201        .get_or_start_lsp()
202        .await
203        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
204
205    let item = client
206        .query_hover(&req.file, req.line, req.col)
207        .await
208        .map_err(query_error)?;
209
210    Ok(Json(item))
211}
212
213async fn handle_references(
214    State(state): State<Arc<AppState>>,
215    request: Result<Json<ReferenceQueryRequest>, JsonRejection>,
216) -> Result<Json<Vec<ReferenceItem>>, (StatusCode, String)> {
217    let Json(req) = request.map_err(|error| invalid_json_request("references", error))?;
218    let client = state
219        .get_or_start_lsp()
220        .await
221        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
222
223    let items = client
224        .query_references(&req.file, req.line, req.col)
225        .await
226        .map_err(query_error)?;
227
228    Ok(Json(items))
229}
230
231async fn handle_calls(
232    State(state): State<Arc<AppState>>,
233    request: Result<Json<CallQueryRequest>, JsonRejection>,
234) -> Result<Json<Vec<CallItem>>, (StatusCode, String)> {
235    let Json(req) = request.map_err(|error| invalid_json_request("calls", error))?;
236    let client = state
237        .get_or_start_lsp()
238        .await
239        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
240
241    let items = client
242        .query_calls(&req.file, req.line, req.col, req.direction, req.depth)
243        .await
244        .map_err(query_error)?;
245
246    Ok(Json(items))
247}
248
249async fn handle_relations(
250    State(state): State<Arc<AppState>>,
251    request: Result<Json<RelationQueryRequest>, JsonRejection>,
252) -> Result<Json<Vec<RelationItem>>, (StatusCode, String)> {
253    let Json(req) = request.map_err(|error| invalid_json_request("relations", error))?;
254    let client = state
255        .get_or_start_lsp()
256        .await
257        .map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
258
259    let items = client
260        .query_relations(&req.file, req.line, req.col, req.mode)
261        .await
262        .map_err(query_error)?;
263
264    Ok(Json(items))
265}
266
267fn invalid_json_request(operation: &str, error: JsonRejection) -> (StatusCode, String) {
268    (
269        StatusCode::BAD_REQUEST,
270        format!("Invalid {operation} request: {error}"),
271    )
272}
273
274fn query_error(error: anyhow::Error) -> (StatusCode, String) {
275    let message = error.to_string();
276    if message.contains("-32601") {
277        return (
278            StatusCode::NOT_IMPLEMENTED,
279            format!("rust-analyzer does not support this navigation capability: {message}"),
280        );
281    }
282    if message.starts_with("Invalid ")
283        || message.contains("must be at least")
284        || message.contains("outside workspace")
285        || message.contains("Failed to resolve Rust source file")
286    {
287        return (StatusCode::BAD_REQUEST, message);
288    }
289    (StatusCode::INTERNAL_SERVER_ERROR, message)
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295
296    #[test]
297    fn test_daemon_started_payload_is_machine_readable() {
298        let payload = daemon_started_payload(
299            SocketAddr::from(([127, 0, 0, 1], 60094)),
300            Path::new("workspace"),
301        );
302        let encoded = serde_json::to_string(&payload).unwrap();
303        let decoded: serde_json::Value = serde_json::from_str(&encoded).unwrap();
304        assert_eq!(decoded["success"], true);
305        assert_eq!(decoded["address"], "http://127.0.0.1:60094");
306    }
307
308    #[test]
309    fn test_query_error_preserves_actionable_status_categories() {
310        let (status, message) = query_error(anyhow::anyhow!(
311            "Invalid Rust symbol kind 'fn'. Allowed values: function"
312        ));
313        assert_eq!(status, StatusCode::BAD_REQUEST);
314        assert!(message.contains("function"));
315
316        let (status, message) = query_error(anyhow::anyhow!(
317            "LSP error: {{\"code\":-32601,\"message\":\"unsupported\"}}"
318        ));
319        assert_eq!(status, StatusCode::NOT_IMPLEMENTED);
320        assert!(message.contains("does not support"));
321
322        let (status, message) = query_error(anyhow::anyhow!("unexpected reader failure"));
323        assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
324        assert_eq!(message, "unexpected reader failure");
325    }
326}