Skip to main content

aft/commands/
trace_data.rs

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