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 pub fn compact_signature(&self) -> String {
207 let mut sig = String::with_capacity(self.name.len() + 40);
208 sig.push_str(&self.name);
209 sig.push('(');
210
211 if !self.parameters.is_empty() {
212 let params_str = self.parameters.join(", ");
213 if params_str.len() > Self::MAX_PARAMS_DISPLAY_LEN {
214 let truncate_at = params_str.floor_char_boundary(Self::TRUNCATION_POINT);
216 sig.push_str(¶ms_str[..truncate_at]);
217 sig.push_str("...");
218 } else {
219 sig.push_str(¶ms_str);
220 }
221 }
222
223 sig.push(')');
224
225 if let Some(ret_type) = &self.return_type {
226 sig.push_str(" -> ");
227 sig.push_str(ret_type);
228 }
229
230 write!(sig, " :{}-{}", self.line, self.end_line).ok();
231 sig
232 }
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
236pub struct ClassInfo {
237 pub name: String,
238 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
239 pub line: usize,
240 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
241 pub end_line: usize,
242 pub methods: Vec<FunctionInfo>,
243 pub fields: Vec<String>,
244 #[schemars(description = "Inherited types (parent classes, interfaces, trait bounds)")]
246 pub inherits: Vec<String>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
250pub struct CallInfo {
251 pub caller: String,
252 pub callee: String,
253 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
254 pub line: usize,
255 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
256 pub column: usize,
257 #[serde(skip_serializing_if = "Option::is_none")]
259 #[schemars(schema_with = "crate::schema_helpers::option_integer_schema")]
260 pub arg_count: Option<usize>,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
264pub struct AssignmentInfo {
265 pub variable: String,
267 pub value: String,
269 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
271 pub line: usize,
272 pub scope: String,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
277pub struct FieldAccessInfo {
278 pub object: String,
280 pub field: String,
282 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
284 pub line: usize,
285 pub scope: String,
287}
288
289#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
290pub struct ReferenceInfo {
291 pub symbol: String,
292 pub reference_type: ReferenceType,
293 pub location: String,
294 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
295 pub line: usize,
296}
297
298#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
299#[serde(rename_all = "lowercase")]
300pub enum ReferenceType {
301 Definition,
302 Usage,
303 Import,
304 Export,
305}
306
307#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
308#[serde(rename_all = "lowercase")]
309pub enum EntryType {
310 File,
311 Directory,
312 Function,
313 Class,
314 Variable,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq, Hash)]
319#[serde(rename_all = "snake_case")]
320pub enum AnalysisMode {
321 Overview,
323 FileDetails,
325 SymbolFocus,
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
330pub struct CallChain {
331 pub chain: Vec<CallInfo>,
332 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
333 pub depth: u32,
334}
335
336#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
337pub struct FocusedAnalysisData {
338 pub symbol: String,
339 pub definition: Option<FunctionInfo>,
340 pub call_chains: Vec<CallChain>,
341 pub references: Vec<ReferenceInfo>,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
345pub struct ElementQueryResult {
346 pub query: String,
347 pub results: Vec<String>,
348 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
349 pub count: usize,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
353pub struct ImportInfo {
354 pub module: String,
356 pub items: Vec<String>,
358 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
360 pub line: usize,
361}
362
363#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
364pub struct SemanticAnalysis {
365 pub functions: Vec<FunctionInfo>,
366 pub classes: Vec<ClassInfo>,
367 pub imports: Vec<ImportInfo>,
369 pub references: Vec<ReferenceInfo>,
370 #[serde(skip)]
372 #[schemars(skip)]
373 pub call_frequency: HashMap<String, usize>,
374 pub calls: Vec<CallInfo>,
376 #[serde(skip)]
378 #[schemars(skip)]
379 pub impl_traits: Vec<ImplTraitInfo>,
380}
381
382#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
384pub struct ModuleFunctionInfo {
385 pub name: String,
387 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
389 pub line: usize,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
394pub struct ModuleImportInfo {
395 pub module: String,
397 pub items: Vec<String>,
399}
400
401#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
403pub struct ModuleInfo {
404 pub name: String,
406 #[schemars(schema_with = "crate::schema_helpers::integer_schema")]
408 pub line_count: usize,
409 pub language: String,
411 pub functions: Vec<ModuleFunctionInfo>,
413 pub imports: Vec<ModuleImportInfo>,
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_compact_signature_short_params() {
423 let func = FunctionInfo {
424 name: "add".to_string(),
425 line: 10,
426 end_line: 12,
427 parameters: vec!["a: i32".to_string(), "b: i32".to_string()],
428 return_type: Some("i32".to_string()),
429 };
430
431 let sig = func.compact_signature();
432 assert_eq!(sig, "add(a: i32, b: i32) -> i32 :10-12");
433 }
434
435 #[test]
436 fn test_compact_signature_long_params_truncation() {
437 let func = FunctionInfo {
438 name: "process".to_string(),
439 line: 20,
440 end_line: 50,
441 parameters: vec![
442 "config: ComplexConfigType".to_string(),
443 "data: VeryLongDataStructureNameThatExceedsEightyCharacters".to_string(),
444 "callback: Fn(Result) -> ()".to_string(),
445 ],
446 return_type: Some("Result<Output>".to_string()),
447 };
448
449 let sig = func.compact_signature();
450 assert!(sig.contains("process("));
451 assert!(sig.contains("..."));
452 assert!(sig.contains("-> Result<Output>"));
453 assert!(sig.contains(":20-50"));
454 }
455
456 #[test]
457 fn test_compact_signature_empty_params() {
458 let func = FunctionInfo {
459 name: "main".to_string(),
460 line: 1,
461 end_line: 5,
462 parameters: vec![],
463 return_type: None,
464 };
465
466 let sig = func.compact_signature();
467 assert_eq!(sig, "main() :1-5");
468 }
469
470 #[test]
471 fn schema_flatten_inline() {
472 use schemars::schema_for;
473
474 let dir_schema = schema_for!(AnalyzeDirectoryParams);
476 let dir_props = dir_schema
477 .as_object()
478 .and_then(|o| o.get("properties"))
479 .and_then(|v| v.as_object())
480 .expect("AnalyzeDirectoryParams must have properties");
481
482 assert!(
483 dir_props.contains_key("cursor"),
484 "cursor must be top-level in AnalyzeDirectoryParams schema"
485 );
486 assert!(
487 dir_props.contains_key("page_size"),
488 "page_size must be top-level in AnalyzeDirectoryParams schema"
489 );
490 assert!(
491 dir_props.contains_key("force"),
492 "force must be top-level in AnalyzeDirectoryParams schema"
493 );
494 assert!(
495 dir_props.contains_key("summary"),
496 "summary must be top-level in AnalyzeDirectoryParams schema"
497 );
498
499 let file_schema = schema_for!(AnalyzeFileParams);
501 let file_props = file_schema
502 .as_object()
503 .and_then(|o| o.get("properties"))
504 .and_then(|v| v.as_object())
505 .expect("AnalyzeFileParams must have properties");
506
507 assert!(
508 file_props.contains_key("cursor"),
509 "cursor must be top-level in AnalyzeFileParams schema"
510 );
511 assert!(
512 file_props.contains_key("page_size"),
513 "page_size must be top-level in AnalyzeFileParams schema"
514 );
515 assert!(
516 file_props.contains_key("force"),
517 "force must be top-level in AnalyzeFileParams schema"
518 );
519 assert!(
520 file_props.contains_key("summary"),
521 "summary must be top-level in AnalyzeFileParams schema"
522 );
523
524 let symbol_schema = schema_for!(AnalyzeSymbolParams);
526 let symbol_props = symbol_schema
527 .as_object()
528 .and_then(|o| o.get("properties"))
529 .and_then(|v| v.as_object())
530 .expect("AnalyzeSymbolParams must have properties");
531
532 assert!(
533 symbol_props.contains_key("cursor"),
534 "cursor must be top-level in AnalyzeSymbolParams schema"
535 );
536 assert!(
537 symbol_props.contains_key("page_size"),
538 "page_size must be top-level in AnalyzeSymbolParams schema"
539 );
540 assert!(
541 symbol_props.contains_key("force"),
542 "force must be top-level in AnalyzeSymbolParams schema"
543 );
544 assert!(
545 symbol_props.contains_key("summary"),
546 "summary must be top-level in AnalyzeSymbolParams schema"
547 );
548
549 let file_ast = file_props
551 .get("ast_recursion_limit")
552 .expect("ast_recursion_limit must be present in AnalyzeFileParams schema");
553 assert_eq!(
554 file_ast.get("minimum").and_then(|v| v.as_u64()),
555 Some(1),
556 "ast_recursion_limit in AnalyzeFileParams must have minimum: 1"
557 );
558 let symbol_ast = symbol_props
559 .get("ast_recursion_limit")
560 .expect("ast_recursion_limit must be present in AnalyzeSymbolParams schema");
561 assert_eq!(
562 symbol_ast.get("minimum").and_then(|v| v.as_u64()),
563 Some(1),
564 "ast_recursion_limit in AnalyzeSymbolParams must have minimum: 1"
565 );
566 }
567}
568
569#[derive(Debug, serde::Serialize)]
572#[serde(rename_all = "camelCase")]
573pub struct ErrorMeta {
574 pub error_category: &'static str,
575 pub is_retryable: bool,
576 pub suggested_action: &'static str,
577}
578
579#[cfg(test)]
580mod error_meta_tests {
581 use super::*;
582
583 #[test]
584 fn test_error_meta_serialization_camel_case() {
585 let meta = ErrorMeta {
586 error_category: "validation",
587 is_retryable: false,
588 suggested_action: "fix input",
589 };
590 let v = serde_json::to_value(&meta).unwrap();
591 assert_eq!(v["errorCategory"], "validation");
592 assert_eq!(v["isRetryable"], false);
593 assert_eq!(v["suggestedAction"], "fix input");
594 }
595
596 #[test]
597 fn test_error_meta_validation_not_retryable() {
598 let meta = ErrorMeta {
599 error_category: "validation",
600 is_retryable: false,
601 suggested_action: "use summary=true",
602 };
603 assert!(!meta.is_retryable);
604 }
605
606 #[test]
607 fn test_error_meta_transient_retryable() {
608 let meta = ErrorMeta {
609 error_category: "transient",
610 is_retryable: true,
611 suggested_action: "retry the request",
612 };
613 assert!(meta.is_retryable);
614 }
615}