Skip to main content

aft/commands/
trace_data.rs

1use std::path::Path;
2
3use crate::context::AppContext;
4use crate::protocol::{RawRequest, Response};
5
6/// Handle a `trace_data` request.
7///
8/// Traces how an expression flows through variable assignments within a
9/// function body and across function boundaries via argument-to-parameter
10/// matching. Destructuring, spread, and unresolved calls produce approximate
11/// hops and stop tracking.
12///
13/// Expects:
14/// - `file` (string, required) — path to the source file containing the symbol
15/// - `symbol` (string, required) — name of the function containing the expression
16/// - `expression` (string, required) — the expression/variable name to track
17/// - `depth` (number, optional, default 5) — maximum cross-file hop depth
18///
19/// Returns `TraceDataResult` with fields: `expression`, `origin_file`,
20/// `origin_symbol`, `hops` (array of DataFlowHop), `depth_limited`.
21///
22/// Returns error if:
23/// - required params missing
24/// - call graph not initialized (configure not called)
25/// - symbol not found in the file
26pub fn handle_trace_data(req: &RawRequest, ctx: &AppContext) -> Response {
27    let file = match req.params.get("file").and_then(|v| v.as_str()) {
28        Some(f) => f,
29        None => {
30            return Response::error(
31                &req.id,
32                "invalid_request",
33                "trace_data: missing required param 'file'",
34            );
35        }
36    };
37
38    let symbol = match req.params.get("symbol").and_then(|v| v.as_str()) {
39        Some(s) => s,
40        None => {
41            return Response::error(
42                &req.id,
43                "invalid_request",
44                "trace_data: missing required param 'symbol'",
45            );
46        }
47    };
48
49    let expression = match req.params.get("expression").and_then(|v| v.as_str()) {
50        Some(e) => e,
51        None => {
52            return Response::error(
53                &req.id,
54                "invalid_request",
55                "trace_data: missing required param 'expression'",
56            );
57        }
58    };
59
60    let depth = req
61        .params
62        .get("depth")
63        .and_then(|v| v.as_u64())
64        .unwrap_or(5)
65        .min(100) as usize;
66
67    let mut cg_ref = ctx.callgraph().borrow_mut();
68    let graph = match cg_ref.as_mut() {
69        Some(g) => g,
70        None => {
71            return Response::error(
72                &req.id,
73                "not_configured",
74                "trace_data: project not configured — send 'configure' first",
75            );
76        }
77    };
78
79    let file_path = Path::new(file);
80
81    // Build file data first to check if the symbol exists
82    match graph.build_file(file_path) {
83        Ok(data) => {
84            let has_symbol = data.calls_by_symbol.contains_key(symbol)
85                || data.exported_symbols.contains(&symbol.to_string())
86                || data.symbol_metadata.contains_key(symbol);
87            if !has_symbol {
88                return Response::error(
89                    &req.id,
90                    "symbol_not_found",
91                    format!("trace_data: symbol '{}' not found in {}", symbol, file),
92                );
93            }
94        }
95        Err(e) => {
96            return Response::error(&req.id, e.code(), e.to_string());
97        }
98    }
99
100    match graph.trace_data(file_path, symbol, expression, depth) {
101        Ok(result) => {
102            let result_json = serde_json::to_value(&result).unwrap_or_default();
103            Response::success(&req.id, result_json)
104        }
105        Err(e) => Response::error(&req.id, e.code(), e.to_string()),
106    }
107}