Skip to main content

aft/commands/
trace_data.rs

1use std::path::Path;
2
3use crate::commands::callgraph_store_adapter::{
4    building_response, store_error_response, trace_data_result, unavailable_response,
5};
6use crate::context::{AppContext, CallgraphStoreAccess};
7use crate::protocol::{RawRequest, Response};
8
9/// Handle a `trace_data` request.
10///
11/// Traces how an expression flows through variable assignments within a
12/// function body and across function boundaries via argument-to-parameter
13/// matching. Destructuring, spread, and unresolved calls produce approximate
14/// hops and stop tracking.
15///
16/// Expects:
17/// - `file` (string, required) — path to the source file containing the symbol
18/// - `symbol` (string, required) — name of the function containing the expression
19/// - `expression` (string, required) — the expression/variable name to track
20/// - `depth` (number, optional, default 5) — maximum cross-file hop depth
21///
22/// Returns `TraceDataResult` with fields: `expression`, `origin_file`,
23/// `origin_symbol`, `hops` (array of DataFlowHop), `depth_limited`.
24///
25/// Returns error if:
26/// - required params missing
27/// - call graph not initialized (configure not called)
28/// - symbol not found in the file
29pub fn handle_trace_data(req: &RawRequest, ctx: &AppContext) -> Response {
30    let file = match req.params.get("file").and_then(|v| v.as_str()) {
31        Some(f) => f,
32        None => {
33            return Response::error(
34                &req.id,
35                "invalid_request",
36                "trace_data: missing required param 'file'",
37            );
38        }
39    };
40
41    let symbol = match req.params.get("symbol").and_then(|v| v.as_str()) {
42        Some(s) => s,
43        None => {
44            return Response::error(
45                &req.id,
46                "invalid_request",
47                "trace_data: missing required param 'symbol'",
48            );
49        }
50    };
51
52    let expression = match req.params.get("expression").and_then(|v| v.as_str()) {
53        Some(e) => e,
54        None => {
55            return Response::error(
56                &req.id,
57                "invalid_request",
58                "trace_data: missing required param 'expression'",
59            );
60        }
61    };
62
63    let depth = req
64        .params
65        .get("depth")
66        .and_then(|v| v.as_u64())
67        .unwrap_or(5)
68        .min(100) as usize;
69
70    let file_path = match ctx.validate_path(&req.id, Path::new(file)) {
71        Ok(path) => path,
72        Err(resp) => return resp,
73    };
74
75    let project_root = ctx.config().project_root.clone();
76    if let Some(project_root) = project_root {
77        let canonical_root = std::fs::canonicalize(&project_root).unwrap_or(project_root.clone());
78        let input_for_resolution = if file_path.is_relative() {
79            project_root.join(&file_path)
80        } else {
81            file_path.clone()
82        };
83        let canonical_input =
84            std::fs::canonicalize(&input_for_resolution).unwrap_or(input_for_resolution);
85        if !canonical_input.starts_with(&canonical_root) {
86            return Response::error(
87                &req.id,
88                "path_outside_project_root",
89                format!(
90                    "Callgraph operations require paths inside project_root. Got: {} (project_root: {})",
91                    file_path.display(),
92                    project_root.display(),
93                ),
94            );
95        }
96    }
97
98    let store = match ctx.callgraph_store_for_ops() {
99        CallgraphStoreAccess::Ready(store) => store,
100        CallgraphStoreAccess::Building => return building_response(&req.id, "trace_data"),
101        CallgraphStoreAccess::Unavailable => {
102            return unavailable_response(&req.id, "trace_data", ctx.is_worktree_bridge())
103        }
104        CallgraphStoreAccess::Error(error) => {
105            return store_error_response(&req.id, "trace_data", error)
106        }
107    };
108
109    match trace_data_result(
110        &store,
111        &file_path,
112        symbol,
113        expression,
114        depth,
115        ctx.symbol_cache(),
116    ) {
117        Ok(result) => {
118            let result_json = serde_json::to_value(&result).unwrap_or_default();
119            Response::success(&req.id, result_json)
120        }
121        Err(error) => store_error_response(&req.id, "trace_data", error),
122    }
123}