Skip to main content

routa_server/api/
traces.rs

1use axum::{
2    extract::{Query as QueryParams, State},
3    routing::get,
4    Json, Router,
5};
6use serde::Deserialize;
7
8use crate::error::ServerError;
9use crate::state::AppState;
10use routa_core::trace::{TraceQuery, TraceReader};
11
12pub fn router() -> Router<AppState> {
13    Router::new()
14        .route("/", get(query_traces).post(export_traces))
15        .route("/stats", get(get_trace_stats))
16        .route("/{id}", get(get_trace_by_id))
17}
18
19/// GET /api/traces — Query traces with optional filters.
20///
21/// Query parameters:
22/// - sessionId: Filter by session ID
23/// - workspaceId: Filter by workspace ID
24/// - file: Filter by file path
25/// - eventType: Filter by event type
26/// - startDate: Start date (YYYY-MM-DD)
27/// - endDate: End date (YYYY-MM-DD)
28/// - limit: Max number of results
29/// - offset: Skip N results
30async fn query_traces(
31    State(_state): State<AppState>,
32    QueryParams(params): QueryParams<TraceQueryParams>,
33) -> Result<Json<serde_json::Value>, ServerError> {
34    // Get current working directory for trace base path
35    let cwd = std::env::current_dir()
36        .map_err(|e| ServerError::Internal(format!("Failed to get cwd: {e}")))?;
37
38    let reader = TraceReader::new(&cwd);
39    let query = params.to_trace_query();
40
41    let traces = reader
42        .query(&query)
43        .await
44        .map_err(|e| ServerError::Internal(format!("Failed to query traces: {e}")))?;
45
46    Ok(Json(serde_json::json!({
47        "traces": traces,
48        "count": traces.len()
49    })))
50}
51
52/// GET /api/traces/stats — Get trace statistics.
53async fn get_trace_stats(
54    State(_state): State<AppState>,
55) -> Result<Json<serde_json::Value>, ServerError> {
56    let cwd = std::env::current_dir()
57        .map_err(|e| ServerError::Internal(format!("Failed to get cwd: {e}")))?;
58
59    let reader = TraceReader::new(&cwd);
60    let stats = reader
61        .stats()
62        .await
63        .map_err(|e| ServerError::Internal(format!("Failed to get trace stats: {e}")))?;
64
65    Ok(Json(serde_json::json!({ "stats": stats })))
66}
67
68/// GET /api/traces/:id — Get a single trace by ID.
69async fn get_trace_by_id(
70    State(_state): State<AppState>,
71    axum::extract::Path(id): axum::extract::Path<String>,
72) -> Result<Json<serde_json::Value>, ServerError> {
73    let cwd = std::env::current_dir()
74        .map_err(|e| ServerError::Internal(format!("Failed to get cwd: {e}")))?;
75
76    let reader = TraceReader::new(&cwd);
77    let trace = reader
78        .get_by_id(&id)
79        .await
80        .map_err(|e| ServerError::Internal(format!("Failed to get trace: {e}")))?;
81
82    match trace {
83        Some(trace) => Ok(Json(serde_json::json!({ "trace": trace }))),
84        None => Err(ServerError::NotFound(format!("Trace {id} not found"))),
85    }
86}
87
88/// POST /api/traces/export — Export traces in Agent Trace JSON format.
89async fn export_traces(
90    State(_state): State<AppState>,
91    QueryParams(params): QueryParams<TraceQueryParams>,
92) -> Result<Json<serde_json::Value>, ServerError> {
93    let cwd = std::env::current_dir()
94        .map_err(|e| ServerError::Internal(format!("Failed to get cwd: {e}")))?;
95
96    let reader = TraceReader::new(&cwd);
97    let query = params.to_trace_query();
98
99    let traces_json = reader
100        .export(&query)
101        .await
102        .map_err(|e| ServerError::Internal(format!("Failed to export traces: {e}")))?;
103
104    Ok(Json(serde_json::json!({
105        "export": traces_json,
106        "format": "agent-trace-json",
107        "version": "0.1.0"
108    })))
109}
110
111/// Query parameters for trace API endpoints.
112#[derive(Debug, Deserialize)]
113#[serde(rename_all = "camelCase")]
114struct TraceQueryParams {
115    session_id: Option<String>,
116    workspace_id: Option<String>,
117    file: Option<String>,
118    event_type: Option<String>,
119    start_date: Option<String>,
120    end_date: Option<String>,
121    limit: Option<usize>,
122    offset: Option<usize>,
123}
124
125impl TraceQueryParams {
126    fn to_trace_query(&self) -> TraceQuery {
127        TraceQuery {
128            session_id: self.session_id.clone(),
129            workspace_id: self.workspace_id.clone(),
130            file: self.file.clone(),
131            event_type: self.event_type.clone(),
132            start_date: self.start_date.clone(),
133            end_date: self.end_date.clone(),
134            limit: self.limit,
135            offset: self.offset,
136        }
137    }
138}