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