aft/commands/
trace_data.rs1use 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
12pub 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}