mockforge_http/handlers/
xray.rs1use axum::{
7 extract::{Path, Query, State},
8 http::StatusCode,
9 response::Json,
10};
11use mockforge_core::consistency::ConsistencyEngine;
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use std::sync::Arc;
15use tracing::debug;
16use uuid::Uuid;
17
18#[derive(Clone)]
20pub struct XRayState {
21 pub engine: Arc<ConsistencyEngine>,
23}
24
25#[derive(Debug, Deserialize)]
27pub struct XRayQuery {
28 #[serde(default = "default_workspace")]
30 pub workspace: String,
31}
32
33fn default_workspace() -> String {
34 "default".to_string()
35}
36
37pub async fn get_state_summary(
43 State(state): State<XRayState>,
44 Query(params): Query<XRayQuery>,
45) -> Result<Json<Value>, StatusCode> {
46 let unified_state = state.engine.get_state(¶ms.workspace).await.ok_or_else(|| {
47 debug!("No state found for workspace: {}", params.workspace);
48 StatusCode::NOT_FOUND
49 })?;
50
51 let summary = serde_json::json!({
53 "workspace_id": unified_state.workspace_id,
54 "scenario": unified_state.active_scenario,
55 "persona": unified_state.active_persona.as_ref().map(|p| serde_json::json!({
56 "id": p.id,
57 "traits": p.traits,
58 })),
59 "reality_level": unified_state.reality_level.value(),
60 "reality_level_name": unified_state.reality_level.name(),
61 "reality_ratio": unified_state.reality_continuum_ratio,
62 "chaos_rules": unified_state
64 .active_chaos_rules
65 .iter()
66 .filter_map(|r| r.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()))
67 .collect::<Vec<_>>(),
68 "timestamp": unified_state.last_updated,
69 });
70
71 Ok(Json(summary))
72}
73
74pub async fn get_state(
78 State(state): State<XRayState>,
79 Query(params): Query<XRayQuery>,
80) -> Result<Json<Value>, StatusCode> {
81 let unified_state = state.engine.get_state(¶ms.workspace).await.ok_or_else(|| {
82 debug!("No state found for workspace: {}", params.workspace);
83 StatusCode::NOT_FOUND
84 })?;
85
86 Ok(Json(serde_json::to_value(&unified_state).unwrap()))
87}
88
89pub async fn get_request_context(
96 State(_state): State<XRayState>,
97 Path(request_id): Path<String>,
98 Query(params): Query<XRayQuery>,
99) -> Result<Json<Value>, StatusCode> {
100 debug!("Request context lookup for request_id: {} (not yet implemented)", request_id);
103 Ok(Json(serde_json::json!({
104 "request_id": request_id,
105 "workspace": params.workspace,
106 "message": "Request context tracking not yet implemented",
107 })))
108}
109
110pub async fn get_workspace_summary(
114 State(state): State<XRayState>,
115 Path(workspace_id): Path<String>,
116) -> Result<Json<Value>, StatusCode> {
117 let unified_state = state.engine.get_state(&workspace_id).await.ok_or_else(|| {
118 debug!("No state found for workspace: {}", workspace_id);
119 StatusCode::NOT_FOUND
120 })?;
121
122 let summary = serde_json::json!({
123 "workspace_id": unified_state.workspace_id,
124 "scenario": unified_state.active_scenario,
125 "persona_id": unified_state.active_persona.as_ref().map(|p| p.id.clone()),
126 "reality_level": unified_state.reality_level.value(),
127 "reality_ratio": unified_state.reality_continuum_ratio,
128 "active_chaos_rules_count": unified_state.active_chaos_rules.len(),
129 "entity_count": unified_state.entity_state.len(),
130 "protocol_count": unified_state.protocol_states.len(),
131 "last_updated": unified_state.last_updated,
132 });
133
134 Ok(Json(summary))
135}
136
137pub async fn list_entities(
141 State(state): State<XRayState>,
142 Query(params): Query<XRayQuery>,
143) -> Result<Json<Value>, StatusCode> {
144 let unified_state = state.engine.get_state(¶ms.workspace).await.ok_or_else(|| {
145 debug!("No state found for workspace: {}", params.workspace);
146 StatusCode::NOT_FOUND
147 })?;
148
149 let entities: Vec<&mockforge_core::consistency::EntityState> =
150 unified_state.entity_state.values().collect();
151
152 Ok(Json(serde_json::json!({
153 "workspace": params.workspace,
154 "entities": entities,
155 "count": entities.len(),
156 })))
157}
158
159pub async fn get_entity(
163 State(state): State<XRayState>,
164 Path((entity_type, entity_id)): Path<(String, String)>,
165 Query(params): Query<XRayQuery>,
166) -> Result<Json<Value>, StatusCode> {
167 let entity = state
168 .engine
169 .get_entity(¶ms.workspace, &entity_type, &entity_id)
170 .await
171 .ok_or_else(|| {
172 debug!(
173 "Entity not found: {}:{} in workspace: {}",
174 entity_type, entity_id, params.workspace
175 );
176 StatusCode::NOT_FOUND
177 })?;
178
179 Ok(Json(serde_json::to_value(&entity).unwrap()))
180}
181
182pub fn xray_router(state: XRayState) -> axum::Router {
184 use axum::routing::get;
185
186 axum::Router::new()
187 .route("/api/v1/xray/state/summary", get(get_state_summary))
188 .route("/api/v1/xray/state", get(get_state))
189 .route("/api/v1/xray/request-context/{request_id}", get(get_request_context))
190 .route("/api/v1/xray/workspace/{workspace_id}/summary", get(get_workspace_summary))
191 .route("/api/v1/xray/entities", get(list_entities))
192 .route("/api/v1/xray/entities/{entity_type}/{entity_id}", get(get_entity))
193 .with_state(state)
194}