mockforge_core/
priority_handler.rs

1//! Priority-based HTTP request handler implementing the full priority chain:
2//! Custom Fixtures → Replay → Stateful → Route Chaos (per-route fault/latency) → Global Fail → Proxy → Mock → Record
3
4use crate::behavioral_economics::BehavioralEconomicsEngine;
5use crate::stateful_handler::StatefulResponseHandler;
6use crate::{
7    CustomFixtureLoader, Error, FailureInjector, ProxyHandler, RealityContinuumEngine,
8    RecordReplayHandler, RequestFingerprint, ResponsePriority, ResponseSource, Result,
9};
10// RouteChaosInjector moved to mockforge-route-chaos crate to avoid Send issues
11// We define a trait here that RouteChaosInjector can implement to avoid circular dependency
12use async_trait::async_trait;
13use axum::http::{HeaderMap, Method, StatusCode, Uri};
14use std::collections::HashMap;
15use std::sync::Arc;
16use tokio::sync::RwLock;
17
18/// Fault injection response (defined in mockforge-core to avoid circular dependency)
19#[derive(Debug, Clone)]
20pub struct RouteFaultResponse {
21    /// HTTP status code
22    pub status_code: u16,
23    /// Error message
24    pub error_message: String,
25    /// Fault type identifier
26    pub fault_type: String,
27}
28
29/// Trait for route chaos injection (fault injection and latency)
30/// This trait is defined in mockforge-core to avoid circular dependency.
31/// The concrete RouteChaosInjector in mockforge-route-chaos implements this trait.
32#[async_trait]
33pub trait RouteChaosInjectorTrait: Send + Sync {
34    /// Inject latency for this request
35    async fn inject_latency(&self, method: &Method, uri: &Uri) -> Result<()>;
36
37    /// Get fault injection response for a request
38    fn get_fault_response(&self, method: &Method, uri: &Uri) -> Option<RouteFaultResponse>;
39}
40
41/// Trait for behavioral scenario replay engines
42#[async_trait]
43pub trait BehavioralScenarioReplay: Send + Sync {
44    /// Try to replay a request against active scenarios
45    async fn try_replay(
46        &self,
47        method: &Method,
48        uri: &Uri,
49        headers: &HeaderMap,
50        body: Option<&[u8]>,
51        session_id: Option<&str>,
52    ) -> Result<Option<BehavioralReplayResponse>>;
53}
54
55/// Response from behavioral scenario replay
56#[derive(Debug, Clone)]
57pub struct BehavioralReplayResponse {
58    /// HTTP status code
59    pub status_code: u16,
60    /// Response headers
61    pub headers: HashMap<String, String>,
62    /// Response body
63    pub body: Vec<u8>,
64    /// Timing delay in milliseconds
65    pub timing_ms: Option<u64>,
66    /// Content type
67    pub content_type: String,
68}
69
70/// Priority-based HTTP request handler
71pub struct PriorityHttpHandler {
72    /// Custom fixture loader (simple format fixtures)
73    custom_fixture_loader: Option<Arc<CustomFixtureLoader>>,
74    /// Record/replay handler
75    record_replay: RecordReplayHandler,
76    /// Behavioral scenario replay engine (for journey-level simulations)
77    behavioral_scenario_replay: Option<Arc<dyn BehavioralScenarioReplay + Send + Sync>>,
78    /// Stateful response handler
79    stateful_handler: Option<Arc<StatefulResponseHandler>>,
80    /// Per-route chaos injector (fault injection and latency)
81    /// Uses trait object to avoid circular dependency with mockforge-route-chaos
82    route_chaos_injector: Option<Arc<dyn RouteChaosInjectorTrait>>,
83    /// Failure injector (global/tag-based)
84    failure_injector: Option<FailureInjector>,
85    /// Proxy handler
86    proxy_handler: Option<ProxyHandler>,
87    /// Mock response generator (from OpenAPI spec)
88    mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
89    /// OpenAPI spec for tag extraction
90    openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
91    /// Reality Continuum engine for blending mock and real responses
92    continuum_engine: Option<Arc<RealityContinuumEngine>>,
93    /// Behavioral Economics Engine for reactive mock behavior
94    behavioral_economics_engine: Option<Arc<RwLock<BehavioralEconomicsEngine>>>,
95    /// Request tracking for metrics (endpoint -> (request_count, error_count, last_request_time))
96    request_metrics: Arc<RwLock<HashMap<String, (u64, u64, std::time::Instant)>>>,
97}
98
99/// Trait for mock response generation
100pub trait MockGenerator {
101    /// Generate a mock response for the given request
102    fn generate_mock_response(
103        &self,
104        fingerprint: &RequestFingerprint,
105        headers: &HeaderMap,
106        body: Option<&[u8]>,
107    ) -> Result<Option<MockResponse>>;
108}
109
110/// Mock response
111#[derive(Debug, Clone)]
112pub struct MockResponse {
113    /// Response status code
114    pub status_code: u16,
115    /// Response headers
116    pub headers: HashMap<String, String>,
117    /// Response body
118    pub body: String,
119    /// Content type
120    pub content_type: String,
121}
122
123impl PriorityHttpHandler {
124    /// Create a new priority HTTP handler
125    pub fn new(
126        record_replay: RecordReplayHandler,
127        failure_injector: Option<FailureInjector>,
128        proxy_handler: Option<ProxyHandler>,
129        mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
130    ) -> Self {
131        Self {
132            custom_fixture_loader: None,
133            record_replay,
134            behavioral_scenario_replay: None,
135            stateful_handler: None,
136            route_chaos_injector: None,
137            failure_injector,
138            proxy_handler,
139            mock_generator,
140            openapi_spec: None,
141            continuum_engine: None,
142            behavioral_economics_engine: None,
143            request_metrics: Arc::new(RwLock::new(HashMap::new())),
144        }
145    }
146
147    /// Create a new priority HTTP handler with OpenAPI spec
148    pub fn new_with_openapi(
149        record_replay: RecordReplayHandler,
150        failure_injector: Option<FailureInjector>,
151        proxy_handler: Option<ProxyHandler>,
152        mock_generator: Option<Box<dyn MockGenerator + Send + Sync>>,
153        openapi_spec: Option<crate::openapi::spec::OpenApiSpec>,
154    ) -> Self {
155        Self {
156            custom_fixture_loader: None,
157            record_replay,
158            behavioral_scenario_replay: None,
159            stateful_handler: None,
160            route_chaos_injector: None,
161            failure_injector,
162            proxy_handler,
163            mock_generator,
164            openapi_spec,
165            continuum_engine: None,
166            behavioral_economics_engine: None,
167            request_metrics: Arc::new(RwLock::new(HashMap::new())),
168        }
169    }
170
171    /// Set custom fixture loader
172    pub fn with_custom_fixture_loader(mut self, loader: Arc<CustomFixtureLoader>) -> Self {
173        self.custom_fixture_loader = Some(loader);
174        self
175    }
176
177    /// Set stateful response handler
178    pub fn with_stateful_handler(mut self, handler: Arc<StatefulResponseHandler>) -> Self {
179        self.stateful_handler = Some(handler);
180        self
181    }
182
183    /// Set per-route chaos injector
184    pub fn with_route_chaos_injector(mut self, injector: Arc<dyn RouteChaosInjectorTrait>) -> Self {
185        self.route_chaos_injector = Some(injector);
186        self
187    }
188
189    /// Set Reality Continuum engine
190    pub fn with_continuum_engine(mut self, engine: Arc<RealityContinuumEngine>) -> Self {
191        self.continuum_engine = Some(engine);
192        self
193    }
194
195    /// Set Behavioral Economics Engine
196    pub fn with_behavioral_economics_engine(
197        mut self,
198        engine: Arc<RwLock<BehavioralEconomicsEngine>>,
199    ) -> Self {
200        self.behavioral_economics_engine = Some(engine);
201        self
202    }
203
204    /// Set behavioral scenario replay engine
205    pub fn with_behavioral_scenario_replay(
206        mut self,
207        replay_engine: Arc<dyn BehavioralScenarioReplay + Send + Sync>,
208    ) -> Self {
209        self.behavioral_scenario_replay = Some(replay_engine);
210        self
211    }
212
213    /// Process a request through the priority chain
214    pub async fn process_request(
215        &self,
216        method: &Method,
217        uri: &Uri,
218        headers: &HeaderMap,
219        body: Option<&[u8]>,
220    ) -> Result<PriorityResponse> {
221        let fingerprint = RequestFingerprint::new(method.clone(), uri, headers, body);
222
223        // 0. CUSTOM FIXTURES: Check if we have a custom fixture (highest priority)
224        if let Some(ref custom_loader) = self.custom_fixture_loader {
225            if let Some(custom_fixture) = custom_loader.load_fixture(&fingerprint) {
226                // Apply delay if specified
227                if custom_fixture.delay_ms > 0 {
228                    tokio::time::sleep(tokio::time::Duration::from_millis(custom_fixture.delay_ms))
229                        .await;
230                }
231
232                // Convert response to JSON string if it's not already a string
233                let response_body = if custom_fixture.response.is_string() {
234                    custom_fixture.response.as_str().unwrap().to_string()
235                } else {
236                    serde_json::to_string(&custom_fixture.response).map_err(|e| {
237                        Error::generic(format!(
238                            "Failed to serialize custom fixture response: {}",
239                            e
240                        ))
241                    })?
242                };
243
244                // Determine content type
245                let content_type = custom_fixture
246                    .headers
247                    .get("content-type")
248                    .cloned()
249                    .unwrap_or_else(|| "application/json".to_string());
250
251                return Ok(PriorityResponse {
252                    source: ResponseSource::new(
253                        ResponsePriority::Replay,
254                        "custom_fixture".to_string(),
255                    )
256                    .with_metadata("fixture_path".to_string(), custom_fixture.path.clone()),
257                    status_code: custom_fixture.status,
258                    headers: custom_fixture.headers.clone(),
259                    body: response_body.into_bytes(),
260                    content_type,
261                });
262            }
263        }
264
265        // 1. REPLAY: Check if we have a recorded fixture
266        if let Some(recorded_request) =
267            self.record_replay.replay_handler().load_fixture(&fingerprint).await?
268        {
269            let content_type = recorded_request
270                .response_headers
271                .get("content-type")
272                .unwrap_or(&"application/json".to_string())
273                .clone();
274
275            return Ok(PriorityResponse {
276                source: ResponseSource::new(ResponsePriority::Replay, "replay".to_string())
277                    .with_metadata("fixture_path".to_string(), "recorded".to_string()),
278                status_code: recorded_request.status_code,
279                headers: recorded_request.response_headers,
280                body: recorded_request.response_body.into_bytes(),
281                content_type,
282            });
283        }
284
285        // 1.5. BEHAVIORAL SCENARIO REPLAY: Check for active behavioral scenarios
286        if let Some(ref scenario_replay) = self.behavioral_scenario_replay {
287            // Extract session ID from headers or cookies
288            let session_id = headers
289                .get("x-session-id")
290                .or_else(|| headers.get("session-id"))
291                .and_then(|v| v.to_str().ok())
292                .map(|s| s.to_string());
293
294            if let Ok(Some(replay_response)) = scenario_replay
295                .try_replay(method, uri, headers, body, session_id.as_deref())
296                .await
297            {
298                // Apply timing delay if specified
299                if let Some(timing_ms) = replay_response.timing_ms {
300                    tokio::time::sleep(tokio::time::Duration::from_millis(timing_ms)).await;
301                }
302                return Ok(PriorityResponse {
303                    source: ResponseSource::new(
304                        ResponsePriority::Replay,
305                        "behavioral_scenario".to_string(),
306                    )
307                    .with_metadata("replay_type".to_string(), "scenario".to_string()),
308                    status_code: replay_response.status_code,
309                    headers: replay_response.headers,
310                    body: replay_response.body,
311                    content_type: replay_response.content_type,
312                });
313            }
314        }
315
316        // 2. STATEFUL: Check for stateful response handling
317        if let Some(ref stateful_handler) = self.stateful_handler {
318            if let Some(stateful_response) =
319                stateful_handler.process_request(method, uri, headers, body).await?
320            {
321                return Ok(PriorityResponse {
322                    source: ResponseSource::new(ResponsePriority::Stateful, "stateful".to_string())
323                        .with_metadata("state".to_string(), stateful_response.state)
324                        .with_metadata("resource_id".to_string(), stateful_response.resource_id),
325                    status_code: stateful_response.status_code,
326                    headers: stateful_response.headers,
327                    body: stateful_response.body.into_bytes(),
328                    content_type: stateful_response.content_type,
329                });
330            }
331        }
332
333        // 2.5. ROUTE CHAOS: Check for per-route fault injection and latency
334        if let Some(ref route_chaos) = self.route_chaos_injector {
335            // Inject latency first (before fault injection)
336            if let Err(e) = route_chaos.inject_latency(method, uri).await {
337                tracing::warn!("Failed to inject per-route latency: {}", e);
338            }
339
340            // Check for per-route fault injection
341            if let Some(fault_response) = route_chaos.get_fault_response(method, uri) {
342                let error_response = serde_json::json!({
343                    "error": fault_response.error_message,
344                    "injected_failure": true,
345                    "fault_type": fault_response.fault_type,
346                    "timestamp": chrono::Utc::now().to_rfc3339()
347                });
348
349                return Ok(PriorityResponse {
350                    source: ResponseSource::new(
351                        ResponsePriority::Fail,
352                        "route_fault_injection".to_string(),
353                    )
354                    .with_metadata("fault_type".to_string(), fault_response.fault_type)
355                    .with_metadata("error_message".to_string(), fault_response.error_message),
356                    status_code: fault_response.status_code,
357                    headers: HashMap::new(),
358                    body: serde_json::to_string(&error_response)?.into_bytes(),
359                    content_type: "application/json".to_string(),
360                });
361            }
362        }
363
364        // 3. FAIL: Check for global/tag-based failure injection
365        if let Some(ref failure_injector) = self.failure_injector {
366            let tags = if let Some(ref spec) = self.openapi_spec {
367                fingerprint.openapi_tags(spec).unwrap_or_else(|| fingerprint.tags())
368            } else {
369                fingerprint.tags()
370            };
371            if let Some((status_code, error_message)) = failure_injector.process_request(&tags) {
372                let error_response = serde_json::json!({
373                    "error": error_message,
374                    "injected_failure": true,
375                    "timestamp": chrono::Utc::now().to_rfc3339()
376                });
377
378                return Ok(PriorityResponse {
379                    source: ResponseSource::new(
380                        ResponsePriority::Fail,
381                        "failure_injection".to_string(),
382                    )
383                    .with_metadata("error_message".to_string(), error_message),
384                    status_code,
385                    headers: HashMap::new(),
386                    body: serde_json::to_string(&error_response)?.into_bytes(),
387                    content_type: "application/json".to_string(),
388                });
389            }
390        }
391
392        // Check if Reality Continuum is enabled and should blend responses
393        let should_blend = if let Some(ref continuum_engine) = self.continuum_engine {
394            continuum_engine.is_enabled().await
395        } else {
396            false
397        };
398
399        // 4. PROXY: Check if request should be proxied (respecting migration mode)
400        if let Some(ref proxy_handler) = self.proxy_handler {
401            // Check migration mode first
402            let migration_mode = if proxy_handler.config.migration_enabled {
403                proxy_handler.config.get_effective_migration_mode(uri.path())
404            } else {
405                None
406            };
407
408            // If migration mode is Mock, skip proxy and continue to mock generator
409            if let Some(crate::proxy::config::MigrationMode::Mock) = migration_mode {
410                // Force mock mode - skip proxy
411            } else if proxy_handler.config.should_proxy_with_condition(method, uri, headers, body) {
412                // Check if this is shadow mode (proxy + generate mock for comparison)
413                let is_shadow = proxy_handler.config.should_shadow(uri.path());
414
415                // If continuum is enabled, we need both mock and real responses
416                if should_blend {
417                    // Fetch both responses in parallel
418                    let proxy_future = proxy_handler.proxy_request(method, uri, headers, body);
419                    let mock_result = if let Some(ref mock_generator) = self.mock_generator {
420                        mock_generator.generate_mock_response(&fingerprint, headers, body)
421                    } else {
422                        Ok(None)
423                    };
424
425                    // Wait for proxy response
426                    let proxy_result = proxy_future.await;
427
428                    // Handle blending
429                    match (proxy_result, mock_result) {
430                        (Ok(proxy_response), Ok(Some(mock_response))) => {
431                            // Both succeeded - blend them
432                            if let Some(ref continuum_engine) = self.continuum_engine {
433                                let blend_ratio =
434                                    continuum_engine.get_blend_ratio(uri.path()).await;
435                                let blender = continuum_engine.blender();
436
437                                // Parse JSON bodies
438                                let mock_body_str = &mock_response.body;
439                                let real_body_bytes =
440                                    proxy_response.body.clone().unwrap_or_default();
441                                let real_body_str = String::from_utf8_lossy(&real_body_bytes);
442
443                                let mock_json: serde_json::Value =
444                                    serde_json::from_str(mock_body_str)
445                                        .unwrap_or_else(|_| serde_json::json!({}));
446                                let real_json: serde_json::Value =
447                                    serde_json::from_str(&real_body_str)
448                                        .unwrap_or_else(|_| serde_json::json!({}));
449
450                                // Blend the JSON responses
451                                let blended_json =
452                                    blender.blend_responses(&mock_json, &real_json, blend_ratio);
453                                let blended_body = serde_json::to_string(&blended_json)
454                                    .unwrap_or_else(|_| real_body_str.to_string());
455
456                                // Blend status codes
457                                let blended_status = blender.blend_status_code(
458                                    mock_response.status_code,
459                                    proxy_response.status_code,
460                                    blend_ratio,
461                                );
462
463                                // Blend headers
464                                let mut proxy_headers = HashMap::new();
465                                for (key, value) in proxy_response.headers.iter() {
466                                    if let Ok(value_str) = value.to_str() {
467                                        proxy_headers.insert(
468                                            key.as_str().to_string(),
469                                            value_str.to_string(),
470                                        );
471                                    }
472                                }
473                                let blended_headers = blender.blend_headers(
474                                    &mock_response.headers,
475                                    &proxy_headers,
476                                    blend_ratio,
477                                );
478
479                                let content_type = blended_headers
480                                    .get("content-type")
481                                    .cloned()
482                                    .or_else(|| {
483                                        proxy_response
484                                            .headers
485                                            .get("content-type")
486                                            .and_then(|v| v.to_str().ok())
487                                            .map(|s| s.to_string())
488                                    })
489                                    .unwrap_or_else(|| "application/json".to_string());
490
491                                tracing::info!(
492                                    path = %uri.path(),
493                                    blend_ratio = blend_ratio,
494                                    "Reality Continuum: blended mock and real responses"
495                                );
496
497                                let mut source = ResponseSource::new(
498                                    ResponsePriority::Proxy,
499                                    "continuum".to_string(),
500                                )
501                                .with_metadata("blend_ratio".to_string(), blend_ratio.to_string())
502                                .with_metadata(
503                                    "upstream_url".to_string(),
504                                    proxy_handler.config.get_upstream_url(uri.path()),
505                                );
506
507                                if let Some(mode) = migration_mode {
508                                    source = source.with_metadata(
509                                        "migration_mode".to_string(),
510                                        format!("{:?}", mode),
511                                    );
512                                }
513
514                                return Ok(PriorityResponse {
515                                    source,
516                                    status_code: blended_status,
517                                    headers: blended_headers,
518                                    body: blended_body.into_bytes(),
519                                    content_type,
520                                });
521                            }
522                        }
523                        (Ok(proxy_response), Ok(None)) => {
524                            // Only proxy succeeded - use it (fallback behavior)
525                            tracing::debug!(
526                                path = %uri.path(),
527                                "Continuum: mock generation failed, using real response"
528                            );
529                            // Fall through to normal proxy handling
530                        }
531                        (Ok(proxy_response), Err(_)) => {
532                            // Only proxy succeeded - use it (fallback behavior)
533                            tracing::debug!(
534                                path = %uri.path(),
535                                "Continuum: mock generation failed, using real response"
536                            );
537                            // Fall through to normal proxy handling
538                        }
539                        (Err(e), Ok(Some(mock_response))) => {
540                            // Only mock succeeded - use it (fallback behavior)
541                            tracing::debug!(
542                                path = %uri.path(),
543                                error = %e,
544                                "Continuum: proxy failed, using mock response"
545                            );
546                            // Fall through to normal mock handling below
547                            let mut source = ResponseSource::new(
548                                ResponsePriority::Mock,
549                                "continuum_fallback".to_string(),
550                            )
551                            .with_metadata("generated_from".to_string(), "openapi_spec".to_string())
552                            .with_metadata(
553                                "fallback_reason".to_string(),
554                                "proxy_failed".to_string(),
555                            );
556
557                            if let Some(mode) = migration_mode {
558                                source = source.with_metadata(
559                                    "migration_mode".to_string(),
560                                    format!("{:?}", mode),
561                                );
562                            }
563
564                            return Ok(PriorityResponse {
565                                source,
566                                status_code: mock_response.status_code,
567                                headers: mock_response.headers,
568                                body: mock_response.body.into_bytes(),
569                                content_type: mock_response.content_type,
570                            });
571                        }
572                        (Err(e), _) => {
573                            // Both failed
574                            tracing::warn!(
575                                path = %uri.path(),
576                                error = %e,
577                                "Continuum: both proxy and mock failed"
578                            );
579                            // If migration mode is Real, fail hard
580                            if let Some(crate::proxy::config::MigrationMode::Real) = migration_mode
581                            {
582                                return Err(Error::generic(format!(
583                                    "Proxy request failed in real mode: {}",
584                                    e
585                                )));
586                            }
587                            // Continue to next handler
588                        }
589                    }
590                }
591
592                // Normal proxy handling (when continuum is not enabled or blending failed)
593                match proxy_handler.proxy_request(method, uri, headers, body).await {
594                    Ok(proxy_response) => {
595                        let mut response_headers = HashMap::new();
596                        for (key, value) in proxy_response.headers.iter() {
597                            let key_str = key.as_str();
598                            if let Ok(value_str) = value.to_str() {
599                                response_headers.insert(key_str.to_string(), value_str.to_string());
600                            }
601                        }
602
603                        let content_type = response_headers
604                            .get("content-type")
605                            .unwrap_or(&"application/json".to_string())
606                            .clone();
607
608                        // If shadow mode, also generate mock response for comparison
609                        if is_shadow {
610                            if let Some(ref mock_generator) = self.mock_generator {
611                                if let Ok(Some(mock_response)) = mock_generator
612                                    .generate_mock_response(&fingerprint, headers, body)
613                                {
614                                    // Log comparison between real and mock
615                                    tracing::info!(
616                                        path = %uri.path(),
617                                        real_status = proxy_response.status_code,
618                                        mock_status = mock_response.status_code,
619                                        "Shadow mode: comparing real and mock responses"
620                                    );
621
622                                    // Compare response bodies (basic comparison)
623                                    let real_body_bytes =
624                                        proxy_response.body.clone().unwrap_or_default();
625                                    let real_body = String::from_utf8_lossy(&real_body_bytes);
626                                    let mock_body = &mock_response.body;
627
628                                    if real_body != *mock_body {
629                                        tracing::warn!(
630                                            path = %uri.path(),
631                                            "Shadow mode: real and mock responses differ"
632                                        );
633                                    }
634                                }
635                            }
636                        }
637
638                        let mut source = ResponseSource::new(
639                            ResponsePriority::Proxy,
640                            if is_shadow {
641                                "shadow".to_string()
642                            } else {
643                                "proxy".to_string()
644                            },
645                        )
646                        .with_metadata(
647                            "upstream_url".to_string(),
648                            proxy_handler.config.get_upstream_url(uri.path()),
649                        );
650
651                        if let Some(mode) = migration_mode {
652                            source = source
653                                .with_metadata("migration_mode".to_string(), format!("{:?}", mode));
654                        }
655
656                        return Ok(PriorityResponse {
657                            source,
658                            status_code: proxy_response.status_code,
659                            headers: response_headers,
660                            body: proxy_response.body.unwrap_or_default(),
661                            content_type,
662                        });
663                    }
664                    Err(e) => {
665                        tracing::warn!("Proxy request failed: {}", e);
666                        // If migration mode is Real, fail hard (don't fall back to mock)
667                        if let Some(crate::proxy::config::MigrationMode::Real) = migration_mode {
668                            return Err(Error::generic(format!(
669                                "Proxy request failed in real mode: {}",
670                                e
671                            )));
672                        }
673                        // Continue to next handler for other modes
674                    }
675                }
676            }
677        }
678
679        // 4. MOCK: Generate mock response from OpenAPI spec
680        if let Some(ref mock_generator) = self.mock_generator {
681            // Check if we're in mock mode (forced by migration)
682            let migration_mode = if let Some(ref proxy_handler) = self.proxy_handler {
683                if proxy_handler.config.migration_enabled {
684                    proxy_handler.config.get_effective_migration_mode(uri.path())
685                } else {
686                    None
687                }
688            } else {
689                None
690            };
691
692            if let Some(mock_response) =
693                mock_generator.generate_mock_response(&fingerprint, headers, body)?
694            {
695                let mut source = ResponseSource::new(ResponsePriority::Mock, "mock".to_string())
696                    .with_metadata("generated_from".to_string(), "openapi_spec".to_string());
697
698                if let Some(mode) = migration_mode {
699                    source =
700                        source.with_metadata("migration_mode".to_string(), format!("{:?}", mode));
701                }
702
703                return Ok(PriorityResponse {
704                    source,
705                    status_code: mock_response.status_code,
706                    headers: mock_response.headers,
707                    body: mock_response.body.into_bytes(),
708                    content_type: mock_response.content_type,
709                });
710            }
711        }
712
713        // 5. RECORD: Record the request for future replay
714        if self.record_replay.record_handler().should_record(method) {
715            // For now, return a default response and record it
716            let default_response = serde_json::json!({
717                "message": "Request recorded for future replay",
718                "timestamp": chrono::Utc::now().to_rfc3339(),
719                "fingerprint": fingerprint.to_hash()
720            });
721
722            let response_body = serde_json::to_string(&default_response)?;
723            let status_code = 200;
724
725            // Record the request
726            self.record_replay
727                .record_handler()
728                .record_request(&fingerprint, status_code, headers, &response_body, None)
729                .await?;
730
731            return Ok(PriorityResponse {
732                source: ResponseSource::new(ResponsePriority::Record, "record".to_string())
733                    .with_metadata("recorded".to_string(), "true".to_string()),
734                status_code,
735                headers: HashMap::new(),
736                body: response_body.into_bytes(),
737                content_type: "application/json".to_string(),
738            });
739        }
740
741        // If we reach here, no handler could process the request
742        Err(Error::generic("No handler could process the request".to_string()))
743    }
744
745    /// Apply behavioral economics rules to a response
746    ///
747    /// Updates condition evaluator with current metrics and evaluates rules,
748    /// then applies any matching actions to modify the response.
749    async fn apply_behavioral_economics(
750        &self,
751        response: PriorityResponse,
752        method: &Method,
753        uri: &Uri,
754        latency_ms: Option<u64>,
755    ) -> Result<PriorityResponse> {
756        if let Some(ref engine) = self.behavioral_economics_engine {
757            let engine = engine.read().await;
758            let evaluator = engine.condition_evaluator();
759
760            // Update condition evaluator with current metrics
761            {
762                let mut eval = evaluator.write().await;
763                if let Some(latency) = latency_ms {
764                    eval.update_latency(uri.path(), latency);
765                }
766
767                // Update load and error rates
768                let endpoint = uri.path().to_string();
769                let mut metrics = self.request_metrics.write().await;
770                let now = std::time::Instant::now();
771
772                // Get or create metrics entry for this endpoint
773                let (request_count, error_count, last_request_time) = metrics
774                    .entry(endpoint.clone())
775                    .or_insert_with(|| (0, 0, now));
776
777                // Increment request count
778                *request_count += 1;
779
780                // Check if this is an error response (status >= 400)
781                if response.status_code >= 400 {
782                    *error_count += 1;
783                }
784
785                // Calculate error rate
786                let error_rate = if *request_count > 0 {
787                    *error_count as f64 / *request_count as f64
788                } else {
789                    0.0
790                };
791                eval.update_error_rate(&endpoint, error_rate);
792
793                // Calculate load (requests per second) based on time window
794                let time_elapsed = now.duration_since(*last_request_time).as_secs_f64();
795                if time_elapsed > 0.0 {
796                    let rps = *request_count as f64 / time_elapsed.max(1.0);
797                    eval.update_load(rps);
798                }
799
800                // Reset metrics periodically (every 60 seconds) to avoid unbounded growth
801                if time_elapsed > 60.0 {
802                    *request_count = 1;
803                    *error_count = if response.status_code >= 400 { 1 } else { 0 };
804                    *last_request_time = now;
805                } else {
806                    *last_request_time = now;
807                }
808            }
809
810            // Evaluate rules and get executed actions
811            let executed_actions = engine.evaluate().await?;
812
813            // Apply actions to response if any were executed
814            if !executed_actions.is_empty() {
815                tracing::debug!(
816                    "Behavioral economics engine executed {} actions",
817                    executed_actions.len()
818                );
819                // Actions are executed by the engine, but we may need to modify
820                // the response based on action results. For now, the engine
821                // handles action execution internally.
822            }
823        }
824
825        Ok(response)
826    }
827}
828
829/// Priority response
830#[derive(Debug, Clone)]
831pub struct PriorityResponse {
832    /// Response source information
833    pub source: ResponseSource,
834    /// HTTP status code
835    pub status_code: u16,
836    /// Response headers
837    pub headers: HashMap<String, String>,
838    /// Response body
839    pub body: Vec<u8>,
840    /// Content type
841    pub content_type: String,
842}
843
844impl PriorityResponse {
845    /// Convert to Axum response
846    pub fn to_axum_response(self) -> axum::response::Response {
847        let mut response = axum::response::Response::new(axum::body::Body::from(self.body));
848        *response.status_mut() = StatusCode::from_u16(self.status_code).unwrap_or(StatusCode::OK);
849
850        // Add headers
851        for (key, value) in self.headers {
852            if let (Ok(header_name), Ok(header_value)) =
853                (key.parse::<axum::http::HeaderName>(), value.parse::<axum::http::HeaderValue>())
854            {
855                response.headers_mut().insert(header_name, header_value);
856            }
857        }
858
859        // Set content type if not already set
860        if !response.headers().contains_key("content-type") {
861            if let Ok(header_value) = self.content_type.parse::<axum::http::HeaderValue>() {
862                response.headers_mut().insert("content-type", header_value);
863            }
864        }
865
866        response
867    }
868}
869
870/// Simple mock generator for testing
871pub struct SimpleMockGenerator {
872    /// Default status code
873    pub default_status: u16,
874    /// Default response body
875    pub default_body: String,
876}
877
878impl SimpleMockGenerator {
879    /// Create a new simple mock generator
880    pub fn new(default_status: u16, default_body: String) -> Self {
881        Self {
882            default_status,
883            default_body,
884        }
885    }
886}
887
888impl MockGenerator for SimpleMockGenerator {
889    fn generate_mock_response(
890        &self,
891        _fingerprint: &RequestFingerprint,
892        _headers: &HeaderMap,
893        _body: Option<&[u8]>,
894    ) -> Result<Option<MockResponse>> {
895        Ok(Some(MockResponse {
896            status_code: self.default_status,
897            headers: HashMap::new(),
898            body: self.default_body.clone(),
899            content_type: "application/json".to_string(),
900        }))
901    }
902}
903
904#[cfg(test)]
905mod tests {
906    use super::*;
907    use tempfile::TempDir;
908
909    #[tokio::test]
910    async fn test_priority_chain() {
911        let temp_dir = TempDir::new().unwrap();
912        let fixtures_dir = temp_dir.path().to_path_buf();
913
914        let record_replay = RecordReplayHandler::new(fixtures_dir, true, true, false);
915        let mock_generator =
916            Box::new(SimpleMockGenerator::new(200, r#"{"message": "mock response"}"#.to_string()));
917
918        let handler = PriorityHttpHandler::new_with_openapi(
919            record_replay,
920            None, // No failure injection
921            None, // No proxy
922            Some(mock_generator),
923            None, // No OpenAPI spec
924        );
925
926        let method = Method::GET;
927        let uri = Uri::from_static("/api/test");
928        let headers = HeaderMap::new();
929
930        let response = handler.process_request(&method, &uri, &headers, None).await.unwrap();
931
932        assert_eq!(response.status_code, 200);
933        assert_eq!(response.source.source_type, "mock");
934    }
935}