mockforge_http/consistency/
http_adapter.rs

1//! HTTP protocol adapter for consistency engine
2//!
3//! This adapter integrates HTTP protocol with the consistency engine,
4//! ensuring HTTP responses reflect the unified state (persona, scenario, etc.)
5
6use mockforge_core::consistency::adapters::ProtocolAdapter;
7use mockforge_core::consistency::types::{PersonaProfile, ProtocolState, StateChangeEvent};
8use mockforge_core::consistency::ConsistencyEngine;
9use mockforge_core::protocol_abstraction::Protocol;
10use mockforge_core::Result;
11use std::sync::Arc;
12use tokio::sync::RwLock;
13use tracing::{debug, info, warn};
14
15/// HTTP protocol adapter for consistency engine
16///
17/// This adapter listens to state change events from the consistency engine
18/// and updates HTTP middleware/handlers to reflect the unified state.
19pub struct HttpAdapter {
20    /// Reference to the consistency engine
21    engine: Arc<ConsistencyEngine>,
22    /// Current persona for each workspace
23    workspace_personas: Arc<RwLock<std::collections::HashMap<String, Option<PersonaProfile>>>>,
24    /// Current scenario for each workspace
25    workspace_scenarios: Arc<RwLock<std::collections::HashMap<String, Option<String>>>>,
26}
27
28impl HttpAdapter {
29    /// Create a new HTTP adapter
30    pub fn new(engine: Arc<ConsistencyEngine>) -> Self {
31        Self {
32            engine,
33            workspace_personas: Arc::new(RwLock::new(std::collections::HashMap::new())),
34            workspace_scenarios: Arc::new(RwLock::new(std::collections::HashMap::new())),
35        }
36    }
37
38    /// Get current persona for a workspace
39    pub async fn get_persona(&self, workspace_id: &str) -> Option<PersonaProfile> {
40        let personas = self.workspace_personas.read().await;
41        personas.get(workspace_id).cloned().flatten()
42    }
43
44    /// Get current scenario for a workspace
45    pub async fn get_scenario(&self, workspace_id: &str) -> Option<String> {
46        let scenarios = self.workspace_scenarios.read().await;
47        scenarios.get(workspace_id).cloned().flatten()
48    }
49}
50
51#[async_trait::async_trait]
52impl ProtocolAdapter for HttpAdapter {
53    fn protocol(&self) -> Protocol {
54        Protocol::Http
55    }
56
57    async fn on_state_change(&self, event: &StateChangeEvent) -> Result<()> {
58        match event {
59            StateChangeEvent::PersonaChanged {
60                workspace_id,
61                persona,
62            } => {
63                let mut personas = self.workspace_personas.write().await;
64                personas.insert(workspace_id.clone(), Some(persona.clone()));
65                info!(
66                    "HTTP adapter: Updated persona for workspace {} to {}",
67                    workspace_id, persona.id
68                );
69            }
70            StateChangeEvent::ScenarioChanged {
71                workspace_id,
72                scenario_id,
73            } => {
74                let mut scenarios = self.workspace_scenarios.write().await;
75                scenarios.insert(workspace_id.clone(), Some(scenario_id.clone()));
76                info!(
77                    "HTTP adapter: Updated scenario for workspace {} to {}",
78                    workspace_id, scenario_id
79                );
80            }
81            StateChangeEvent::RealityLevelChanged { workspace_id, .. } => {
82                debug!("HTTP adapter: Reality level changed for workspace {}", workspace_id);
83                // HTTP responses will use reality level from unified state when generating responses
84            }
85            StateChangeEvent::RealityRatioChanged { workspace_id, .. } => {
86                debug!("HTTP adapter: Reality ratio changed for workspace {}", workspace_id);
87                // HTTP responses will use reality ratio from unified state when blending responses
88            }
89            StateChangeEvent::EntityCreated {
90                workspace_id,
91                entity,
92            } => {
93                debug!(
94                    "HTTP adapter: Entity created {}:{} for workspace {}",
95                    entity.entity_type, entity.entity_id, workspace_id
96                );
97                // Entity is now available for HTTP endpoints to query
98            }
99            StateChangeEvent::EntityUpdated {
100                workspace_id,
101                entity,
102            } => {
103                debug!(
104                    "HTTP adapter: Entity updated {}:{} for workspace {}",
105                    entity.entity_type, entity.entity_id, workspace_id
106                );
107                // Updated entity is now available for HTTP endpoints
108            }
109            StateChangeEvent::ChaosRuleActivated { workspace_id, rule } => {
110                // Note: ChaosScenario is now serde_json::Value, so we extract the name field
111                let rule_name = rule.get("name").and_then(|v| v.as_str()).unwrap_or("unknown");
112                info!(
113                    "HTTP adapter: Chaos rule '{}' activated for workspace {}",
114                    rule_name, workspace_id
115                );
116                // Chaos rule will be applied to HTTP responses via chaos middleware
117            }
118            StateChangeEvent::ChaosRuleDeactivated {
119                workspace_id,
120                rule_name,
121            } => {
122                info!(
123                    "HTTP adapter: Chaos rule '{}' deactivated for workspace {}",
124                    rule_name, workspace_id
125                );
126                // Chaos rule will be removed from HTTP responses
127            }
128        }
129        Ok(())
130    }
131
132    async fn get_current_state(&self, workspace_id: &str) -> Result<Option<ProtocolState>> {
133        // Get protocol state from consistency engine
134        let state = self.engine.get_protocol_state(workspace_id, Protocol::Http).await;
135        Ok(state)
136    }
137
138    async fn apply_persona(&self, workspace_id: &str, persona: &PersonaProfile) -> Result<()> {
139        let mut personas = self.workspace_personas.write().await;
140        personas.insert(workspace_id.to_string(), Some(persona.clone()));
141        info!("HTTP adapter: Applied persona {} to workspace {}", persona.id, workspace_id);
142        Ok(())
143    }
144
145    async fn apply_scenario(&self, workspace_id: &str, scenario_id: &str) -> Result<()> {
146        let mut scenarios = self.workspace_scenarios.write().await;
147        scenarios.insert(workspace_id.to_string(), Some(scenario_id.to_string()));
148        info!("HTTP adapter: Applied scenario {} to workspace {}", scenario_id, workspace_id);
149        Ok(())
150    }
151}