1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt::Write;
5use std::path::PathBuf;
6
7#[allow(unused_imports)]
8use crate::analyze::{AnalysisOutput, FileAnalysisOutput, FocusedAnalysisOutput};
9
10#[derive(Debug, Clone, PartialEq)]
13pub struct CallEdge {
14 pub path: PathBuf,
15 pub line: usize,
16 pub neighbor_name: String,
17 pub is_impl_trait: bool,
18}
19
20#[derive(Debug, Clone)]
22pub struct ImplTraitInfo {
23 pub trait_name: String,
24 pub impl_type: String,
25 pub path: PathBuf,
26 pub line: usize,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
31pub struct PaginationParams {
32 pub cursor: Option<String>,
34 #[schemars(schema_with = "crate::schema_helpers::option_page_size_schema")]
36 pub page_size: Option<usize>,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
41pub struct OutputControlParams {
42 pub force: Option<bool>,
44 pub summary: Option<bool>,
46 pub verbose: Option<bool>,
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
51pub struct AnalyzeDirectoryParams {
52 pub path: String,
54
55 #[schemars(schema_with = "crate::schema_helpers::option_integer_schema")]
57 pub max_depth: Option<u32>,
58
59 #[serde(flatten)]
60 pub pagination: PaginationParams,
61
62 #[serde(flatten)]
63 pub output_control: OutputControlParams,
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
68#[serde(rename_all = "snake_case")]
69pub enum AnalyzeFileField {
70 Functions,
72 Classes,
74 Imports,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
79pub struct AnalyzeFileParams {
80 pub path: String,
82
83 #[schemars(schema_with = "crate::schema_helpers::option_ast_limit_schema")]
85 pub ast_recursion_limit: Option<usize>,
86
87 #[serde(skip_serializing_if = "Option::is_none")]
92 pub fields: Option<Vec<AnalyzeFileField>>,
93
94 #[serde(flatten)]
95 pub pagination: PaginationParams,
96
97 #[serde(flatten)]
98 pub output_control: OutputControlParams,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
102pub struct AnalyzeModuleParams {
103 pub path: String,
105}
106
107#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, JsonSchema)]
109#[serde(rename_all = "snake_case")]
110pub enum SymbolMatchMode {
111 #[default]
113 Exact,
114 Insensitive,
116 Prefix,
118 Contains,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
123pub struct AnalyzeSymbolParams {
124 pub path: String,
126
127 pub symbol: String,
129
130 pub match_mode: Option<SymbolMatchMode>,
132
133 #[schemars(schema_with = "crate::schema_helpers::option_integer_schema")]
135 pub follow_depth: Option<u32>,
136
137 #[schemars(schema_with = "crate::schema_helpers::option_integer_schema")]
139 pub max_depth: Option<u32>,
140
141 #[schemars(schema_with = "crate::schema_helpers::option_ast_limit_schema")]
143 pub ast_recursion_limit: Option<usize>,
144
145 #[serde(flatten)]
146 pub pagination: PaginationParams,
147
148 #[serde(flatten)]
149 pub output_control: OutputControlParams,
150
151 #[serde(default)]
154 pub impl_only: Option<bool>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
158pub struct AnalysisResult {
159 pub path: String,
160 pub mode: AnalysisMode,
161 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
162 pub import_count: usize,
163 #[schemars(schema_with = "crate::schema_helpers::option_integer_schema")]
164 pub main_line: Option<usize>,
165 pub files: Vec<FileInfo>,
166 pub functions: Vec<FunctionInfo>,
167 pub classes: Vec<ClassInfo>,
168 pub references: Vec<ReferenceInfo>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
172pub struct FileInfo {
173 pub path: String,
174 pub language: String,
175 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
176 pub line_count: usize,
177 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
178 pub function_count: usize,
179 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
180 pub class_count: usize,
181 pub is_test: bool,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
186pub struct FunctionInfo {
187 pub name: String,
188 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
189 pub line: usize,
190 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
191 pub end_line: usize,
192 pub parameters: Vec<String>,
194 pub return_type: Option<String>,
195}
196
197impl FunctionInfo {
198 const MAX_PARAMS_DISPLAY_LEN: usize = 80;
200 const TRUNCATION_POINT: usize = 77;
202
203 #[must_use]
207 pub fn compact_signature(&self) -> String {
208 let mut sig = String::with_capacity(self.name.len() + 40);
209 sig.push_str(&self.name);
210 sig.push('(');
211
212 if !self.parameters.is_empty() {
213 let params_str = self.parameters.join(", ");
214 if params_str.len() > Self::MAX_PARAMS_DISPLAY_LEN {
215 let truncate_at = params_str.floor_char_boundary(Self::TRUNCATION_POINT);
217 sig.push_str(¶ms_str[..truncate_at]);
218 sig.push_str("...");
219 } else {
220 sig.push_str(¶ms_str);
221 }
222 }
223
224 sig.push(')');
225
226 if let Some(ret_type) = &self.return_type {
227 sig.push_str(" -> ");
228 sig.push_str(ret_type);
229 }
230
231 write!(sig, " :{}-{}", self.line, self.end_line).ok();
232 sig
233 }
234}
235
236#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
237pub struct ClassInfo {
238 pub name: String,
239 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
240 pub line: usize,
241 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
242 pub end_line: usize,
243 pub methods: Vec<FunctionInfo>,
244 pub fields: Vec<String>,
245 #[schemars(description = "Inherited types (parent classes, interfaces, trait bounds)")]
247 pub inherits: Vec<String>,
248}
249
250#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
251pub struct CallInfo {
252 pub caller: String,
253 pub callee: String,
254 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
255 pub line: usize,
256 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
257 pub column: usize,
258 #[serde(skip_serializing_if = "Option::is_none")]
260 #[schemars(schema_with = "crate::schema_helpers::option_integer_schema")]
261 pub arg_count: Option<usize>,
262}
263
264#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
265pub struct AssignmentInfo {
266 pub variable: String,
268 pub value: String,
270 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
272 pub line: usize,
273 pub scope: String,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
278pub struct FieldAccessInfo {
279 pub object: String,
281 pub field: String,
283 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
285 pub line: usize,
286 pub scope: String,
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
291pub struct ReferenceInfo {
292 pub symbol: String,
293 pub reference_type: ReferenceType,
294 pub location: String,
295 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
296 pub line: usize,
297}
298
299#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
300#[serde(rename_all = "lowercase")]
301pub enum ReferenceType {
302 Definition,
303 Usage,
304 Import,
305 Export,
306}
307
308#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
309#[serde(rename_all = "lowercase")]
310pub enum EntryType {
311 File,
312 Directory,
313 Function,
314 Class,
315 Variable,
316}
317
318#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
320#[serde(rename_all = "snake_case")]
321pub enum AnalysisMode {
322 Overview,
324 FileDetails,
326 SymbolFocus,
328}
329
330#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
331pub struct CallChain {
332 pub chain: Vec<CallInfo>,
333 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
334 pub depth: u32,
335}
336
337#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
338pub struct FocusedAnalysisData {
339 pub symbol: String,
340 pub definition: Option<FunctionInfo>,
341 pub call_chains: Vec<CallChain>,
342 pub references: Vec<ReferenceInfo>,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
346pub struct ElementQueryResult {
347 pub query: String,
348 pub results: Vec<String>,
349 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
350 pub count: usize,
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
354pub struct ImportInfo {
355 pub module: String,
357 pub items: Vec<String>,
359 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
361 pub line: usize,
362}
363
364#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
365pub struct SemanticAnalysis {
366 pub functions: Vec<FunctionInfo>,
367 pub classes: Vec<ClassInfo>,
368 pub imports: Vec<ImportInfo>,
370 pub references: Vec<ReferenceInfo>,
371 #[serde(skip)]
373 #[schemars(skip)]
374 pub call_frequency: HashMap<String, usize>,
375 pub calls: Vec<CallInfo>,
377 #[serde(skip)]
379 #[schemars(skip)]
380 pub impl_traits: Vec<ImplTraitInfo>,
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
385pub struct ModuleFunctionInfo {
386 pub name: String,
388 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
390 pub line: usize,
391}
392
393#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
395pub struct ModuleImportInfo {
396 pub module: String,
398 pub items: Vec<String>,
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
404pub struct ModuleInfo {
405 pub name: String,
407 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
409 pub line_count: usize,
410 pub language: String,
412 pub functions: Vec<ModuleFunctionInfo>,
414 pub imports: Vec<ModuleImportInfo>,
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[test]
423 fn test_compact_signature_short_params() {
424 let func = FunctionInfo {
425 name: "add".to_string(),
426 line: 10,
427 end_line: 12,
428 parameters: vec!["a: i32".to_string(), "b: i32".to_string()],
429 return_type: Some("i32".to_string()),
430 };
431
432 let sig = func.compact_signature();
433 assert_eq!(sig, "add(a: i32, b: i32) -> i32 :10-12");
434 }
435
436 #[test]
437 fn test_compact_signature_long_params_truncation() {
438 let func = FunctionInfo {
439 name: "process".to_string(),
440 line: 20,
441 end_line: 50,
442 parameters: vec![
443 "config: ComplexConfigType".to_string(),
444 "data: VeryLongDataStructureNameThatExceedsEightyCharacters".to_string(),
445 "callback: Fn(Result) -> ()".to_string(),
446 ],
447 return_type: Some("Result<Output>".to_string()),
448 };
449
450 let sig = func.compact_signature();
451 assert!(sig.contains("process("));
452 assert!(sig.contains("..."));
453 assert!(sig.contains("-> Result<Output>"));
454 assert!(sig.contains(":20-50"));
455 }
456
457 #[test]
458 fn test_compact_signature_empty_params() {
459 let func = FunctionInfo {
460 name: "main".to_string(),
461 line: 1,
462 end_line: 5,
463 parameters: vec![],
464 return_type: None,
465 };
466
467 let sig = func.compact_signature();
468 assert_eq!(sig, "main() :1-5");
469 }
470
471 #[test]
472 fn schema_flatten_inline() {
473 use schemars::schema_for;
474
475 let dir_schema = schema_for!(AnalyzeDirectoryParams);
477 let dir_props = dir_schema
478 .as_object()
479 .and_then(|o| o.get("properties"))
480 .and_then(|v| v.as_object())
481 .expect("AnalyzeDirectoryParams must have properties");
482
483 assert!(
484 dir_props.contains_key("cursor"),
485 "cursor must be top-level in AnalyzeDirectoryParams schema"
486 );
487 assert!(
488 dir_props.contains_key("page_size"),
489 "page_size must be top-level in AnalyzeDirectoryParams schema"
490 );
491 assert!(
492 dir_props.contains_key("force"),
493 "force must be top-level in AnalyzeDirectoryParams schema"
494 );
495 assert!(
496 dir_props.contains_key("summary"),
497 "summary must be top-level in AnalyzeDirectoryParams schema"
498 );
499
500 let file_schema = schema_for!(AnalyzeFileParams);
502 let file_props = file_schema
503 .as_object()
504 .and_then(|o| o.get("properties"))
505 .and_then(|v| v.as_object())
506 .expect("AnalyzeFileParams must have properties");
507
508 assert!(
509 file_props.contains_key("cursor"),
510 "cursor must be top-level in AnalyzeFileParams schema"
511 );
512 assert!(
513 file_props.contains_key("page_size"),
514 "page_size must be top-level in AnalyzeFileParams schema"
515 );
516 assert!(
517 file_props.contains_key("force"),
518 "force must be top-level in AnalyzeFileParams schema"
519 );
520 assert!(
521 file_props.contains_key("summary"),
522 "summary must be top-level in AnalyzeFileParams schema"
523 );
524
525 let symbol_schema = schema_for!(AnalyzeSymbolParams);
527 let symbol_props = symbol_schema
528 .as_object()
529 .and_then(|o| o.get("properties"))
530 .and_then(|v| v.as_object())
531 .expect("AnalyzeSymbolParams must have properties");
532
533 assert!(
534 symbol_props.contains_key("cursor"),
535 "cursor must be top-level in AnalyzeSymbolParams schema"
536 );
537 assert!(
538 symbol_props.contains_key("page_size"),
539 "page_size must be top-level in AnalyzeSymbolParams schema"
540 );
541 assert!(
542 symbol_props.contains_key("force"),
543 "force must be top-level in AnalyzeSymbolParams schema"
544 );
545 assert!(
546 symbol_props.contains_key("summary"),
547 "summary must be top-level in AnalyzeSymbolParams schema"
548 );
549
550 let file_ast = file_props
552 .get("ast_recursion_limit")
553 .expect("ast_recursion_limit must be present in AnalyzeFileParams schema");
554 assert_eq!(
555 file_ast.get("minimum").and_then(|v| v.as_u64()),
556 Some(1),
557 "ast_recursion_limit in AnalyzeFileParams must have minimum: 1"
558 );
559 let symbol_ast = symbol_props
560 .get("ast_recursion_limit")
561 .expect("ast_recursion_limit must be present in AnalyzeSymbolParams schema");
562 assert_eq!(
563 symbol_ast.get("minimum").and_then(|v| v.as_u64()),
564 Some(1),
565 "ast_recursion_limit in AnalyzeSymbolParams must have minimum: 1"
566 );
567 }
568}
569
570#[derive(Debug, serde::Serialize)]
573#[serde(rename_all = "camelCase")]
574pub struct ErrorMeta {
575 pub error_category: &'static str,
576 pub is_retryable: bool,
577 pub suggested_action: &'static str,
578}
579
580#[cfg(test)]
581mod error_meta_tests {
582 use super::*;
583
584 #[test]
585 fn test_error_meta_serialization_camel_case() {
586 let meta = ErrorMeta {
587 error_category: "validation",
588 is_retryable: false,
589 suggested_action: "fix input",
590 };
591 let v = serde_json::to_value(&meta).unwrap();
592 assert_eq!(v["errorCategory"], "validation");
593 assert_eq!(v["isRetryable"], false);
594 assert_eq!(v["suggestedAction"], "fix input");
595 }
596
597 #[test]
598 fn test_error_meta_validation_not_retryable() {
599 let meta = ErrorMeta {
600 error_category: "validation",
601 is_retryable: false,
602 suggested_action: "use summary=true",
603 };
604 assert!(!meta.is_retryable);
605 }
606
607 #[test]
608 fn test_error_meta_transient_retryable() {
609 let meta = ErrorMeta {
610 error_category: "transient",
611 is_retryable: true,
612 suggested_action: "retry the request",
613 };
614 assert!(meta.is_retryable);
615 }
616}