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(skip_serializing_if = "Option::is_none")]
230    pub path_pattern: Option<String>,
231    #[serde(default)]
232    pub exclude_patterns: Vec<String>,
233    #[serde(default = "default_head_limit")]
234    pub limit: u32,
235}
236
237fn default_head_limit() -> u32 {
238    DEFAULT_HEAD_LIMIT
239}
240
241fn default_pattern() -> String {
242    ".*".to_string()
243}
244
245#[derive(Debug, Clone, Serialize, Deserialize)]
246pub struct GrepResult {
247    #[serde(default)]
248    pub symbols: Vec<SymbolInfo>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub warning: Option<String>,
251    #[serde(default)]
252    pub truncated: bool,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub total_count: Option<u32>,
255}
256
257// ============================================================================
258// Files
259// ============================================================================
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct FilesParams {
263    pub workspace_root: String,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub subpath: Option<String>,
266    #[serde(default)]
267    pub exclude_patterns: Vec<String>,
268    #[serde(default)]
269    pub include_patterns: Vec<String>,
270    #[serde(skip_serializing_if = "Option::is_none")]
271    pub filter_pattern: Option<String>,
272    #[serde(default = "default_head_limit")]
273    pub head: u32,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct FilesResult {
278    pub files: HashMap<String, FileInfo>,
279    pub total_files: u32,
280    pub total_bytes: u64,
281    pub total_lines: u32,
282    #[serde(default)]
283    pub excluded_dirs: Vec<String>,
284    #[serde(default)]
285    pub truncated: bool,
286}
287
288// ============================================================================
289// Show
290// ============================================================================
291
292#[derive(Debug, Clone, Serialize, Deserialize)]
293pub struct ShowParams {
294    pub workspace_root: String,
295    pub path: String,
296    pub line: u32,
297    #[serde(default)]
298    pub column: u32,
299    #[serde(default)]
300    pub context: u32,
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub head: Option<u32>,
303    #[serde(skip_serializing_if = "Option::is_none", alias = "symbol")]
304    pub symbol_name: Option<String>,
305    #[serde(skip_serializing_if = "Option::is_none", alias = "kind")]
306    pub symbol_kind: Option<String>,
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub range_start_line: Option<u32>,
309    #[serde(skip_serializing_if = "Option::is_none")]
310    pub range_end_line: Option<u32>,
311    #[serde(default)]
312    pub direct_location: bool,
313}
314
315#[derive(Debug, Clone, Serialize, Deserialize)]
316pub struct ShowResult {
317    pub path: String,
318    pub start_line: u32,
319    pub end_line: u32,
320    pub content: String,
321    #[serde(skip_serializing_if = "Option::is_none")]
322    pub symbol: Option<String>,
323    #[serde(default)]
324    pub truncated: bool,
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub total_lines: Option<u32>,
327}
328
329// ============================================================================
330// References
331// ============================================================================
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
334pub struct ReferencesParams {
335    pub workspace_root: String,
336    pub path: String,
337    pub line: u32,
338    #[serde(default)]
339    pub column: u32,
340    #[serde(default)]
341    pub context: u32,
342    #[serde(default = "default_head_limit")]
343    pub head: u32,
344}
345
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct ReferencesResult {
348    pub locations: Vec<LocationInfo>,
349    #[serde(default)]
350    pub truncated: bool,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub total_count: Option<u32>,
353}
354
355// ============================================================================
356// Declaration
357// ============================================================================
358
359#[derive(Debug, Clone, Serialize, Deserialize)]
360pub struct DeclarationParams {
361    pub workspace_root: String,
362    pub path: String,
363    pub line: u32,
364    #[serde(default)]
365    pub column: u32,
366    #[serde(default)]
367    pub context: u32,
368    #[serde(default = "default_head_limit")]
369    pub head: u32,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
373pub struct DeclarationResult {
374    pub locations: Vec<LocationInfo>,
375    #[serde(default)]
376    pub truncated: bool,
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub total_count: Option<u32>,
379}
380
381// ============================================================================
382// Implementations
383// ============================================================================
384
385#[derive(Debug, Clone, Serialize, Deserialize)]
386pub struct ImplementationsParams {
387    pub workspace_root: String,
388    pub path: String,
389    pub line: u32,
390    #[serde(default)]
391    pub column: u32,
392    #[serde(default)]
393    pub context: u32,
394    #[serde(default = "default_head_limit")]
395    pub head: u32,
396}
397
398#[derive(Debug, Clone, Serialize, Deserialize)]
399pub struct ImplementationsResult {
400    #[serde(default)]
401    pub locations: Vec<LocationInfo>,
402    #[serde(skip_serializing_if = "Option::is_none")]
403    pub error: Option<String>,
404    #[serde(default)]
405    pub truncated: bool,
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub total_count: Option<u32>,
408}
409
410// ============================================================================
411// Subtypes
412// ============================================================================
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
415pub struct SubtypesParams {
416    pub workspace_root: String,
417    pub path: String,
418    pub line: u32,
419    #[serde(default)]
420    pub column: u32,
421    #[serde(default)]
422    pub context: u32,
423    #[serde(default = "default_head_limit")]
424    pub head: u32,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
428pub struct SubtypesResult {
429    pub locations: Vec<LocationInfo>,
430    #[serde(default)]
431    pub truncated: bool,
432    #[serde(skip_serializing_if = "Option::is_none")]
433    pub total_count: Option<u32>,
434}
435
436// ============================================================================
437// Supertypes
438// ============================================================================
439
440#[derive(Debug, Clone, Serialize, Deserialize)]
441pub struct SupertypesParams {
442    pub workspace_root: String,
443    pub path: String,
444    pub line: u32,
445    #[serde(default)]
446    pub column: u32,
447    #[serde(default)]
448    pub context: u32,
449    #[serde(default = "default_head_limit")]
450    pub head: u32,
451}
452
453#[derive(Debug, Clone, Serialize, Deserialize)]
454pub struct SupertypesResult {
455    pub locations: Vec<LocationInfo>,
456    #[serde(default)]
457    pub truncated: bool,
458    #[serde(skip_serializing_if = "Option::is_none")]
459    pub total_count: Option<u32>,
460}
461
462// ============================================================================
463// Calls
464// ============================================================================
465
466#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
467#[serde(rename_all = "lowercase")]
468pub enum CallsMode {
469    Outgoing,
470    Incoming,
471    Path,
472}
473
474#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct CallsParams {
476    pub workspace_root: String,
477    pub mode: CallsMode,
478    #[serde(skip_serializing_if = "Option::is_none")]
479    pub from_path: Option<String>,
480    #[serde(skip_serializing_if = "Option::is_none")]
481    pub from_line: Option<u32>,
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub from_column: Option<u32>,
484    #[serde(skip_serializing_if = "Option::is_none")]
485    pub from_symbol: Option<String>,
486    #[serde(skip_serializing_if = "Option::is_none")]
487    pub to_path: Option<String>,
488    #[serde(skip_serializing_if = "Option::is_none")]
489    pub to_line: Option<u32>,
490    #[serde(skip_serializing_if = "Option::is_none")]
491    pub to_column: Option<u32>,
492    #[serde(skip_serializing_if = "Option::is_none")]
493    pub to_symbol: Option<String>,
494    #[serde(default = "default_max_depth")]
495    pub max_depth: u32,
496    #[serde(default)]
497    pub include_non_workspace: bool,
498    #[serde(default = "default_head_limit")]
499    pub head: u32,
500}
501
502fn default_max_depth() -> u32 {
503    3
504}
505
506#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct CallsResult {
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub root: Option<CallNode>,
510    #[serde(skip_serializing_if = "Option::is_none")]
511    pub path: Option<Vec<CallNode>>,
512    #[serde(skip_serializing_if = "Option::is_none")]
513    pub message: Option<String>,
514    #[serde(skip_serializing_if = "Option::is_none")]
515    pub error: Option<String>,
516    #[serde(default)]
517    pub truncated: bool,
518}
519
520// ============================================================================
521// Rename
522// ============================================================================
523
524#[derive(Debug, Clone, Serialize, Deserialize)]
525pub struct GraphParams {
526    pub workspace_root: String,
527    #[serde(default)]
528    pub include_non_workspace: bool,
529    #[serde(default)]
530    pub exclude_patterns: Vec<String>,
531    #[serde(default)]
532    pub include_patterns: Vec<String>,
533    #[serde(default)]
534    pub include_tests: bool,
535}
536
537// ============================================================================
538// Rename (symbol)
539// ============================================================================
540
541#[derive(Debug, Clone, Serialize, Deserialize)]
542pub struct RenameParams {
543    pub workspace_root: String,
544    pub path: String,
545    pub line: u32,
546    #[serde(default)]
547    pub column: u32,
548    pub new_name: String,
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize)]
552pub struct RenameResult {
553    pub files_changed: Vec<String>,
554}
555
556// ============================================================================
557// Move File
558// ============================================================================
559
560#[derive(Debug, Clone, Serialize, Deserialize)]
561pub struct MoveFileParams {
562    pub workspace_root: String,
563    pub old_path: String,
564    pub new_path: String,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
568pub struct MoveFileResult {
569    pub files_changed: Vec<String>,
570    pub imports_updated: bool,
571}
572
573// ============================================================================
574// Raw LSP Request
575// ============================================================================
576
577#[derive(Debug, Clone, Serialize, Deserialize)]
578pub struct RawLspRequestParams {
579    pub workspace_root: String,
580    pub method: String,
581    #[serde(default)]
582    pub params: serde_json::Value,
583    #[serde(default = "default_language")]
584    pub language: String,
585}
586
587fn default_language() -> String {
588    "python".to_string()
589}
590
591// ============================================================================
592// Workspace Management
593// ============================================================================
594
595#[derive(Debug, Clone, Serialize, Deserialize)]
596pub struct RestartWorkspaceParams {
597    pub workspace_root: String,
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct RestartWorkspaceResult {
602    pub restarted: Vec<String>,
603}
604
605#[derive(Debug, Clone, Serialize, Deserialize)]
606pub struct RemoveWorkspaceParams {
607    pub workspace_root: String,
608}
609
610#[derive(Debug, Clone, Serialize, Deserialize)]
611pub struct RemoveWorkspaceResult {
612    pub servers_stopped: Vec<String>,
613}
614
615// ============================================================================
616// Add Workspace
617// ============================================================================
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
620pub struct AddWorkspaceParams {
621    pub workspace_root: String,
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize)]
625pub struct AddWorkspaceResult {
626    pub added: bool,
627    pub workspace_root: String,
628    pub message: String,
629}
630
631// ============================================================================
632// Resolve Symbol
633// ============================================================================
634
635#[derive(Debug, Clone, Serialize, Deserialize)]
636pub struct ResolveSymbolParams {
637    pub workspace_root: String,
638    pub symbol_path: String,
639}
640
641#[derive(Debug, Clone, Serialize, Deserialize)]
642pub struct ResolveSymbolResult {
643    #[serde(skip_serializing_if = "Option::is_none")]
644    pub path: Option<String>,
645    #[serde(skip_serializing_if = "Option::is_none")]
646    pub line: Option<u32>,
647    #[serde(skip_serializing_if = "Option::is_none")]
648    pub column: Option<u32>,
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub name: Option<String>,
651    #[serde(skip_serializing_if = "Option::is_none")]
652    pub kind: Option<String>,
653    #[serde(skip_serializing_if = "Option::is_none")]
654    pub container: Option<String>,
655    #[serde(skip_serializing_if = "Option::is_none")]
656    pub range_start_line: Option<u32>,
657    #[serde(skip_serializing_if = "Option::is_none")]
658    pub range_end_line: Option<u32>,
659    #[serde(skip_serializing_if = "Option::is_none")]
660    pub error: Option<String>,
661    #[serde(skip_serializing_if = "Option::is_none")]
662    pub matches: Option<Vec<SymbolInfo>>,
663    #[serde(skip_serializing_if = "Option::is_none")]
664    pub total_matches: Option<u32>,
665}
666
667#[derive(Default)]
668pub struct ResolveSymbolResultBuilder {
669    pub path: String,
670    pub line: u32,
671    pub column: u32,
672    pub name: Option<String>,
673    pub kind: Option<String>,
674    pub container: Option<String>,
675    pub range_start_line: Option<u32>,
676    pub range_end_line: Option<u32>,
677}
678
679impl ResolveSymbolResult {
680    pub fn success(builder: ResolveSymbolResultBuilder) -> Self {
681        Self {
682            path: Some(builder.path),
683            line: Some(builder.line),
684            column: Some(builder.column),
685            name: builder.name,
686            kind: builder.kind,
687            container: builder.container,
688            range_start_line: builder.range_start_line,
689            range_end_line: builder.range_end_line,
690            error: None,
691            matches: None,
692            total_matches: None,
693        }
694    }
695
696    pub fn not_found(symbol: &str) -> Self {
697        Self {
698            error: Some(format!("Symbol '{}' not found", symbol)),
699            path: None,
700            line: None,
701            column: None,
702            name: None,
703            kind: None,
704            container: None,
705            range_start_line: None,
706            range_end_line: None,
707            matches: None,
708            total_matches: None,
709        }
710    }
711
712    pub fn ambiguous(symbol: &str, matches: Vec<SymbolInfo>, total: u32) -> Self {
713        Self {
714            error: Some(format!(
715                "Symbol '{}' is ambiguous ({} matches)",
716                symbol, total
717            )),
718            matches: Some(matches),
719            total_matches: Some(total),
720            path: None,
721            line: None,
722            column: None,
723            name: None,
724            kind: None,
725            container: None,
726            range_start_line: None,
727            range_end_line: None,
728        }
729    }
730}