aft/commands/
trace_data.rs1use std::path::Path;
2
3use crate::context::AppContext;
4use crate::error::AftError;
5use crate::protocol::{RawRequest, Response};
6
7pub fn handle_trace_data(req: &RawRequest, ctx: &AppContext) -> Response {
28 let file = match req.params.get("file").and_then(|v| v.as_str()) {
29 Some(f) => f,
30 None => {
31 return Response::error(
32 &req.id,
33 "invalid_request",
34 "trace_data: missing required param 'file'",
35 );
36 }
37 };
38
39 let symbol = match req.params.get("symbol").and_then(|v| v.as_str()) {
40 Some(s) => s,
41 None => {
42 return Response::error(
43 &req.id,
44 "invalid_request",
45 "trace_data: missing required param 'symbol'",
46 );
47 }
48 };
49
50 let expression = match req.params.get("expression").and_then(|v| v.as_str()) {
51 Some(e) => e,
52 None => {
53 return Response::error(
54 &req.id,
55 "invalid_request",
56 "trace_data: missing required param 'expression'",
57 );
58 }
59 };
60
61 let depth = req
62 .params
63 .get("depth")
64 .and_then(|v| v.as_u64())
65 .unwrap_or(5)
66 .min(100) as usize;
67
68 let mut cg_ref = ctx.callgraph().borrow_mut();
69 let graph = match cg_ref.as_mut() {
70 Some(g) => g,
71 None => {
72 return Response::error(
73 &req.id,
74 "not_configured",
75 "trace_data: project not configured — send 'configure' first",
76 );
77 }
78 };
79
80 let file_path = match ctx.validate_path(&req.id, Path::new(file)) {
81 Ok(path) => path,
82 Err(resp) => return resp,
83 };
84
85 let project_root = ctx.config().project_root.clone();
86 if let Some(project_root) = project_root {
87 let canonical_root = std::fs::canonicalize(&project_root).unwrap_or(project_root.clone());
88 let input_for_resolution = if file_path.is_relative() {
89 project_root.join(&file_path)
90 } else {
91 file_path.clone()
92 };
93 let canonical_input =
94 std::fs::canonicalize(&input_for_resolution).unwrap_or(input_for_resolution);
95 if !canonical_input.starts_with(&canonical_root) {
96 return Response::error(
97 &req.id,
98 "path_outside_project_root",
99 format!(
100 "Callgraph operations require paths inside project_root. Got: {} (project_root: {})",
101 file_path.display(),
102 project_root.display(),
103 ),
104 );
105 }
106 }
107
108 match graph.build_file(&file_path) {
110 Ok(data) => {
111 let has_symbol = data.calls_by_symbol.contains_key(symbol)
112 || data.exported_symbols.contains(&symbol.to_string())
113 || data.symbol_metadata.contains_key(symbol);
114 if !has_symbol {
115 return Response::error(
116 &req.id,
117 "symbol_not_found",
118 format!("trace_data: symbol '{}' not found in {}", symbol, file),
119 );
120 }
121 }
122 Err(e) => {
123 return Response::error(&req.id, e.code(), e.to_string());
124 }
125 }
126
127 let max_files = ctx.config().max_callgraph_files;
128
129 match graph.trace_data(&file_path, symbol, expression, depth, max_files) {
130 Ok(result) => {
131 let result_json = serde_json::to_value(&result).unwrap_or_default();
132 Response::success(&req.id, result_json)
133 }
134 Err(err @ AftError::ProjectTooLarge { .. }) => {
135 Response::error(&req.id, "project_too_large", format!("{}", err))
136 }
137 Err(e) => Response::error(&req.id, e.code(), e.to_string()),
138 }
139}