1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt::Write;
5
6#[allow(unused_imports)]
7use crate::analyze::{AnalysisOutput, FileAnalysisOutput, FocusedAnalysisOutput};
8
9#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
11pub struct PaginationParams {
12 pub cursor: Option<String>,
14 pub page_size: Option<usize>,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
20pub struct OutputControlParams {
21 pub force: Option<bool>,
23 pub summary: Option<bool>,
25 pub verbose: Option<bool>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
30pub struct AnalyzeDirectoryParams {
31 pub path: String,
33
34 pub max_depth: Option<u32>,
36
37 #[serde(flatten)]
38 pub pagination: PaginationParams,
39
40 #[serde(flatten)]
41 pub output_control: OutputControlParams,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
45pub struct AnalyzeFileParams {
46 pub path: String,
48
49 pub ast_recursion_limit: Option<usize>,
51
52 #[serde(flatten)]
53 pub pagination: PaginationParams,
54
55 #[serde(flatten)]
56 pub output_control: OutputControlParams,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
60pub struct AnalyzeModuleParams {
61 pub path: String,
63}
64
65#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
67#[serde(rename_all = "snake_case")]
68pub enum SymbolMatchMode {
69 #[default]
71 Exact,
72 Insensitive,
74 Prefix,
76 Contains,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
81pub struct AnalyzeSymbolParams {
82 pub path: String,
84
85 pub symbol: String,
87
88 pub match_mode: Option<SymbolMatchMode>,
90
91 pub follow_depth: Option<u32>,
93
94 pub max_depth: Option<u32>,
96
97 pub ast_recursion_limit: Option<usize>,
99
100 #[serde(flatten)]
101 pub pagination: PaginationParams,
102
103 #[serde(flatten)]
104 pub output_control: OutputControlParams,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
108pub struct AnalysisResult {
109 pub path: String,
110 pub mode: AnalysisMode,
111 pub import_count: usize,
112 pub main_line: Option<usize>,
113 pub files: Vec<FileInfo>,
114 pub functions: Vec<FunctionInfo>,
115 pub classes: Vec<ClassInfo>,
116 pub references: Vec<ReferenceInfo>,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
120pub struct FileInfo {
121 pub path: String,
122 pub language: String,
123 pub line_count: usize,
124 pub function_count: usize,
125 pub class_count: usize,
126 pub is_test: bool,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
131pub struct FunctionInfo {
132 pub name: String,
133 pub line: usize,
134 pub end_line: usize,
135 pub parameters: Vec<String>,
137 pub return_type: Option<String>,
138}
139
140impl FunctionInfo {
141 const MAX_PARAMS_DISPLAY_LEN: usize = 80;
143 const TRUNCATION_POINT: usize = 77;
145
146 pub fn compact_signature(&self) -> String {
150 let mut sig = String::with_capacity(self.name.len() + 40);
151 sig.push_str(&self.name);
152 sig.push('(');
153
154 if !self.parameters.is_empty() {
155 let params_str = self.parameters.join(", ");
156 if params_str.len() > Self::MAX_PARAMS_DISPLAY_LEN {
157 let truncate_at = params_str.floor_char_boundary(Self::TRUNCATION_POINT);
159 sig.push_str(¶ms_str[..truncate_at]);
160 sig.push_str("...");
161 } else {
162 sig.push_str(¶ms_str);
163 }
164 }
165
166 sig.push(')');
167
168 if let Some(ret_type) = &self.return_type {
169 sig.push_str(" -> ");
170 sig.push_str(ret_type);
171 }
172
173 write!(sig, " :{}-{}", self.line, self.end_line).ok();
174 sig
175 }
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
179pub struct ClassInfo {
180 pub name: String,
181 pub line: usize,
182 pub end_line: usize,
183 pub methods: Vec<FunctionInfo>,
184 pub fields: Vec<String>,
185 #[schemars(description = "Inherited types (parent classes, interfaces, trait bounds)")]
187 pub inherits: Vec<String>,
188}
189
190#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
191pub struct CallInfo {
192 pub caller: String,
193 pub callee: String,
194 pub line: usize,
195 pub column: usize,
196 #[serde(skip_serializing_if = "Option::is_none")]
198 pub arg_count: Option<usize>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
202pub struct AssignmentInfo {
203 pub variable: String,
205 pub value: String,
207 pub line: usize,
209 pub scope: String,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
214pub struct FieldAccessInfo {
215 pub object: String,
217 pub field: String,
219 pub line: usize,
221 pub scope: String,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
226pub struct ReferenceInfo {
227 pub symbol: String,
228 pub reference_type: ReferenceType,
229 pub location: String,
230 pub line: usize,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
234#[serde(rename_all = "lowercase")]
235pub enum ReferenceType {
236 Definition,
237 Usage,
238 Import,
239 Export,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
243#[serde(rename_all = "lowercase")]
244pub enum EntryType {
245 File,
246 Directory,
247 Function,
248 Class,
249 Variable,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
254#[serde(rename_all = "snake_case")]
255pub enum AnalysisMode {
256 Overview,
258 FileDetails,
260 SymbolFocus,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
265pub struct CallChain {
266 pub chain: Vec<CallInfo>,
267 pub depth: u32,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
271pub struct FocusedAnalysisData {
272 pub symbol: String,
273 pub definition: Option<FunctionInfo>,
274 pub call_chains: Vec<CallChain>,
275 pub references: Vec<ReferenceInfo>,
276}
277
278#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
279pub struct ElementQueryResult {
280 pub query: String,
281 pub results: Vec<String>,
282 pub count: usize,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
286pub struct ImportInfo {
287 pub module: String,
289 pub items: Vec<String>,
291 pub line: usize,
293}
294
295#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
296pub struct SemanticAnalysis {
297 pub functions: Vec<FunctionInfo>,
298 pub classes: Vec<ClassInfo>,
299 pub imports: Vec<ImportInfo>,
301 pub references: Vec<ReferenceInfo>,
302 #[serde(skip)]
304 #[schemars(skip)]
305 pub call_frequency: HashMap<String, usize>,
306 pub calls: Vec<CallInfo>,
308 #[serde(skip)]
310 #[schemars(skip)]
311 pub assignments: Vec<AssignmentInfo>,
312 #[serde(skip)]
314 #[schemars(skip)]
315 pub field_accesses: Vec<FieldAccessInfo>,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
320pub struct ModuleFunctionInfo {
321 pub name: String,
323 pub line: usize,
325}
326
327#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
329pub struct ModuleImportInfo {
330 pub module: String,
332 pub items: Vec<String>,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
338pub struct ModuleInfo {
339 pub name: String,
341 pub line_count: usize,
343 pub language: String,
345 pub functions: Vec<ModuleFunctionInfo>,
347 pub imports: Vec<ModuleImportInfo>,
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn test_compact_signature_short_params() {
357 let func = FunctionInfo {
358 name: "add".to_string(),
359 line: 10,
360 end_line: 12,
361 parameters: vec!["a: i32".to_string(), "b: i32".to_string()],
362 return_type: Some("i32".to_string()),
363 };
364
365 let sig = func.compact_signature();
366 assert_eq!(sig, "add(a: i32, b: i32) -> i32 :10-12");
367 }
368
369 #[test]
370 fn test_compact_signature_long_params_truncation() {
371 let func = FunctionInfo {
372 name: "process".to_string(),
373 line: 20,
374 end_line: 50,
375 parameters: vec![
376 "config: ComplexConfigType".to_string(),
377 "data: VeryLongDataStructureNameThatExceedsEightyCharacters".to_string(),
378 "callback: Fn(Result) -> ()".to_string(),
379 ],
380 return_type: Some("Result<Output>".to_string()),
381 };
382
383 let sig = func.compact_signature();
384 assert!(sig.contains("process("));
385 assert!(sig.contains("..."));
386 assert!(sig.contains("-> Result<Output>"));
387 assert!(sig.contains(":20-50"));
388 }
389
390 #[test]
391 fn test_compact_signature_empty_params() {
392 let func = FunctionInfo {
393 name: "main".to_string(),
394 line: 1,
395 end_line: 5,
396 parameters: vec![],
397 return_type: None,
398 };
399
400 let sig = func.compact_signature();
401 assert_eq!(sig, "main() :1-5");
402 }
403
404 #[test]
405 fn schema_flatten_inline() {
406 use schemars::schema_for;
407
408 let dir_schema = schema_for!(AnalyzeDirectoryParams);
410 let dir_props = dir_schema
411 .as_object()
412 .and_then(|o| o.get("properties"))
413 .and_then(|v| v.as_object())
414 .expect("AnalyzeDirectoryParams must have properties");
415
416 assert!(
417 dir_props.contains_key("cursor"),
418 "cursor must be top-level in AnalyzeDirectoryParams schema"
419 );
420 assert!(
421 dir_props.contains_key("page_size"),
422 "page_size must be top-level in AnalyzeDirectoryParams schema"
423 );
424 assert!(
425 dir_props.contains_key("force"),
426 "force must be top-level in AnalyzeDirectoryParams schema"
427 );
428 assert!(
429 dir_props.contains_key("summary"),
430 "summary must be top-level in AnalyzeDirectoryParams schema"
431 );
432
433 let file_schema = schema_for!(AnalyzeFileParams);
435 let file_props = file_schema
436 .as_object()
437 .and_then(|o| o.get("properties"))
438 .and_then(|v| v.as_object())
439 .expect("AnalyzeFileParams must have properties");
440
441 assert!(
442 file_props.contains_key("cursor"),
443 "cursor must be top-level in AnalyzeFileParams schema"
444 );
445 assert!(
446 file_props.contains_key("page_size"),
447 "page_size must be top-level in AnalyzeFileParams schema"
448 );
449 assert!(
450 file_props.contains_key("force"),
451 "force must be top-level in AnalyzeFileParams schema"
452 );
453 assert!(
454 file_props.contains_key("summary"),
455 "summary must be top-level in AnalyzeFileParams schema"
456 );
457
458 let symbol_schema = schema_for!(AnalyzeSymbolParams);
460 let symbol_props = symbol_schema
461 .as_object()
462 .and_then(|o| o.get("properties"))
463 .and_then(|v| v.as_object())
464 .expect("AnalyzeSymbolParams must have properties");
465
466 assert!(
467 symbol_props.contains_key("cursor"),
468 "cursor must be top-level in AnalyzeSymbolParams schema"
469 );
470 assert!(
471 symbol_props.contains_key("page_size"),
472 "page_size must be top-level in AnalyzeSymbolParams schema"
473 );
474 assert!(
475 symbol_props.contains_key("force"),
476 "force must be top-level in AnalyzeSymbolParams schema"
477 );
478 assert!(
479 symbol_props.contains_key("summary"),
480 "summary must be top-level in AnalyzeSymbolParams schema"
481 );
482 }
483}
484
485#[derive(Debug, serde::Serialize)]
488#[serde(rename_all = "camelCase")]
489pub struct ErrorMeta {
490 pub error_category: &'static str,
491 pub is_retryable: bool,
492 pub suggested_action: &'static str,
493}
494
495#[cfg(test)]
496mod error_meta_tests {
497 use super::*;
498
499 #[test]
500 fn test_error_meta_serialization_camel_case() {
501 let meta = ErrorMeta {
502 error_category: "validation",
503 is_retryable: false,
504 suggested_action: "fix input",
505 };
506 let v = serde_json::to_value(&meta).unwrap();
507 assert_eq!(v["errorCategory"], "validation");
508 assert_eq!(v["isRetryable"], false);
509 assert_eq!(v["suggestedAction"], "fix input");
510 }
511
512 #[test]
513 fn test_error_meta_validation_not_retryable() {
514 let meta = ErrorMeta {
515 error_category: "validation",
516 is_retryable: false,
517 suggested_action: "use summary=true",
518 };
519 assert!(!meta.is_retryable);
520 }
521
522 #[test]
523 fn test_error_meta_transient_retryable() {
524 let meta = ErrorMeta {
525 error_category: "transient",
526 is_retryable: true,
527 suggested_action: "retry the request",
528 };
529 assert!(meta.is_retryable);
530 }
531}