mockforge_http/consistency/
middleware.rs1use crate::consistency::HttpAdapter;
7use axum::{body::Body, extract::Request, http::Response, middleware::Next};
8use mockforge_core::consistency::ConsistencyEngine;
9use mockforge_core::request_logger::RealityTraceMetadata;
10use std::sync::Arc;
11use tracing::debug;
12
13#[derive(Clone)]
15pub struct ConsistencyMiddlewareState {
16 pub engine: Arc<ConsistencyEngine>,
18 pub adapter: Arc<HttpAdapter>,
20 pub xray_state: Option<std::sync::Arc<crate::handlers::xray::XRayState>>,
22}
23
24pub async fn consistency_middleware(req: Request, next: Next) -> Response<Body> {
32 let workspace_id = req
35 .headers()
36 .get("X-MockForge-Workspace")
37 .and_then(|h| h.to_str().ok())
38 .map(|s| s.to_string())
39 .or_else(|| {
40 req.uri().query().and_then(|q| {
41 q.split('&').find_map(|pair| {
42 let mut parts = pair.splitn(2, '=');
43 if parts.next() == Some("workspace") {
44 parts.next().and_then(|v| {
45 urlencoding::decode(v).ok().map(|decoded| decoded.to_string())
46 })
47 } else {
48 None
49 }
50 })
51 })
52 })
53 .unwrap_or_else(|| "default".to_string());
54
55 let state = req.extensions().get::<ConsistencyMiddlewareState>();
57
58 if let Some(state) = state {
59 if let Some(unified_state) = state.engine.get_state(&workspace_id).await {
61 let persona_id = unified_state.active_persona.as_ref().map(|p| p.id.clone());
63 let scenario_id = unified_state.active_scenario.clone();
64 let reality_level = unified_state.reality_level.value();
65 let reality_ratio = unified_state.reality_continuum_ratio;
66 let chaos_rules: Vec<String> = unified_state
68 .active_chaos_rules
69 .iter()
70 .filter_map(|r| r.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()))
71 .collect();
72 let request_id = uuid::Uuid::new_v4().to_string();
73
74 let path = req.uri().path();
77
78 if reality_ratio > 0.0 {
80 mockforge_core::pillar_tracking::record_reality_usage(
81 Some(workspace_id.clone()),
82 None,
83 "blended_reality_ratio",
84 serde_json::json!({
85 "ratio": reality_ratio,
86 "path": path
87 }),
88 )
89 .await;
90 }
91
92 if !chaos_rules.is_empty() {
94 mockforge_core::pillar_tracking::record_reality_usage(
95 Some(workspace_id.clone()),
96 None,
97 "chaos_enabled",
98 serde_json::json!({
99 "rules": chaos_rules,
100 "count": chaos_rules.len()
101 }),
102 )
103 .await;
104 }
105 let reality_metadata =
106 RealityTraceMetadata::from_unified_state(&unified_state, reality_ratio, path);
107
108 if let Some(xray_state) = &state.xray_state {
110 let unified_state_clone = unified_state.clone();
112 let request_id_clone = request_id.clone();
113 let workspace_id_clone = workspace_id.clone();
114
115 let xray_state_clone = xray_state.clone();
117 tokio::spawn(async move {
118 crate::handlers::xray::store_request_context(
119 &xray_state_clone,
120 request_id_clone,
121 workspace_id_clone,
122 &unified_state_clone,
123 )
124 .await;
125 });
126 }
127
128 let mut req = req;
130 req.extensions_mut().insert(unified_state);
131 req.extensions_mut().insert(reality_metadata);
132
133 let mut response = next.run(req).await;
135
136 response
138 .headers_mut()
139 .insert("X-MockForge-Workspace", workspace_id.parse().unwrap());
140 response
141 .headers_mut()
142 .insert("X-MockForge-Request-ID", request_id.parse().unwrap());
143 if let Some(ref persona_id) = persona_id {
144 response
145 .headers_mut()
146 .insert("X-MockForge-Persona", persona_id.parse().unwrap());
147 }
148 if let Some(ref scenario_id) = scenario_id {
149 response
150 .headers_mut()
151 .insert("X-MockForge-Scenario", scenario_id.parse().unwrap());
152 }
153 response
154 .headers_mut()
155 .insert("X-MockForge-Reality-Level", reality_level.to_string().parse().unwrap());
156 response
157 .headers_mut()
158 .insert("X-MockForge-Reality-Ratio", reality_ratio.to_string().parse().unwrap());
159 if !chaos_rules.is_empty() {
160 response
161 .headers_mut()
162 .insert("X-MockForge-Chaos-Rules", chaos_rules.join(",").parse().unwrap());
163 }
164
165 return response;
166 } else {
167 debug!("No unified state found for workspace {}", workspace_id);
168 }
169 }
170
171 next.run(req).await
173}