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;
12use uuid::Uuid;
13
14#[derive(Clone)]
16pub struct ConsistencyMiddlewareState {
17 pub engine: Arc<ConsistencyEngine>,
19 pub adapter: Arc<HttpAdapter>,
21}
22
23pub async fn consistency_middleware(req: Request, next: Next) -> Response<Body> {
31 let workspace_id = req
34 .headers()
35 .get("X-MockForge-Workspace")
36 .and_then(|h| h.to_str().ok())
37 .map(|s| s.to_string())
38 .or_else(|| {
39 req.uri().query().and_then(|q| {
40 q.split('&').find_map(|pair| {
41 let mut parts = pair.splitn(2, '=');
42 if parts.next() == Some("workspace") {
43 parts.next().and_then(|v| {
44 urlencoding::decode(v).ok().map(|decoded| decoded.to_string())
45 })
46 } else {
47 None
48 }
49 })
50 })
51 })
52 .unwrap_or_else(|| "default".to_string());
53
54 let state = req.extensions().get::<ConsistencyMiddlewareState>();
56
57 if let Some(state) = state {
58 if let Some(unified_state) = state.engine.get_state(&workspace_id).await {
60 let persona_id = unified_state.active_persona.as_ref().map(|p| p.id.clone());
62 let scenario_id = unified_state.active_scenario.clone();
63 let reality_level = unified_state.reality_level.value();
64 let reality_ratio = unified_state.reality_continuum_ratio;
65 let chaos_rules: Vec<String> = unified_state
67 .active_chaos_rules
68 .iter()
69 .filter_map(|r| r.get("name").and_then(|v| v.as_str()).map(|s| s.to_string()))
70 .collect();
71 let request_id = uuid::Uuid::new_v4().to_string();
72
73 let path = req.uri().path();
76 let reality_metadata =
77 RealityTraceMetadata::from_unified_state(&unified_state, reality_ratio, path);
78
79 let mut req = req;
81 req.extensions_mut().insert(unified_state);
82 req.extensions_mut().insert(reality_metadata);
83
84 let mut response = next.run(req).await;
86
87 response
89 .headers_mut()
90 .insert("X-MockForge-Workspace", workspace_id.parse().unwrap());
91 response
92 .headers_mut()
93 .insert("X-MockForge-Request-ID", request_id.parse().unwrap());
94 if let Some(ref persona_id) = persona_id {
95 response
96 .headers_mut()
97 .insert("X-MockForge-Persona", persona_id.parse().unwrap());
98 }
99 if let Some(ref scenario_id) = scenario_id {
100 response
101 .headers_mut()
102 .insert("X-MockForge-Scenario", scenario_id.parse().unwrap());
103 }
104 response
105 .headers_mut()
106 .insert("X-MockForge-Reality-Level", reality_level.to_string().parse().unwrap());
107 response
108 .headers_mut()
109 .insert("X-MockForge-Reality-Ratio", reality_ratio.to_string().parse().unwrap());
110 if !chaos_rules.is_empty() {
111 response
112 .headers_mut()
113 .insert("X-MockForge-Chaos-Rules", chaos_rules.join(",").parse().unwrap());
114 }
115
116 return response;
117 } else {
118 debug!("No unified state found for workspace {}", workspace_id);
119 }
120 }
121
122 next.run(req).await
124}