Skip to main content

rust_analyzer_cli/lsp/
types.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3use std::path::PathBuf;
4use std::str::FromStr;
5
6#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
7#[serde(rename_all = "snake_case")]
8pub enum DaemonState {
9    Starting,
10    Ready,
11    Failed,
12}
13
14#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
15#[serde(rename_all = "snake_case")]
16pub enum RustSymbolKind {
17    Module,
18    Struct,
19    Enum,
20    EnumVariant,
21    Trait,
22    Impl,
23    Function,
24    Method,
25    AssociatedFunction,
26    Field,
27    TypeAlias,
28    Const,
29    Static,
30    Macro,
31    Union,
32    TypeParameter,
33    Variable,
34    Unknown,
35}
36
37impl fmt::Display for RustSymbolKind {
38    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39        let value = match self {
40            Self::Module => "module",
41            Self::Struct => "struct",
42            Self::Enum => "enum",
43            Self::EnumVariant => "enum_variant",
44            Self::Trait => "trait",
45            Self::Impl => "impl",
46            Self::Function => "function",
47            Self::Method => "method",
48            Self::AssociatedFunction => "associated_function",
49            Self::Field => "field",
50            Self::TypeAlias => "type_alias",
51            Self::Const => "const",
52            Self::Static => "static",
53            Self::Macro => "macro",
54            Self::Union => "union",
55            Self::TypeParameter => "type_parameter",
56            Self::Variable => "variable",
57            Self::Unknown => "unknown",
58        };
59        f.write_str(value)
60    }
61}
62
63impl RustSymbolKind {
64    pub const CLI_VALUES: &'static [&'static str] = &[
65        "module",
66        "struct",
67        "enum",
68        "enum_variant",
69        "trait",
70        "impl",
71        "function",
72        "method",
73        "associated_function",
74        "field",
75        "type_alias",
76        "const",
77        "static",
78        "macro",
79        "union",
80        "type_parameter",
81        "variable",
82    ];
83
84    pub fn parse_filter(value: &str) -> Option<Self> {
85        match value {
86            "module" => Some(Self::Module),
87            "struct" => Some(Self::Struct),
88            "enum" => Some(Self::Enum),
89            "enum_variant" => Some(Self::EnumVariant),
90            "trait" => Some(Self::Trait),
91            "impl" => Some(Self::Impl),
92            "function" => Some(Self::Function),
93            "method" => Some(Self::Method),
94            "associated_function" => Some(Self::AssociatedFunction),
95            "field" => Some(Self::Field),
96            "type_alias" => Some(Self::TypeAlias),
97            "const" => Some(Self::Const),
98            "static" => Some(Self::Static),
99            "macro" => Some(Self::Macro),
100            "union" => Some(Self::Union),
101            "type_parameter" => Some(Self::TypeParameter),
102            "variable" => Some(Self::Variable),
103            _ => None,
104        }
105    }
106}
107
108#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
109#[serde(rename_all = "lowercase")]
110pub enum CallDirection {
111    Incoming,
112    Outgoing,
113}
114
115impl fmt::Display for CallDirection {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.write_str(match self {
118            Self::Incoming => "incoming",
119            Self::Outgoing => "outgoing",
120        })
121    }
122}
123
124impl FromStr for CallDirection {
125    type Err = String;
126
127    fn from_str(value: &str) -> Result<Self, Self::Err> {
128        match value {
129            "incoming" => Ok(Self::Incoming),
130            "outgoing" => Ok(Self::Outgoing),
131            _ => Err(format!(
132                "invalid call direction '{value}'; allowed values: incoming, outgoing"
133            )),
134        }
135    }
136}
137
138#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
139#[serde(rename_all = "snake_case")]
140pub enum RelationMode {
141    Implementations,
142}
143
144impl fmt::Display for RelationMode {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        f.write_str(match self {
147            Self::Implementations => "implementations",
148        })
149    }
150}
151
152impl FromStr for RelationMode {
153    type Err = String;
154
155    fn from_str(value: &str) -> Result<Self, Self::Err> {
156        match value {
157            "implementations" => Ok(Self::Implementations),
158            _ => Err(format!(
159                "invalid relation mode '{value}'; allowed values: implementations"
160            )),
161        }
162    }
163}
164
165#[derive(Debug, Serialize, Deserialize)]
166pub struct DaemonStatusResponse {
167    pub state: DaemonState,
168    pub workspace_root: String,
169    pub process_id: Option<u32>,
170    pub error: Option<String>,
171}
172
173#[derive(Debug, Serialize, Deserialize)]
174pub struct SymbolQueryRequest {
175    pub name: String,
176    pub kind: String,
177    pub exact: bool,
178    #[serde(default)]
179    pub include_body: bool,
180    #[serde(default = "default_body_max_lines")]
181    pub max_lines: usize,
182}
183
184#[derive(Debug, Serialize, Deserialize)]
185pub struct SymbolItem {
186    pub name: String,
187    pub kind: RustSymbolKind,
188    pub file: String,
189    pub line: u32,
190    pub col: u32,
191    pub container_name: Option<String>,
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub body: Option<String>,
194}
195
196#[derive(Debug, Serialize, Deserialize)]
197pub struct OutlineQueryRequest {
198    pub file: PathBuf,
199    #[serde(default)]
200    pub include_body: bool,
201    #[serde(default = "default_body_max_lines")]
202    pub max_lines: usize,
203}
204
205#[derive(Debug, Serialize, Deserialize)]
206pub struct OutlineItem {
207    pub name: String,
208    pub kind: RustSymbolKind,
209    pub detail: Option<String>,
210    pub line: u32,
211    pub col: u32,
212    pub end_line: u32,
213    pub children: Vec<OutlineItem>,
214    #[serde(skip_serializing_if = "Option::is_none")]
215    pub body: Option<String>,
216}
217
218#[derive(Debug, Serialize, Deserialize)]
219pub struct DefinitionQueryRequest {
220    pub file: PathBuf,
221    pub line: u32,
222    pub col: u32,
223    #[serde(default)]
224    pub include_body: bool,
225    #[serde(default = "default_body_max_lines")]
226    pub max_lines: usize,
227}
228
229#[derive(Debug, Serialize, Deserialize)]
230pub struct BodyQueryRequest {
231    pub file: PathBuf,
232    pub line: u32,
233    pub col: u32,
234    #[serde(default = "default_body_max_lines")]
235    pub max_lines: usize,
236}
237
238// Keep body limits consistent across all daemon endpoints unless the caller opts into unlimited output.
239fn default_body_max_lines() -> usize {
240    100
241}
242
243fn default_call_depth() -> u32 {
244    1
245}
246
247#[derive(Debug, Serialize, Deserialize)]
248pub struct DefinitionItem {
249    pub file: String,
250    pub line: u32,
251    pub col: u32,
252    pub end_line: u32,
253    pub end_col: u32,
254    pub snippet: Option<String>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub body: Option<String>,
257}
258
259#[derive(Debug, Serialize, Deserialize)]
260pub struct RelationQueryRequest {
261    pub file: PathBuf,
262    pub line: u32,
263    pub col: u32,
264    pub mode: RelationMode,
265}
266
267#[derive(Debug, Serialize, Deserialize)]
268pub struct RelationItem {
269    pub file: String,
270    pub line: u32,
271    pub col: u32,
272    pub end_line: u32,
273    pub end_col: u32,
274}
275
276#[derive(Debug, Serialize, Deserialize)]
277pub struct ReferenceQueryRequest {
278    pub file: PathBuf,
279    pub line: u32,
280    pub col: u32,
281}
282
283#[derive(Debug, Serialize, Deserialize)]
284pub struct CallQueryRequest {
285    pub file: PathBuf,
286    pub line: u32,
287    pub col: u32,
288    pub direction: CallDirection,
289    #[serde(default = "default_call_depth")]
290    pub depth: u32,
291}
292
293#[derive(Debug, Serialize, Deserialize)]
294pub struct ReferenceItem {
295    pub file: String,
296    pub line: u32,
297    pub col: u32,
298    pub end_line: u32,
299    pub end_col: u32,
300}
301
302#[derive(Debug, Serialize, Deserialize)]
303pub struct CallItem {
304    pub name: String,
305    pub kind: RustSymbolKind,
306    pub file: String,
307    pub line: u32,
308    pub col: u32,
309    pub end_line: u32,
310    pub end_col: u32,
311    pub direction: CallDirection,
312    pub depth: u32,
313}
314
315#[derive(Debug, Serialize, Deserialize)]
316pub struct BodyItem {
317    pub file: String,
318    pub line: u32,
319    pub col: u32,
320    pub end_line: u32,
321    pub end_col: u32,
322    pub body: String,
323    pub total_lines: usize,
324    pub is_truncated: bool,
325}
326
327#[derive(Debug, Serialize, Deserialize)]
328pub struct HoverQueryRequest {
329    pub file: PathBuf,
330    pub line: u32,
331    pub col: u32,
332}
333
334#[derive(Debug, Serialize, Deserialize)]
335pub struct HoverRange {
336    pub start_line: u32,
337    pub start_col: u32,
338    pub end_line: u32,
339    pub end_col: u32,
340}
341
342#[derive(Debug, Serialize, Deserialize)]
343pub struct HoverItem {
344    pub file: String,
345    pub line: u32,
346    pub col: u32,
347    pub contents: String,
348    #[serde(skip_serializing_if = "Option::is_none")]
349    pub range: Option<HoverRange>,
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_daemon_status_json_roundtrip() {
358        let status = DaemonStatusResponse {
359            state: DaemonState::Ready,
360            workspace_root: "/path/to/workspace".to_string(),
361            process_id: Some(12345),
362            error: None,
363        };
364        let json = serde_json::to_string(&status).unwrap();
365        let decoded: DaemonStatusResponse = serde_json::from_str(&json).unwrap();
366        assert_eq!(decoded.state, DaemonState::Ready);
367        assert_eq!(decoded.process_id, Some(12345));
368        assert_eq!(decoded.workspace_root, "/path/to/workspace");
369    }
370
371    #[test]
372    fn test_symbol_item_json_roundtrip() {
373        let item = SymbolItem {
374            name: "ExampleStruct".to_string(),
375            kind: RustSymbolKind::Struct,
376            file: "tests/code_example.rs".to_string(),
377            line: 15,
378            col: 1,
379            container_name: Some("lsp".to_string()),
380            body: None,
381        };
382
383        let json = serde_json::to_string(&item).unwrap();
384        let decoded: SymbolItem = serde_json::from_str(&json).unwrap();
385        assert_eq!(decoded.name, "ExampleStruct");
386        assert_eq!(decoded.kind, RustSymbolKind::Struct);
387    }
388
389    #[test]
390    fn test_body_query_request_defaults_to_100_lines() {
391        let request: BodyQueryRequest =
392            serde_json::from_str(r#"{"file":"tests/code_example.rs","line":15,"col":10}"#).unwrap();
393        assert_eq!(request.max_lines, 100);
394    }
395
396    #[test]
397    fn test_hover_item_json_roundtrip() {
398        let item = HoverItem {
399            file: "tests/code_example.rs".to_string(),
400            line: 155,
401            col: 5,
402            contents: "Runs a long example.".to_string(),
403            range: Some(HoverRange {
404                start_line: 155,
405                start_col: 5,
406                end_line: 155,
407                end_col: 17,
408            }),
409        };
410
411        let json = serde_json::to_string(&item).unwrap();
412        let decoded: HoverItem = serde_json::from_str(&json).unwrap();
413        assert_eq!(decoded.contents, "Runs a long example.");
414        assert_eq!(decoded.range.unwrap().start_line, 155);
415    }
416
417    #[test]
418    fn test_symbol_kind_filter_uses_canonical_values() {
419        assert_eq!(
420            RustSymbolKind::parse_filter("function"),
421            Some(RustSymbolKind::Function)
422        );
423        assert_eq!(
424            RustSymbolKind::parse_filter("trait"),
425            Some(RustSymbolKind::Trait)
426        );
427        assert_eq!(RustSymbolKind::parse_filter("fn"), None);
428        assert_eq!(RustSymbolKind::parse_filter("var"), None);
429    }
430
431    #[test]
432    fn test_navigation_modes_use_stable_wire_values() {
433        assert_eq!(CallDirection::Outgoing.to_string(), "outgoing");
434        assert_eq!(
435            serde_json::to_string(&CallDirection::Incoming).unwrap(),
436            "\"incoming\""
437        );
438        assert_eq!(
439            serde_json::from_str::<RelationMode>("\"implementations\"").unwrap(),
440            RelationMode::Implementations
441        );
442        assert!("invalid".parse::<CallDirection>().is_err());
443        assert!("invalid".parse::<RelationMode>().is_err());
444        let call_request: CallQueryRequest = serde_json::from_str(
445            r#"{"file":"tests/code_example.rs","line":1,"col":1,"direction":"incoming"}"#,
446        )
447        .unwrap();
448        assert_eq!(call_request.depth, 1);
449    }
450}