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
65fn 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, 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, 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, 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}