Skip to main content

rust_analyzer_cli/lsp/
types.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4#[derive(Debug, Serialize, Deserialize)]
5pub struct DaemonStatusResponse {
6    pub ready: bool,
7    pub indexed_at: Option<String>,
8    pub workspace_root: String,
9    pub process_id: u32,
10}
11
12#[derive(Debug, Serialize, Deserialize)]
13pub struct SymbolQueryRequest {
14    pub name: String,
15    pub kind: String,
16    pub exact: bool,
17}
18
19#[derive(Debug, Serialize, Deserialize)]
20pub struct SymbolItem {
21    pub name: String,
22    pub kind: String,
23    pub file: String,
24    pub line: u32,
25    pub col: u32,
26    pub container_name: Option<String>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub body: Option<String>,
29}
30
31#[derive(Debug, Serialize, Deserialize)]
32pub struct OutlineQueryRequest {
33    pub file: PathBuf,
34}
35
36#[derive(Debug, Serialize, Deserialize)]
37pub struct OutlineItem {
38    pub name: String,
39    pub kind: String,
40    pub detail: Option<String>,
41    pub line: u32,
42    pub col: u32,
43    pub end_line: u32,
44    pub children: Vec<OutlineItem>,
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub body: Option<String>,
47}
48
49#[derive(Debug, Serialize, Deserialize)]
50pub struct DefinitionQueryRequest {
51    pub file: PathBuf,
52    pub line: u32,
53    pub col: u32,
54}
55
56#[derive(Debug, Serialize, Deserialize)]
57pub struct BodyQueryRequest {
58    pub file: PathBuf,
59    pub line: u32,
60    pub col: u32,
61    #[serde(default = "default_body_max_lines")]
62    pub max_lines: usize,
63}
64
65// Keep the daemon default aligned with the CLI so large bodies are bounded unless requested.
66fn default_body_max_lines() -> usize {
67    100
68}
69
70#[derive(Debug, Serialize, Deserialize)]
71pub struct DefinitionItem {
72    pub file: String,
73    pub line: u32,
74    pub col: u32,
75    pub end_line: u32,
76    pub end_col: u32,
77    pub snippet: Option<String>,
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub body: Option<String>,
80}
81
82#[derive(Debug, Serialize, Deserialize)]
83pub struct CursorQueryRequest {
84    pub file: PathBuf,
85    pub line: u32,
86    pub col: u32,
87    pub mode: String, // "incoming", "outgoing", "references"
88    pub depth: u32,
89}
90
91#[derive(Debug, Serialize, Deserialize)]
92pub struct CursorItem {
93    pub name: String,
94    pub kind: String,
95    pub file: String,
96    pub line: u32,
97    pub col: u32,
98    pub caller_or_callee: Option<String>,
99}
100
101#[derive(Debug, Serialize, Deserialize)]
102pub struct TypeHierarchyQueryRequest {
103    pub file: PathBuf,
104    pub line: u32,
105    pub col: u32,
106    pub mode: String, // "supertypes", "subtypes"
107    pub depth: u32,
108}
109
110#[derive(Debug, Serialize, Deserialize)]
111pub struct TypeHierarchyItemResult {
112    pub name: String,
113    pub kind: String,
114    pub file: String,
115    pub line: u32,
116    pub col: u32,
117    pub detail: Option<String>,
118}
119
120#[derive(Debug, Serialize, Deserialize)]
121pub struct BodyItem {
122    pub file: String,
123    pub line: u32,
124    pub col: u32,
125    pub end_line: u32,
126    pub end_col: u32,
127    pub body: String,
128    pub total_lines: usize,
129    pub is_truncated: bool,
130}
131
132#[derive(Debug, Serialize, Deserialize)]
133pub struct HoverQueryRequest {
134    pub file: PathBuf,
135    pub line: u32,
136    pub col: u32,
137}
138
139#[derive(Debug, Serialize, Deserialize)]
140pub struct HoverRange {
141    pub start_line: u32,
142    pub start_col: u32,
143    pub end_line: u32,
144    pub end_col: u32,
145}
146
147#[derive(Debug, Serialize, Deserialize)]
148pub struct HoverItem {
149    pub file: String,
150    pub line: u32,
151    pub col: u32,
152    pub contents: String,
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub range: Option<HoverRange>,
155}
156
157#[derive(Debug, Serialize, Deserialize)]
158pub struct CheckQueryRequest {
159    pub target: Option<String>,
160}
161
162#[derive(Debug, Serialize, Deserialize)]
163pub struct CheckDiagnosticItem {
164    pub message: String,
165    pub level: String, // "error", "warning"
166    pub file: Option<String>,
167    pub line: Option<u32>,
168    pub col: Option<u32>,
169}
170
171#[derive(Debug, Serialize, Deserialize)]
172pub struct CheckResponse {
173    pub success: bool,
174    pub diagnostics: Vec<CheckDiagnosticItem>,
175}
176
177#[cfg(test)]
178mod tests {
179    use super::*;
180
181    #[test]
182    fn test_daemon_status_json_roundtrip() {
183        let status = DaemonStatusResponse {
184            ready: true,
185            indexed_at: Some("2026-07-31T10:00:00Z".to_string()),
186            workspace_root: "/path/to/workspace".to_string(),
187            process_id: 12345,
188        };
189        let json = serde_json::to_string(&status).unwrap();
190        let decoded: DaemonStatusResponse = serde_json::from_str(&json).unwrap();
191        assert!(decoded.ready);
192        assert_eq!(decoded.process_id, 12345);
193        assert_eq!(decoded.workspace_root, "/path/to/workspace");
194    }
195
196    #[test]
197    fn test_symbol_item_json_roundtrip() {
198        let item = SymbolItem {
199            name: "ExampleStruct".to_string(),
200            kind: "struct".to_string(),
201            file: "tests/code_example.rs".to_string(),
202            line: 15,
203            col: 1,
204            container_name: Some("lsp".to_string()),
205            body: None,
206        };
207
208        let json = serde_json::to_string(&item).unwrap();
209        let decoded: SymbolItem = serde_json::from_str(&json).unwrap();
210        assert_eq!(decoded.name, "ExampleStruct");
211        assert_eq!(decoded.kind, "struct");
212    }
213
214    #[test]
215    fn test_body_query_request_defaults_to_100_lines() {
216        let request: BodyQueryRequest =
217            serde_json::from_str(r#"{"file":"tests/code_example.rs","line":15,"col":10}"#).unwrap();
218        assert_eq!(request.max_lines, 100);
219    }
220
221    #[test]
222    fn test_hover_item_json_roundtrip() {
223        let item = HoverItem {
224            file: "tests/code_example.rs".to_string(),
225            line: 155,
226            col: 5,
227            contents: "Runs a long example.".to_string(),
228            range: Some(HoverRange {
229                start_line: 155,
230                start_col: 5,
231                end_line: 155,
232                end_col: 17,
233            }),
234        };
235
236        let json = serde_json::to_string(&item).unwrap();
237        let decoded: HoverItem = serde_json::from_str(&json).unwrap();
238        assert_eq!(decoded.contents, "Runs a long example.");
239        assert_eq!(decoded.range.unwrap().start_line, 155);
240    }
241}