Skip to main content

mockforge_intelligence/
ai_handler.rs

1//! AI-powered response handler for HTTP requests
2//!
3//! This module integrates intelligent mock generation and data drift simulation
4//! into the HTTP request handling pipeline.
5//!
6//! Moved from `mockforge_http::ai_handler` under #656 (post-#555 follow-up).
7//! Original imports went through `mockforge_core::{Result, Error}` re-exports,
8//! both of which now resolve to `mockforge_foundation` — so this module is
9//! cycle-safe with the Issue #562 core↔intelligence cycle-break.
10//!
11//! Note: this module defines its own `AiResponseConfig` wrapper struct
12//! (with `intelligent` + `drift` fields). It is distinct from
13//! `mockforge_foundation::ai_response::AiResponseConfig` (LLM temperature /
14//! max-tokens) — both names coexist in different modules and are not
15//! interchangeable.
16
17use mockforge_data::{
18    DataDriftConfig, DataDriftEngine, IntelligentMockConfig, IntelligentMockGenerator,
19};
20use mockforge_foundation::{Error, Result};
21use serde_json::Value;
22use std::sync::Arc;
23use tokio::sync::RwLock;
24use tracing::{debug, warn};
25
26/// AI response handler that combines intelligent generation and drift simulation
27pub struct AiResponseHandler {
28    /// Intelligent mock generator (if configured)
29    intelligent_generator: Option<IntelligentMockGenerator>,
30    /// Data drift engine (if configured)
31    drift_engine: Option<Arc<RwLock<DataDriftEngine>>>,
32}
33
34impl AiResponseHandler {
35    /// Create a new AI response handler
36    pub fn new(
37        intelligent_config: Option<IntelligentMockConfig>,
38        drift_config: Option<DataDriftConfig>,
39    ) -> Result<Self> {
40        debug!("Creating AI response handler");
41
42        // Initialize intelligent generator if configured
43        let intelligent_generator = if let Some(config) = intelligent_config {
44            debug!("Initializing intelligent mock generator with mode: {:?}", config.mode);
45            Some(IntelligentMockGenerator::new(config).map_err(|e| Error::Config {
46                message: format!("Failed to initialize intelligent generator: {}", e),
47            })?)
48        } else {
49            None
50        };
51
52        // Initialize drift engine if configured
53        let drift_engine = if let Some(config) = drift_config {
54            debug!("Initializing data drift engine");
55            let engine = DataDriftEngine::new(config).map_err(|e| Error::Config {
56                message: format!("Failed to initialize drift engine: {}", e),
57            })?;
58            Some(Arc::new(RwLock::new(engine)))
59        } else {
60            None
61        };
62
63        Ok(Self {
64            intelligent_generator,
65            drift_engine,
66        })
67    }
68
69    /// Check if this handler has any AI features enabled
70    pub fn is_enabled(&self) -> bool {
71        self.intelligent_generator.is_some() || self.drift_engine.is_some()
72    }
73
74    /// Generate a response using configured AI features
75    pub async fn generate_response(&mut self, base_response: Option<Value>) -> Result<Value> {
76        debug!("Generating AI-powered response");
77
78        // Step 1: Generate or use base response
79        let mut response = if let Some(generator) = &mut self.intelligent_generator {
80            match generator.generate().await {
81                Ok(resp) => {
82                    debug!("Intelligent generation successful");
83                    resp
84                }
85                Err(e) => {
86                    warn!("Intelligent generation failed: {}, using fallback", e);
87                    base_response.unwrap_or_else(|| serde_json::json!({}))
88                }
89            }
90        } else if let Some(base) = base_response {
91            base
92        } else {
93            serde_json::json!({})
94        };
95
96        // Step 2: Apply drift if configured
97        if let Some(drift_engine) = &self.drift_engine {
98            match drift_engine.read().await.apply_drift(response.clone()).await {
99                Ok(drifted) => {
100                    debug!("Data drift applied successfully");
101                    response = drifted;
102                }
103                Err(e) => {
104                    warn!("Data drift failed: {}, using non-drifted response", e);
105                }
106            }
107        }
108
109        Ok(response)
110    }
111
112    /// Reset drift state (useful for testing or specific scenarios)
113    pub async fn reset_drift(&self) {
114        if let Some(drift_engine) = &self.drift_engine {
115            drift_engine.read().await.reset().await;
116            debug!("Drift state reset");
117        }
118    }
119
120    /// Get drift request count
121    pub async fn drift_request_count(&self) -> u64 {
122        if let Some(drift_engine) = &self.drift_engine {
123            drift_engine.read().await.request_count().await
124        } else {
125            0
126        }
127    }
128}
129
130/// Helper function to create an AI handler from optional configs
131pub fn create_ai_handler(
132    intelligent_config: Option<IntelligentMockConfig>,
133    drift_config: Option<DataDriftConfig>,
134) -> Result<Option<AiResponseHandler>> {
135    if intelligent_config.is_some() || drift_config.is_some() {
136        Ok(Some(AiResponseHandler::new(intelligent_config, drift_config)?))
137    } else {
138        Ok(None)
139    }
140}
141
142/// Process a response body with AI features if configured
143///
144/// This is a helper function that checks if a response has AI configuration
145/// and applies intelligent generation or drift if present.
146///
147/// # Arguments
148/// * `response_body` - The base response body (as JSON string or Value)
149/// * `intelligent_config` - Optional intelligent mock configuration (from MockResponse.intelligent)
150/// * `drift_config` - Optional drift configuration (from MockResponse.drift)
151///
152/// # Returns
153/// The processed response body as a JSON Value
154pub async fn process_response_with_ai(
155    response_body: Option<Value>,
156    intelligent_config: Option<Value>,
157    drift_config: Option<Value>,
158) -> Result<Value> {
159    // Parse configs if present
160    let intelligent: Option<IntelligentMockConfig> =
161        intelligent_config.and_then(|v| serde_json::from_value(v).ok());
162
163    let drift: Option<DataDriftConfig> = drift_config.and_then(|v| serde_json::from_value(v).ok());
164
165    // If no AI config, return original response
166    if intelligent.is_none() && drift.is_none() {
167        return Ok(response_body.unwrap_or_else(|| serde_json::json!({})));
168    }
169
170    // Create AI handler and generate response
171    let mut handler = AiResponseHandler::new(intelligent, drift)?;
172    handler.generate_response(response_body).await
173}
174
175/// Configuration for AI-powered responses (to be added to MockResponse)
176#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
177pub struct AiResponseConfig {
178    /// Intelligent mock configuration
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub intelligent: Option<IntelligentMockConfig>,
181
182    /// Data drift configuration
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub drift: Option<DataDriftConfig>,
185}
186
187impl AiResponseConfig {
188    /// Check if any AI features are configured
189    pub fn is_enabled(&self) -> bool {
190        self.intelligent.is_some() || self.drift.is_some()
191    }
192
193    /// Create an AI handler from this configuration
194    pub fn create_handler(&self) -> Result<Option<AiResponseHandler>> {
195        create_ai_handler(self.intelligent.clone(), self.drift.clone())
196    }
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use mockforge_data::drift::{DriftRule, DriftStrategy};
203    use mockforge_data::ResponseMode;
204
205    #[test]
206    fn test_ai_handler_creation_intelligent_only() {
207        let config = IntelligentMockConfig::new(ResponseMode::Intelligent)
208            .with_prompt("Test prompt".to_string());
209
210        let result = AiResponseHandler::new(Some(config), None);
211        assert!(result.is_ok());
212
213        let handler = result.unwrap();
214        assert!(handler.is_enabled());
215        assert!(handler.intelligent_generator.is_some());
216        assert!(handler.drift_engine.is_none());
217    }
218
219    #[test]
220    fn test_ai_handler_creation_drift_only() {
221        let rule = DriftRule::new("field".to_string(), DriftStrategy::Linear).with_rate(1.0);
222        let drift_config = DataDriftConfig::new().with_rule(rule);
223
224        let result = AiResponseHandler::new(None, Some(drift_config));
225        assert!(result.is_ok());
226
227        let handler = result.unwrap();
228        assert!(handler.is_enabled());
229        assert!(handler.intelligent_generator.is_none());
230        assert!(handler.drift_engine.is_some());
231    }
232
233    #[test]
234    fn test_ai_handler_creation_both() {
235        let intelligent_config =
236            IntelligentMockConfig::new(ResponseMode::Intelligent).with_prompt("Test".to_string());
237        let rule = DriftRule::new("field".to_string(), DriftStrategy::Linear);
238        let drift_config = DataDriftConfig::new().with_rule(rule);
239
240        let result = AiResponseHandler::new(Some(intelligent_config), Some(drift_config));
241        assert!(result.is_ok());
242
243        let handler = result.unwrap();
244        assert!(handler.is_enabled());
245        assert!(handler.intelligent_generator.is_some());
246        assert!(handler.drift_engine.is_some());
247    }
248
249    #[test]
250    fn test_ai_handler_creation_neither() {
251        let result = AiResponseHandler::new(None, None);
252        assert!(result.is_ok());
253
254        let handler = result.unwrap();
255        assert!(!handler.is_enabled());
256    }
257
258    #[test]
259    fn test_ai_response_config_is_enabled() {
260        let config = AiResponseConfig {
261            intelligent: Some(IntelligentMockConfig::new(ResponseMode::Intelligent)),
262            drift: None,
263        };
264        assert!(config.is_enabled());
265
266        let config = AiResponseConfig {
267            intelligent: None,
268            drift: None,
269        };
270        assert!(!config.is_enabled());
271    }
272
273    #[tokio::test]
274    async fn test_generate_response_with_base() {
275        let mut handler = AiResponseHandler::new(None, None).unwrap();
276        let base = serde_json::json!({"test": "value"});
277
278        let result = handler.generate_response(Some(base.clone())).await;
279        assert!(result.is_ok());
280        assert_eq!(result.unwrap(), base);
281    }
282}