use crate::consistency::HttpAdapter;
use axum::{body::Body, extract::Request, http::Response, middleware::Next};
use mockforge_core::consistency::ConsistencyEngine;
use mockforge_core::request_logger::RealityTraceMetadata;
use std::sync::Arc;
use tracing::debug;
#[derive(Clone)]
pub struct ConsistencyMiddlewareState {
pub engine: Arc<ConsistencyEngine>,
pub adapter: Arc<HttpAdapter>,
pub xray_state: Option<Arc<crate::handlers::xray::XRayState>>,
}
pub async fn consistency_middleware(req: Request, next: Next) -> Response<Body> {
let workspace_id = req
.headers()
.get("X-MockForge-Workspace")
.and_then(|h| h.to_str().ok())
.map(|s| s.to_string())
.or_else(|| {
req.uri().query().and_then(|q| {
q.split('&').find_map(|pair| {
let mut parts = pair.splitn(2, '=');
if parts.next() == Some("workspace") {
parts.next().and_then(|v| {
urlencoding::decode(v).ok().map(|decoded| decoded.to_string())
})
} else {
None
}
})
})
})
.unwrap_or_else(|| "default".to_string());
let state = req.extensions().get::<ConsistencyMiddlewareState>();
if let Some(state) = state {
if let Some(unified_state) = state.engine.get_state(&workspace_id).await {
let persona_id = unified_state.active_persona.as_ref().map(|p| p.id.clone());
let scenario_id = unified_state.active_scenario.clone();
let reality_level = unified_state.reality_level.value();
let reality_ratio = unified_state.reality_continuum_ratio;
let chaos_rules: Vec<String> = unified_state
.active_chaos_rules
.iter()
.filter_map(|r| r.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()))
.collect();
let request_id = uuid::Uuid::new_v4().to_string();
let path = req.uri().path();
if reality_ratio > 0.0 {
mockforge_core::pillar_tracking::record_reality_usage(
Some(workspace_id.clone()),
None,
"blended_reality_ratio",
serde_json::json!({
"ratio": reality_ratio,
"path": path
}),
)
.await;
}
if !chaos_rules.is_empty() {
mockforge_core::pillar_tracking::record_reality_usage(
Some(workspace_id.clone()),
None,
"chaos_enabled",
serde_json::json!({
"rules": chaos_rules,
"count": chaos_rules.len()
}),
)
.await;
}
let reality_metadata =
RealityTraceMetadata::from_unified_state(&unified_state, reality_ratio, path);
if let Some(xray_state) = &state.xray_state {
let unified_state_clone = unified_state.clone();
let request_id_clone = request_id.clone();
let workspace_id_clone = workspace_id.clone();
let xray_state_clone = xray_state.clone();
tokio::spawn(async move {
crate::handlers::xray::store_request_context(
&xray_state_clone,
request_id_clone,
workspace_id_clone,
&unified_state_clone,
)
.await;
});
}
let mut req = req;
req.extensions_mut().insert(unified_state);
req.extensions_mut().insert(reality_metadata);
let mut response = next.run(req).await;
if let Ok(value) = workspace_id.parse() {
response.headers_mut().insert("X-MockForge-Workspace", value);
}
if let Ok(value) = request_id.parse() {
response.headers_mut().insert("X-MockForge-Request-ID", value);
}
if let Some(ref persona_id) = persona_id {
if let Ok(value) = persona_id.parse() {
response.headers_mut().insert("X-MockForge-Persona", value);
}
}
if let Some(ref scenario_id) = scenario_id {
if let Ok(value) = scenario_id.parse() {
response.headers_mut().insert("X-MockForge-Scenario", value);
}
}
if let Ok(value) = reality_level.to_string().parse() {
response.headers_mut().insert("X-MockForge-Reality-Level", value);
}
if let Ok(value) = reality_ratio.to_string().parse() {
response.headers_mut().insert("X-MockForge-Reality-Ratio", value);
}
if !chaos_rules.is_empty() {
if let Ok(value) = chaos_rules.join(",").parse() {
response.headers_mut().insert("X-MockForge-Chaos-Rules", value);
}
}
return response;
} else {
debug!("No unified state found for workspace {}", workspace_id);
}
}
next.run(req).await
}