Skip to main content

leta_types/
rpc.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4use crate::{
5    CacheInfo, CallNode, FileInfo, LocationInfo, SymbolInfo, WorkspaceInfo, DEFAULT_HEAD_LIMIT,
6};
7
8// Streaming message types for NDJSON protocol
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(tag = "type", rename_all = "snake_case")]
11pub enum StreamMessage {
12    Symbol(SymbolInfo),
13    File(FileInfo),
14    ExcludedDir { path: String },
15    Done(StreamDone),
16    Error { message: String },
17}
18
19#[derive(Debug, Clone, Default, Serialize, Deserialize)]
20pub struct StreamDone {
21    #[serde(skip_serializing_if = "Option::is_none")]
22    pub warning: Option<String>,
23    pub truncated: bool,
24    pub total_count: u32,
25    #[serde(skip_serializing_if = "Option::is_none")]
26    pub profiling: Option<ProfilingData>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct FunctionStats {
31    pub name: String,
32    pub calls: u32,
33    pub total_us: u64,
34    pub avg_us: u64,
35    pub p90_us: u64,
36    pub max_us: u64,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct SpanNode {
41    pub name: String,
42    pub self_us: u64,
43    pub total_us: u64,
44    pub calls: u32,
45    pub children: Vec<SpanNode>,
46    pub is_parallel: bool,
47    #[serde(default, skip_serializing_if = "Vec::is_empty")]
48    pub properties: Vec<(String, String)>,
49}
50
51#[derive(Debug, Clone, Default, Serialize, Deserialize)]
52pub struct SpanTree {
53    pub roots: Vec<SpanNode>,
54    pub total_us: u64,
55    pub functions: Vec<FunctionStats>,
56}
57
58#[derive(Debug, Clone, Default, Serialize, Deserialize)]
59pub struct CacheStats {
60    pub symbol_hits: u32,
61    pub symbol_misses: u32,
62    pub hover_hits: u32,
63    pub hover_misses: u32,
64}
65
66impl CacheStats {
67    pub fn symbol_hit_rate(&self) -> f64 {
68        let total = self.symbol_hits + self.symbol_misses;
69        if total == 0 {
70            0.0
71        } else {
72            self.symbol_hits as f64 / total as f64 * 100.0
73        }
74    }
75
76    pub fn hover_hit_rate(&self) -> f64 {
77        let total = self.hover_hits + self.hover_misses;
78        if total == 0 {
79            0.0
80        } else {
81            self.hover_hits as f64 / total as f64 * 100.0
82        }
83    }
84}
85
86#[derive(Debug, Clone, Default, Serialize, Deserialize)]
87pub struct ProfilingData {
88    pub functions: Vec<FunctionStats>,
89    pub cache: CacheStats,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub span_tree: Option<SpanTree>,
92}
93
94#[derive(Debug, Clone, Default, Serialize, Deserialize)]
95pub struct ServerStartupStats {
96    pub server_name: String,
97    pub workspace_root: String,
98    pub init_time_ms: u64,
99    pub ready_time_ms: u64,
100    pub total_time_ms: u64,
101    pub functions: Vec<FunctionStats>,
102}
103
104#[derive(Debug, Clone, Default, Serialize, Deserialize)]
105pub struct ServerIndexingStats {
106    pub server_name: String,
107    pub file_count: u32,
108    pub total_time_ms: u64,
109    pub functions: Vec<FunctionStats>,
110    pub cache: CacheStats,
111}
112
113#[derive(Debug, Clone, Default, Serialize, Deserialize)]
114pub struct WorkspaceProfilingData {
115    pub workspace_root: String,
116    pub total_files: u32,
117    pub total_time_ms: u64,
118    pub server_profiles: Vec<ServerProfilingData>,
119}
120
121#[derive(Debug, Clone, Default, Serialize, Deserialize)]
122pub struct ServerProfilingData {
123    pub server_name: String,
124    pub startup: Option<ServerStartupStats>,
125    pub indexing: Option<ServerIndexingStats>,
126}
127
128// ============================================================================
129// RPC Protocol
130// ============================================================================
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
133pub struct RpcRequest<P> {
134    pub method: String,
135    pub params: P,
136    #[serde(default)]
137    pub profile: bool,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct RpcSuccessResponse<R> {
142    pub result: R,
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub profiling: Option<ProfilingData>,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
148#[serde(untagged)]
149pub enum RpcResponse<R> {
150    Success(RpcSuccessResponse<R>),
151    Error { error: String },
152}
153
154impl<R> RpcResponse<R> {
155    pub fn success(result: R) -> Self {
156        RpcResponse::Success(RpcSuccessResponse {
157            result,
158            profiling: None,
159        })
160    }
161
162    pub fn success_with_profiling(result: R, profiling: ProfilingData) -> Self {
163        let profiling = if profiling.functions.is_empty() {
164            None
165        } else {
166            Some(profiling)
167        };
168        RpcResponse::Success(RpcSuccessResponse { result, profiling })
169    }
170
171    pub fn error(message: impl Into<String>) -> Self {
172        RpcResponse::Error {
173            error: message.into(),
174        }
175    }
176
177    pub fn into_result(self) -> Result<R, String> {
178        match self {
179            RpcResponse::Success(s) => Ok(s.result),
180            RpcResponse::Error { error } => Err(error),
181        }
182    }
183}
184
185// ============================================================================
186// Shutdown
187// ============================================================================
188
189#[derive(Debug, Clone, Default, Serialize, Deserialize)]
190pub struct ShutdownParams {}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct ShutdownResult {
194    pub status: String,
195}
196
197// ============================================================================
198// Describe Session
199// ============================================================================
200
201#[derive(Debug, Clone, Default, Serialize, Deserialize)]
202pub struct DescribeSessionParams {
203    #[serde(default)]
204    pub include_profiling: bool,
205}
206
207#[derive(Debug, Clone, Serialize, Deserialize)]
208pub struct DescribeSessionResult {
209    pub daemon_pid: u32,
210    pub caches: HashMap<String, CacheInfo>,
211    pub workspaces: Vec<WorkspaceInfo>,
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub profiling: Option<Vec<WorkspaceProfilingData>>,
214}
215
216// ============================================================================
217// Grep
218// ============================================================================
219
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct GrepParams {
222    pub workspace_root: String,
223    #[serde(default = "default_pattern")]
224    pub pattern: String,
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub kinds: Option<Vec<String>>,
227    #[serde(default)]
228    pub case_sensitive: bool,
229    #[serde(default)]
230    pub include_docs: bool,
231    #[serde(skip_serializing_if = "Option::is_none")]
232    pub path_pattern: Option<String>,
233    #[serde(default)]
234    pub exclude_patterns: Vec<String>,
235    #[serde(default = "default_head_limit")]
236    pub limit: u32,
237}
238
239fn default_head_limit() -> u32 {
240    DEFAULT_HEAD_LIMIT
241}
242
243fn default_pattern() -> String {
244    ".*".to_string()
245}
246
247#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct GrepResult {
249    #[serde(default)]
250    pub symbols: Vec<SymbolInfo>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub warning: Option<String>,
253    #[serde(default)]
254    pub truncated: bool,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub total_count: Option<u32>,
257}
258
259// ============================================================================
260// Files
261// ============================================================================
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
264pub struct FilesParams {
265    pub workspace_root: String,
266    #[serde(skip_serializing_if = "Option::is_none")]
267    pub subpath: Option<String>,
268    #[serde(default)]
269    pub exclude_patterns: Vec<String>,
270    #[serde(default)]
271    pub include_patterns: Vec<String>,
272    #[serde(skip_serializing_if = "Option::is_none")]
273    pub filter_pattern: Option<String>,
274    #[serde(default = "default_head_limit")]
275    pub head: u32,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct FilesResult {
280    pub files: HashMap<String, FileInfo>,
281    pub total_files: u32,
282    pub total_bytes: u64,
283    pub total_lines: u32,
284    #[serde(default)]
285    pub excluded_dirs: Vec<String>,
286    #[serde(default)]
287    pub truncated: bool,
288}
289
290// ============================================================================
291// Show
292// ============================================================================
293
294#[derive(Debug, Clone, Serialize, Deserialize)]
295pub struct ShowParams {
296    pub workspace_root: String,
297    pub path: String,
298    pub line: u32,
299    #[serde(default)]
300    pub column: u32,
301    #[serde(default)]
302    pub context: u32,
303    #[serde(skip_serializing_if = "Option::is_none")]
304    pub head: Option<u32>,
305    #[serde(skip_serializing_if = "Option::is_none", alias = "symbol")]
306    pub symbol_name: Option<String>,
307    #[serde(skip_serializing_if = "Option::is_none", alias = "kind")]
308    pub symbol_kind: Option<String>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub range_start_line: Option<u32>,
311    #[serde(skip_serializing_if = "Option::is_none")]
312    pub range_end_line: Option<u32>,
313    #[serde(default)]
314    pub direct_location: bool,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct ShowResult {
319    pub path: String,
320    pub start_line: u32,
321    pub end_line: u32,
322    pub content: String,
323    #[serde(skip_serializing_if = "Option::is_none")]
324    pub symbol: Option<String>,
325    #[serde(default)]
326    pub truncated: bool,
327    #[serde(skip_serializing_if = "Option::is_none")]
328    pub total_lines: Option<u32>,
329}
330
331// ============================================================================
332// References
333// ============================================================================
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct ReferencesParams {
337    pub workspace_root: String,
338    pub path: String,
339    pub line: u32,
340    #[serde(default)]
341    pub column: u32,
342    #[serde(default)]
343    pub context: u32,
344    #[serde(default = "default_head_limit")]
345    pub head: u32,
346}
347
348#[derive(Debug, Clone, Serialize, Deserialize)]
349pub struct ReferencesResult {
350    pub locations: Vec<LocationInfo>,
351    #[serde(default)]
352    pub truncated: bool,
353    #[serde(skip_serializing_if = "Option::is_none")]
354    pub total_count: Option<u32>,
355}
356
357// ============================================================================
358// Declaration
359// ============================================================================
360
361#[derive(Debug, Clone, Serialize, Deserialize)]
362pub struct DeclarationParams {
363    pub workspace_root: String,
364    pub path: String,
365    pub line: u32,
366    #[serde(default)]
367    pub column: u32,
368    #[serde(default)]
369    pub context: u32,
370    #[serde(default = "default_head_limit")]
371    pub head: u32,
372}
373
374#[derive(Debug, Clone, Serialize, Deserialize)]
375pub struct DeclarationResult {
376    pub locations: Vec<LocationInfo>,
377    #[serde(default)]
378    pub truncated: bool,
379    #[serde(skip_serializing_if = "Option::is_none")]
380    pub total_count: Option<u32>,
381}
382
383// ============================================================================
384// Implementations
385// ============================================================================
386
387#[derive(Debug, Clone, Serialize, Deserialize)]
388pub struct ImplementationsParams {
389    pub workspace_root: String,
390    pub path: String,
391    pub line: u32,
392    #[serde(default)]
393    pub column: u32,
394    #[serde(default)]
395    pub context: u32,
396    #[serde(default = "default_head_limit")]
397    pub head: u32,
398}
399
400#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct ImplementationsResult {
402    #[serde(default)]
403    pub locations: Vec<LocationInfo>,
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub error: Option<String>,
406    #[serde(default)]
407    pub truncated: bool,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub total_count: Option<u32>,
410}
411
412// ============================================================================
413// Subtypes
414// ============================================================================
415
416#[derive(Debug, Clone, Serialize, Deserialize)]
417pub struct SubtypesParams {
418    pub workspace_root: String,
419    pub path: String,
420    pub line: u32,
421    #[serde(default)]
422    pub column: u32,
423    #[serde(default)]
424    pub context: u32,
425    #[serde(default = "default_head_limit")]
426    pub head: u32,
427}
428
429#[derive(Debug, Clone, Serialize, Deserialize)]
430pub struct SubtypesResult {
431    pub locations: Vec<LocationInfo>,
432    #[serde(default)]
433    pub truncated: bool,
434    #[serde(skip_serializing_if = "Option::is_none")]
435    pub total_count: Option<u32>,
436}
437
438// ============================================================================
439// Supertypes
440// ============================================================================
441
442#[derive(Debug, Clone, Serialize, Deserialize)]
443pub struct SupertypesParams {
444    pub workspace_root: String,
445    pub path: String,
446    pub line: u32,
447    #[serde(default)]
448    pub column: u32,
449    #[serde(default)]
450    pub context: u32,
451    #[serde(default = "default_head_limit")]
452    pub head: u32,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
456pub struct SupertypesResult {
457    pub locations: Vec<LocationInfo>,
458    #[serde(default)]
459    pub truncated: bool,
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub total_count: Option<u32>,
462}
463
464// ============================================================================
465// Calls
466// ============================================================================
467
468#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
469#[serde(rename_all = "lowercase")]
470pub enum CallsMode {
471    Outgoing,
472    Incoming,
473    Path,
474}
475
476#[derive(Debug, Clone, Serialize, Deserialize)]
477pub struct CallsParams {
478    pub workspace_root: String,
479    pub mode: CallsMode,
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub from_path: Option<String>,
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub from_line: Option<u32>,
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub from_column: Option<u32>,
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub from_symbol: Option<String>,
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub to_path: Option<String>,
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub to_line: Option<u32>,
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub to_column: Option<u32>,
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub to_symbol: Option<String>,
496    #[serde(default = "default_max_depth")]
497    pub max_depth: u32,
498    #[serde(default)]
499    pub include_non_workspace: bool,
500    #[serde(default = "default_head_limit")]
501    pub head: u32,
502}
503
504fn default_max_depth() -> u32 {
505    3
506}
507
508#[derive(Debug, Clone, Serialize, Deserialize)]
509pub struct CallsResult {
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub root: Option<CallNode>,
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub path: Option<Vec<CallNode>>,
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub message: Option<String>,
516    #[serde(skip_serializing_if = "Option::is_none")]
517    pub error: Option<String>,
518    #[serde(default)]
519    pub truncated: bool,
520}
521
522// ============================================================================
523// Rename
524// ============================================================================
525
526#[derive(Debug, Clone, Serialize, Deserialize)]
527pub struct RenameParams {
528    pub workspace_root: String,
529    pub path: String,
530    pub line: u32,
531    #[serde(default)]
532    pub column: u32,
533    pub new_name: String,
534}
535
536#[derive(Debug, Clone, Serialize, Deserialize)]
537pub struct RenameResult {
538    pub files_changed: Vec<String>,
539}
540
541// ============================================================================
542// Move File
543// ============================================================================
544
545#[derive(Debug, Clone, Serialize, Deserialize)]
546pub struct MoveFileParams {
547    pub workspace_root: String,
548    pub old_path: String,
549    pub new_path: String,
550}
551
552#[derive(Debug, Clone, Serialize, Deserialize)]
553pub struct MoveFileResult {
554    pub files_changed: Vec<String>,
555    pub imports_updated: bool,
556}
557
558// ============================================================================
559// Raw LSP Request
560// ============================================================================
561
562#[derive(Debug, Clone, Serialize, Deserialize)]
563pub struct RawLspRequestParams {
564    pub workspace_root: String,
565    pub method: String,
566    #[serde(default)]
567    pub params: serde_json::Value,
568    #[serde(default = "default_language")]
569    pub language: String,
570}
571
572fn default_language() -> String {
573    "python".to_string()
574}
575
576// ============================================================================
577// Workspace Management
578// ============================================================================
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct RestartWorkspaceParams {
582    pub workspace_root: String,
583}
584
585#[derive(Debug, Clone, Serialize, Deserialize)]
586pub struct RestartWorkspaceResult {
587    pub restarted: Vec<String>,
588}
589
590#[derive(Debug, Clone, Serialize, Deserialize)]
591pub struct RemoveWorkspaceParams {
592    pub workspace_root: String,
593}
594
595#[derive(Debug, Clone, Serialize, Deserialize)]
596pub struct RemoveWorkspaceResult {
597    pub servers_stopped: Vec<String>,
598}
599
600// ============================================================================
601// Add Workspace
602// ============================================================================
603
604#[derive(Debug, Clone, Serialize, Deserialize)]
605pub struct AddWorkspaceParams {
606    pub workspace_root: String,
607}
608
609#[derive(Debug, Clone, Serialize, Deserialize)]
610pub struct AddWorkspaceResult {
611    pub added: bool,
612    pub workspace_root: String,
613    pub message: String,
614}
615
616// ============================================================================
617// Resolve Symbol
618// ============================================================================
619
620#[derive(Debug, Clone, Serialize, Deserialize)]
621pub struct ResolveSymbolParams {
622    pub workspace_root: String,
623    pub symbol_path: String,
624}
625
626#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct ResolveSymbolResult {
628    #[serde(skip_serializing_if = "Option::is_none")]
629    pub path: Option<String>,
630    #[serde(skip_serializing_if = "Option::is_none")]
631    pub line: Option<u32>,
632    #[serde(skip_serializing_if = "Option::is_none")]
633    pub column: Option<u32>,
634    #[serde(skip_serializing_if = "Option::is_none")]
635    pub name: Option<String>,
636    #[serde(skip_serializing_if = "Option::is_none")]
637    pub kind: Option<String>,
638    #[serde(skip_serializing_if = "Option::is_none")]
639    pub container: Option<String>,
640    #[serde(skip_serializing_if = "Option::is_none")]
641    pub range_start_line: Option<u32>,
642    #[serde(skip_serializing_if = "Option::is_none")]
643    pub range_end_line: Option<u32>,
644    #[serde(skip_serializing_if = "Option::is_none")]
645    pub error: Option<String>,
646    #[serde(skip_serializing_if = "Option::is_none")]
647    pub matches: Option<Vec<SymbolInfo>>,
648    #[serde(skip_serializing_if = "Option::is_none")]
649    pub total_matches: Option<u32>,
650}
651
652#[derive(Default)]
653pub struct ResolveSymbolResultBuilder {
654    pub path: String,
655    pub line: u32,
656    pub column: u32,
657    pub name: Option<String>,
658    pub kind: Option<String>,
659    pub container: Option<String>,
660    pub range_start_line: Option<u32>,
661    pub range_end_line: Option<u32>,
662}
663
664impl ResolveSymbolResult {
665    pub fn success(builder: ResolveSymbolResultBuilder) -> Self {
666        Self {
667            path: Some(builder.path),
668            line: Some(builder.line),
669            column: Some(builder.column),
670            name: builder.name,
671            kind: builder.kind,
672            container: builder.container,
673            range_start_line: builder.range_start_line,
674            range_end_line: builder.range_end_line,
675            error: None,
676            matches: None,
677            total_matches: None,
678        }
679    }
680
681    pub fn not_found(symbol: &str) -> Self {
682        Self {
683            error: Some(format!("Symbol '{}' not found", symbol)),
684            path: None,
685            line: None,
686            column: None,
687            name: None,
688            kind: None,
689            container: None,
690            range_start_line: None,
691            range_end_line: None,
692            matches: None,
693            total_matches: None,
694        }
695    }
696
697    pub fn ambiguous(symbol: &str, matches: Vec<SymbolInfo>, total: u32) -> Self {
698        Self {
699            error: Some(format!(
700                "Symbol '{}' is ambiguous ({} matches)",
701                symbol, total
702            )),
703            matches: Some(matches),
704            total_matches: Some(total),
705            path: None,
706            line: None,
707            column: None,
708            name: None,
709            kind: None,
710            container: None,
711            range_start_line: None,
712            range_end_line: None,
713        }
714    }
715}