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