Skip to main content

mockforge_http/
lib.rs

1// TODO: remove once mockforge-intelligence, mockforge-proxy, etc. crates are extracted
2// Deprecation allowances are now scoped to individual handler files
3// (handlers/*.rs, middleware/drift_tracking.rs, rag_ai_generator.rs,
4// management/ai_gen.rs) and to specific build_router functions below,
5// rather than being crate-wide.
6
7//! # MockForge HTTP
8//!
9//! HTTP/REST API mocking library for MockForge.
10//!
11//! This crate provides HTTP-specific functionality for creating mock REST APIs,
12//! including OpenAPI integration, request validation, AI-powered response generation,
13//! and management endpoints.
14//!
15//! ## Overview
16//!
17//! MockForge HTTP enables you to:
18//!
19//! - **Serve OpenAPI specs**: Automatically generate mock endpoints from OpenAPI/Swagger
20//! - **Validate requests**: Enforce schema validation with configurable modes
21//! - **AI-powered responses**: Generate intelligent responses using LLMs
22//! - **Management API**: Real-time monitoring, configuration, and control
23//! - **Request logging**: Comprehensive HTTP request/response logging
24//! - **Metrics collection**: Track performance and usage statistics
25//! - **Server-Sent Events**: Stream logs and metrics to clients
26//!
27//! ## Quick Start
28//!
29//! ### Basic HTTP Server from OpenAPI
30//!
31//! ```rust,no_run
32//! use axum::Router;
33//! use mockforge_openapi::openapi_routes::ValidationMode;
34//! use mockforge_openapi::openapi_routes::ValidationOptions;
35//! use mockforge_http::build_router;
36//!
37//! #[tokio::main]
38//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
39//!     // Build router from OpenAPI specification
40//!     let router = build_router(
41//!         Some("./api-spec.json".to_string()),
42//!         Some(ValidationOptions {
43//!             request_mode: ValidationMode::Enforce,
44//!             ..ValidationOptions::default()
45//!         }),
46//!         None,
47//!     ).await;
48//!
49//!     // Start the server
50//!     let addr: std::net::SocketAddr = "0.0.0.0:3000".parse()?;
51//!     let listener = tokio::net::TcpListener::bind(addr).await?;
52//!     axum::serve(listener, router).await?;
53//!
54//!     Ok(())
55//! }
56//! ```
57//!
58//! ### With Management API
59//!
60//! Enable real-time monitoring and configuration:
61//!
62//! ```rust,no_run
63//! use mockforge_http::{management_router, ManagementState};
64//!
65//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
66//! let state = ManagementState::new(None, None, 3000);
67//!
68//! // Build management router
69//! let mgmt_router = management_router(state);
70//!
71//! // Mount under your main router
72//! let app = axum::Router::new()
73//!     .nest("/__mockforge", mgmt_router);
74//! # Ok(())
75//! # }
76//! ```
77//!
78//! ### AI-Powered Responses
79//!
80//! Generate intelligent responses based on request context:
81//!
82//! ```rust,no_run
83//! use mockforge_data::intelligent_mock::{IntelligentMockConfig, ResponseMode};
84//! use mockforge_http::{process_response_with_ai, AiResponseConfig};
85//! use serde_json::json;
86//!
87//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
88//! let ai_config = AiResponseConfig {
89//!     intelligent: Some(
90//!         IntelligentMockConfig::new(ResponseMode::Intelligent)
91//!             .with_prompt("Generate realistic user data".to_string()),
92//!     ),
93//!     drift: None,
94//! };
95//!
96//! let response = process_response_with_ai(
97//!     Some(json!({"name": "Alice"})),
98//!     ai_config
99//!         .intelligent
100//!         .clone()
101//!         .map(serde_json::to_value)
102//!         .transpose()?,
103//!     ai_config
104//!         .drift
105//!         .clone()
106//!         .map(serde_json::to_value)
107//!         .transpose()?,
108//! )
109//! .await?;
110//! # Ok(())
111//! # }
112//! ```
113//!
114//! ## Key Features
115//!
116//! ### OpenAPI Integration
117//! - Automatic endpoint generation from specs
118//! - Request/response validation
119//! - Schema-based mock data generation
120//!
121//! ### Management & Monitoring
122//! - [`management`]: REST API for server control and monitoring
123//! - [`management_ws`]: WebSocket API for real-time updates
124//! - [`sse`]: Server-Sent Events for log streaming
125//! - [`request_logging`]: Comprehensive request/response logging
126//! - [`metrics_middleware`]: Performance metrics collection
127//!
128//! ### Advanced Features
129//! - [`ai_handler`]: AI-powered response generation
130//! - [`auth`]: Authentication and authorization
131//! - [`chain_handlers`]: Multi-step request workflows
132//! - [`latency_profiles`]: Configurable latency simulation
133//! - [`replay_listing`]: Fixture management
134//!
135//! ## Middleware
136//!
137//! MockForge HTTP includes several middleware layers:
138//!
139//! - **Request Tracing**: [`http_tracing_middleware`] - Distributed tracing integration
140//! - **Metrics Collection**: [`metrics_middleware`] - Prometheus-compatible metrics
141//! - **Operation Metadata**: [`op_middleware`] - OpenAPI operation tracking
142//!
143//! ## Management API Endpoints
144//!
145//! When using the management router, these endpoints are available:
146//!
147//! - `GET /health` - Health check
148//! - `GET /stats` - Server statistics
149//! - `GET /logs` - Request logs (SSE stream)
150//! - `GET /metrics` - Performance metrics
151//! - `GET /fixtures` - List available fixtures
152//! - `POST /config/*` - Update configuration
153//!
154//! ## Examples
155//!
156//! See the [examples directory](https://github.com/SaaSy-Solutions/mockforge/tree/main/examples)
157//! for complete working examples.
158//!
159//! ## Related Crates
160//!
161//! - [`mockforge-core`](https://docs.rs/mockforge-core): Core mocking functionality
162//! - [`mockforge-data`](https://docs.rs/mockforge-data): Synthetic data generation
163//! - [`mockforge-plugin-core`](https://docs.rs/mockforge-plugin-core): Plugin development
164//!
165//! ## Documentation
166//!
167//! - [MockForge Book](https://docs.mockforge.dev/)
168//! - [HTTP Mocking Guide](https://docs.mockforge.dev/user-guide/http-mocking.html)
169//! - [API Reference](https://docs.rs/mockforge-http)
170
171pub mod ai_handler;
172pub mod auth;
173pub mod chain_handlers;
174/// Cross-protocol consistency engine integration for HTTP
175pub mod consistency;
176/// Contract diff retrieval API
177pub mod contract_diff_api;
178/// Contract diff middleware for automatic request capture
179pub mod contract_diff_middleware;
180/// TCP-listener wrapper that bumps the global `record_accept()` counter,
181/// used by the dashboard sampler to derive connections-per-second.
182pub mod counting_listener;
183pub mod coverage;
184/// Database connection wrapper — moved to `mockforge_intelligence::database`
185/// under #555 (prereq for handler moves). Re-exported here so existing
186/// `mockforge_http::database::Database` callers (4 handler files + the
187/// router init code below) keep resolving. Gated by the `database` feature,
188/// which now plumbs `mockforge-intelligence/database` transitively.
189#[cfg(feature = "database")]
190pub mod database {
191    pub use mockforge_intelligence::database::*;
192}
193/// File generation service for creating mock PDF, CSV, JSON files
194pub mod file_generator;
195/// File serving for generated mock files
196pub mod file_server;
197/// Fixtures management API for hosted-mock deployments
198pub mod fixtures_api;
199/// Kubernetes-native health check endpoints (liveness, readiness, startup probes)
200pub mod health;
201pub mod http_tracing_middleware;
202/// Latency profile configuration for HTTP request simulation
203pub mod latency_profiles;
204/// Management API for server control and monitoring
205pub mod management;
206/// WebSocket-based management API for real-time updates
207pub mod management_ws;
208pub mod metrics_middleware;
209pub mod middleware;
210/// Standalone MockAI HTTP API
211pub mod mockai_api;
212/// Runtime network-profile switching API
213pub mod network_profile_runtime;
214pub mod op_middleware;
215/// Unified protocol server lifecycle implementation
216pub mod protocol_server;
217/// Browser/Mobile Proxy Server
218pub mod proxy_server;
219/// Quick mock generation utilities
220pub mod quick_mock;
221/// RAG-powered AI response generation
222pub mod rag_ai_generator;
223/// Reality-slider-driven mock/proxy switching middleware (#222)
224pub mod reality_proxy;
225/// Replay listing and fixture management
226pub mod replay_listing;
227pub mod request_logging;
228/// Runtime route-scoped chaos rules API
229pub mod route_chaos_runtime;
230/// Runtime named-scenario activation API
231#[cfg(feature = "scenario-engine")]
232pub mod scenarios_runtime;
233/// Specification import API for OpenAPI and AsyncAPI
234pub mod spec_import;
235/// Server-Sent Events for streaming logs and metrics
236pub mod sse;
237/// State machine API for scenario state machines
238pub mod state_machine_api;
239/// Time-travel runtime API mirrored from the admin server onto the main HTTP port
240pub mod time_travel_api;
241/// TLS/HTTPS support
242pub mod tls;
243/// Token response utilities
244pub mod token_response;
245/// UI Builder API for low-code mock endpoint creation
246pub mod ui_builder;
247/// Verification API for request verification
248pub mod verification;
249
250// Access review handlers
251pub mod handlers;
252
253// Re-export AI handler utilities
254pub use ai_handler::{process_response_with_ai, AiResponseConfig, AiResponseHandler};
255// Re-export health check utilities
256pub use health::{HealthManager, ServiceStatus};
257
258// Re-export management API utilities
259pub use management::{
260    management_router, management_router_with_ui_builder, ManagementState, MockConfig,
261    ServerConfig, ServerStats,
262};
263
264// Re-export UI Builder utilities
265pub use ui_builder::{create_ui_builder_router, EndpointConfig, UIBuilderState};
266
267// Re-export management WebSocket utilities
268pub use management_ws::{ws_management_router, MockEvent, WsManagementState};
269
270// Re-export verification API utilities
271pub use verification::verification_router;
272
273// Re-export metrics middleware
274pub use metrics_middleware::collect_http_metrics;
275
276// Re-export tracing middleware
277pub use http_tracing_middleware::http_tracing_middleware;
278
279// Re-export coverage utilities
280pub use coverage::{calculate_coverage, CoverageReport, MethodCoverage, RouteCoverage};
281
282/// Helper function to load persona from config file
283/// Tries to load from common config locations: config.yaml, mockforge.yaml, tools/mockforge/config.yaml
284async fn load_persona_from_config() -> Option<Arc<Persona>> {
285    use mockforge_core::config::load_config;
286
287    // Try common config file locations
288    let config_paths = [
289        "config.yaml",
290        "mockforge.yaml",
291        "tools/mockforge/config.yaml",
292        "../tools/mockforge/config.yaml",
293    ];
294
295    for path in &config_paths {
296        if let Ok(config) = load_config(path).await {
297            // Access intelligent_behavior through mockai config
298            // Note: Config structure is mockai.intelligent_behavior.personas
299            if let Some(persona) = config.mockai.intelligent_behavior.personas.get_active_persona()
300            {
301                tracing::info!(
302                    "Loaded active persona '{}' from config file: {}",
303                    persona.name,
304                    path
305                );
306                return Some(Arc::new(persona.clone()));
307            } else {
308                tracing::debug!(
309                    "No active persona found in config file: {} (personas count: {})",
310                    path,
311                    config.mockai.intelligent_behavior.personas.personas.len()
312                );
313            }
314        } else {
315            tracing::debug!("Could not load config from: {}", path);
316        }
317    }
318
319    tracing::debug!("No persona found in config files, persona-based generation will be disabled");
320    None
321}
322
323use axum::body::Body;
324use axum::extract::State;
325use axum::http::Request;
326use axum::middleware::from_fn_with_state;
327use axum::response::Json;
328use axum::Router;
329use mockforge_chaos::core_failure_injection::{FailureConfig, FailureInjector};
330use mockforge_core::intelligent_behavior::config::Persona;
331use mockforge_foundation::latency::LatencyInjector;
332use mockforge_openapi::openapi_routes::OpenApiRouteRegistry;
333use mockforge_openapi::openapi_routes::ValidationOptions;
334use mockforge_openapi::OpenApiSpec;
335use std::sync::Arc;
336use tower_http::cors::{Any, CorsLayer};
337
338#[cfg(feature = "data-faker")]
339use mockforge_data::provider::register_core_faker_provider;
340use mockforge_foundation::latency::LatencyProfile;
341use std::collections::HashMap;
342use std::ffi::OsStr;
343use std::path::Path;
344use tokio::fs;
345use tokio::sync::RwLock;
346use tracing::*;
347
348/// Route info for storing in state
349#[derive(Clone)]
350pub struct RouteInfo {
351    /// HTTP method (GET, POST, PUT, etc.)
352    pub method: String,
353    /// API path pattern (e.g., "/api/users/{id}")
354    pub path: String,
355    /// OpenAPI operation ID if available
356    pub operation_id: Option<String>,
357    /// Operation summary from OpenAPI spec
358    pub summary: Option<String>,
359    /// Operation description from OpenAPI spec
360    pub description: Option<String>,
361    /// List of parameter names for this route
362    pub parameters: Vec<String>,
363}
364
365/// Shared state for tracking OpenAPI routes
366#[derive(Clone)]
367pub struct HttpServerState {
368    /// List of registered routes from OpenAPI spec
369    pub routes: Vec<RouteInfo>,
370    /// Optional global rate limiter for request throttling
371    pub rate_limiter: Option<Arc<middleware::rate_limit::GlobalRateLimiter>>,
372    /// Production headers to add to all responses (for deceptive deploy)
373    pub production_headers: Option<Arc<HashMap<String, String>>>,
374}
375
376impl Default for HttpServerState {
377    fn default() -> Self {
378        Self::new()
379    }
380}
381
382impl HttpServerState {
383    /// Create a new empty HTTP server state
384    pub fn new() -> Self {
385        Self {
386            routes: Vec::new(),
387            rate_limiter: None,
388            production_headers: None,
389        }
390    }
391
392    /// Create HTTP server state with pre-configured routes
393    pub fn with_routes(routes: Vec<RouteInfo>) -> Self {
394        Self {
395            routes,
396            rate_limiter: None,
397            production_headers: None,
398        }
399    }
400
401    /// Add a rate limiter to the HTTP server state
402    pub fn with_rate_limiter(
403        mut self,
404        rate_limiter: Arc<middleware::rate_limit::GlobalRateLimiter>,
405    ) -> Self {
406        self.rate_limiter = Some(rate_limiter);
407        self
408    }
409
410    /// Add production headers to the HTTP server state
411    pub fn with_production_headers(mut self, headers: Arc<HashMap<String, String>>) -> Self {
412        self.production_headers = Some(headers);
413        self
414    }
415}
416
417/// Handler to return OpenAPI routes information
418async fn get_routes_handler(State(state): State<HttpServerState>) -> Json<serde_json::Value> {
419    let route_info: Vec<serde_json::Value> = state
420        .routes
421        .iter()
422        .map(|route| {
423            serde_json::json!({
424                "method": route.method,
425                "path": route.path,
426                "operation_id": route.operation_id,
427                "summary": route.summary,
428                "description": route.description,
429                "parameters": route.parameters
430            })
431        })
432        .collect();
433
434    Json(serde_json::json!({
435        "routes": route_info,
436        "total": state.routes.len()
437    }))
438}
439
440/// Handler to serve the Scalar API docs page
441async fn get_docs_handler() -> axum::response::Html<&'static str> {
442    axum::response::Html(include_str!("../static/docs.html"))
443}
444
445/// Build the base HTTP router, optionally from an OpenAPI spec.
446pub async fn build_router(
447    spec_path: Option<String>,
448    options: Option<ValidationOptions>,
449    failure_config: Option<FailureConfig>,
450) -> Router {
451    build_router_with_multi_tenant(
452        spec_path,
453        options,
454        failure_config,
455        None,
456        None,
457        None,
458        None,
459        None,
460        None,
461        None,
462    )
463    .await
464}
465
466/// Apply CORS middleware to the router based on configuration
467fn apply_cors_middleware(
468    app: Router,
469    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
470) -> Router {
471    use http::Method;
472    use tower_http::cors::AllowOrigin;
473
474    if let Some(config) = cors_config {
475        if !config.enabled {
476            return app;
477        }
478
479        let mut cors_layer = CorsLayer::new();
480        let is_wildcard_origin;
481
482        // Configure allowed origins
483        if config.allowed_origins.contains(&"*".to_string()) {
484            cors_layer = cors_layer.allow_origin(Any);
485            is_wildcard_origin = true;
486        } else if !config.allowed_origins.is_empty() {
487            // Try to parse each origin, fallback to permissive if parsing fails
488            let origins: Vec<_> = config
489                .allowed_origins
490                .iter()
491                .filter_map(|origin| {
492                    origin.parse::<http::HeaderValue>().ok().map(AllowOrigin::exact)
493                })
494                .collect();
495
496            if origins.is_empty() {
497                // If no valid origins, use permissive for development
498                warn!("No valid CORS origins configured, using permissive CORS");
499                cors_layer = cors_layer.allow_origin(Any);
500                is_wildcard_origin = true;
501            } else {
502                // Use the first origin as exact match (tower-http limitation)
503                // For multiple origins, we'd need a custom implementation
504                if origins.len() == 1 {
505                    cors_layer = cors_layer.allow_origin(origins[0].clone());
506                    is_wildcard_origin = false;
507                } else {
508                    // Multiple origins - use permissive for now
509                    warn!(
510                        "Multiple CORS origins configured, using permissive CORS. \
511                        Consider using '*' for all origins."
512                    );
513                    cors_layer = cors_layer.allow_origin(Any);
514                    is_wildcard_origin = true;
515                }
516            }
517        } else {
518            // No origins specified, use permissive for development
519            cors_layer = cors_layer.allow_origin(Any);
520            is_wildcard_origin = true;
521        }
522
523        // Configure allowed methods
524        if !config.allowed_methods.is_empty() {
525            let methods: Vec<Method> =
526                config.allowed_methods.iter().filter_map(|m| m.parse().ok()).collect();
527            if !methods.is_empty() {
528                cors_layer = cors_layer.allow_methods(methods);
529            }
530        } else {
531            // Default to common HTTP methods
532            cors_layer = cors_layer.allow_methods([
533                Method::GET,
534                Method::POST,
535                Method::PUT,
536                Method::DELETE,
537                Method::PATCH,
538                Method::OPTIONS,
539            ]);
540        }
541
542        // Configure allowed headers
543        if !config.allowed_headers.is_empty() {
544            let headers: Vec<_> = config
545                .allowed_headers
546                .iter()
547                .filter_map(|h| h.parse::<http::HeaderName>().ok())
548                .collect();
549            if !headers.is_empty() {
550                cors_layer = cors_layer.allow_headers(headers);
551            }
552        } else {
553            // Default headers
554            cors_layer =
555                cors_layer.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION]);
556        }
557
558        // Configure credentials - cannot allow credentials with wildcard origin
559        // Determine if credentials should be allowed
560        // Cannot allow credentials with wildcard origin per CORS spec
561        let should_allow_credentials = if is_wildcard_origin {
562            // Wildcard origin - credentials must be false
563            false
564        } else {
565            // Specific origins - use config value (defaults to false)
566            config.allow_credentials
567        };
568
569        cors_layer = cors_layer.allow_credentials(should_allow_credentials);
570
571        info!(
572            "CORS middleware enabled with configured settings (credentials: {})",
573            should_allow_credentials
574        );
575        app.layer(cors_layer)
576    } else {
577        // No CORS config provided - use permissive CORS for development
578        // Note: permissive() allows credentials, but since it uses wildcard origin,
579        // we need to disable credentials to avoid CORS spec violation
580        debug!("No CORS config provided, using permissive CORS for development");
581        // Create a permissive CORS layer but disable credentials to avoid CORS spec violation
582        // (cannot combine credentials with wildcard origin)
583        app.layer(CorsLayer::permissive().allow_credentials(false))
584    }
585}
586
587/// Build the base HTTP router with multi-tenant workspace support
588#[allow(clippy::too_many_arguments)]
589#[allow(deprecated)] // uses MockAI, MultiTenantWorkspaceRegistry, WorkspaceRouter, Workspace (all stay in core)
590pub async fn build_router_with_multi_tenant(
591    spec_path: Option<String>,
592    options: Option<ValidationOptions>,
593    failure_config: Option<FailureConfig>,
594    multi_tenant_config: Option<mockforge_foundation::multi_tenant_types::MultiTenantConfig>,
595    _route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
596    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
597    ai_generator: Option<Arc<dyn mockforge_openapi::response::AiGenerator + Send + Sync>>,
598    smtp_registry: Option<Arc<dyn std::any::Any + Send + Sync>>,
599    mockai: Option<Arc<RwLock<mockforge_core::intelligent_behavior::MockAI>>>,
600    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
601) -> Router {
602    use std::time::Instant;
603
604    let startup_start = Instant::now();
605
606    // Set up the basic router
607    let mut app = Router::new();
608
609    // Initialize rate limiter with default configuration
610    // Can be customized via environment variables or config
611    let mut rate_limit_config = middleware::RateLimitConfig {
612        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
613            .ok()
614            .and_then(|v| v.parse().ok())
615            .unwrap_or(1000),
616        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
617            .ok()
618            .and_then(|v| v.parse().ok())
619            .unwrap_or(2000),
620        per_ip: true,
621        per_endpoint: false,
622    };
623
624    // Apply deceptive deploy configuration if enabled
625    let mut final_cors_config = cors_config;
626    let mut production_headers: Option<std::sync::Arc<HashMap<String, String>>> = None;
627    // Auth config from deceptive deploy OAuth (if configured)
628    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
629
630    if let Some(deploy_config) = &deceptive_deploy_config {
631        if deploy_config.enabled {
632            info!("Deceptive deploy mode enabled - applying production-like configuration");
633
634            // Override CORS config if provided
635            if let Some(prod_cors) = &deploy_config.cors {
636                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
637                    enabled: true,
638                    allowed_origins: prod_cors.allowed_origins.clone(),
639                    allowed_methods: prod_cors.allowed_methods.clone(),
640                    allowed_headers: prod_cors.allowed_headers.clone(),
641                    allow_credentials: prod_cors.allow_credentials,
642                });
643                info!("Applied production-like CORS configuration");
644            }
645
646            // Override rate limit config if provided
647            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
648                rate_limit_config = middleware::RateLimitConfig {
649                    requests_per_minute: prod_rate_limit.requests_per_minute,
650                    burst: prod_rate_limit.burst,
651                    per_ip: prod_rate_limit.per_ip,
652                    per_endpoint: false,
653                };
654                info!(
655                    "Applied production-like rate limiting: {} req/min, burst: {}",
656                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
657                );
658            }
659
660            // Set production headers
661            if !deploy_config.headers.is_empty() {
662                let headers_map: HashMap<String, String> = deploy_config.headers.clone();
663                production_headers = Some(std::sync::Arc::new(headers_map));
664                info!("Configured {} production headers", deploy_config.headers.len());
665            }
666
667            // Integrate OAuth config from deceptive deploy
668            if let Some(prod_oauth) = &deploy_config.oauth {
669                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
670                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
671                    oauth2: Some(oauth2_config),
672                    ..Default::default()
673                });
674                info!("Applied production-like OAuth configuration for deceptive deploy");
675            }
676        }
677    }
678
679    let rate_limit_disabled = middleware::is_rate_limit_disabled();
680    let rate_limiter =
681        std::sync::Arc::new(middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
682
683    let mut state = HttpServerState::new();
684    if rate_limit_disabled {
685        info!(
686            "HTTP rate limiting disabled (MOCKFORGE_RATE_LIMIT_ENABLED=false or --no-rate-limit)"
687        );
688    } else {
689        state = state.with_rate_limiter(rate_limiter.clone());
690    }
691
692    // Add production headers to state if configured
693    if let Some(headers) = production_headers.clone() {
694        state = state.with_production_headers(headers);
695    }
696
697    // Clone spec_path for later use
698    let spec_path_for_mgmt = spec_path.clone();
699
700    // If an OpenAPI spec is provided, integrate it
701    if let Some(spec_path) = spec_path {
702        tracing::debug!("Processing OpenAPI spec path: {}", spec_path);
703
704        // Measure OpenAPI spec loading
705        let spec_load_start = Instant::now();
706        match OpenApiSpec::from_file(&spec_path).await {
707            Ok(openapi) => {
708                let spec_load_duration = spec_load_start.elapsed();
709                info!(
710                    "Successfully loaded OpenAPI spec from {} (took {:?})",
711                    spec_path, spec_load_duration
712                );
713
714                // Measure route registry creation
715                tracing::debug!("Creating OpenAPI route registry...");
716                let registry_start = Instant::now();
717
718                // Try to load persona from config if available
719                let persona = load_persona_from_config().await;
720
721                let registry = if let Some(opts) = options {
722                    tracing::debug!("Using custom validation options");
723                    if let Some(ref persona) = persona {
724                        tracing::info!("Using persona '{}' for route generation", persona.name);
725                    }
726                    OpenApiRouteRegistry::new_with_options_and_persona(openapi, opts, persona)
727                } else {
728                    tracing::debug!("Using environment-based options");
729                    if let Some(ref persona) = persona {
730                        tracing::info!("Using persona '{}' for route generation", persona.name);
731                    }
732                    OpenApiRouteRegistry::new_with_env_and_persona(openapi, persona)
733                };
734                let registry_duration = registry_start.elapsed();
735                info!(
736                    "Created OpenAPI route registry with {} routes (took {:?})",
737                    registry.routes().len(),
738                    registry_duration
739                );
740
741                // Measure route extraction
742                let extract_start = Instant::now();
743                let route_info: Vec<RouteInfo> = registry
744                    .routes()
745                    .iter()
746                    .map(|route| RouteInfo {
747                        method: route.method.clone(),
748                        path: route.path.clone(),
749                        operation_id: route.operation.operation_id.clone(),
750                        summary: route.operation.summary.clone(),
751                        description: route.operation.description.clone(),
752                        parameters: route.parameters.clone(),
753                    })
754                    .collect();
755                state.routes = route_info;
756                let extract_duration = extract_start.elapsed();
757                debug!("Extracted route information (took {:?})", extract_duration);
758
759                // Measure overrides loading
760                let overrides = if std::env::var("MOCKFORGE_HTTP_OVERRIDES_GLOB").is_ok() {
761                    tracing::debug!("Loading overrides from environment variable");
762                    let overrides_start = Instant::now();
763                    match mockforge_core::Overrides::load_from_globs(&[]).await {
764                        Ok(overrides) => {
765                            let overrides_duration = overrides_start.elapsed();
766                            info!(
767                                "Loaded {} override rules (took {:?})",
768                                overrides.rules().len(),
769                                overrides_duration
770                            );
771                            Some(overrides)
772                        }
773                        Err(e) => {
774                            tracing::warn!("Failed to load overrides: {}", e);
775                            None
776                        }
777                    }
778                } else {
779                    None
780                };
781
782                // Measure router building
783                let router_build_start = Instant::now();
784                let overrides_enabled = overrides.is_some();
785                let response_rewriter: Option<
786                    std::sync::Arc<dyn mockforge_openapi::response_rewriter::ResponseRewriter>,
787                > = Some(std::sync::Arc::new(
788                    mockforge_core::openapi_rewriter::CoreResponseRewriter::new(overrides),
789                ));
790                let openapi_router = if let Some(mockai_instance) = &mockai {
791                    tracing::debug!("Building router with MockAI support");
792                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
793                } else if let Some(ai_generator) = &ai_generator {
794                    tracing::debug!("Building router with AI generator support");
795                    registry.build_router_with_ai(Some(ai_generator.clone()))
796                } else if let Some(failure_config) = &failure_config {
797                    tracing::debug!("Building router with failure injection and overrides");
798                    let failure_injector = FailureInjector::new(Some(failure_config.clone()), true);
799                    registry.build_router_with_injectors_and_overrides(
800                        LatencyInjector::default(),
801                        Some(failure_injector),
802                        response_rewriter,
803                        overrides_enabled,
804                    )
805                } else {
806                    tracing::debug!("Building router with overrides");
807                    registry.build_router_with_injectors_and_overrides(
808                        LatencyInjector::default(),
809                        None,
810                        response_rewriter,
811                        overrides_enabled,
812                    )
813                };
814                let router_build_duration = router_build_start.elapsed();
815                debug!("Built OpenAPI router (took {:?})", router_build_duration);
816
817                tracing::debug!("Merging OpenAPI router with main router");
818                app = app.merge(openapi_router);
819                tracing::debug!("Router built successfully");
820            }
821            Err(e) => {
822                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
823            }
824        }
825    }
826
827    // Add basic health check endpoint
828    app = app.route(
829        "/health",
830        axum::routing::get(|| async {
831            use mockforge_core::server_utils::health::HealthStatus;
832            {
833                // HealthStatus should always serialize, but handle errors gracefully
834                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
835                    Ok(value) => Json(value),
836                    Err(e) => {
837                        // Log error but return a simple healthy response
838                        tracing::error!("Failed to serialize health status: {}", e);
839                        Json(serde_json::json!({
840                            "status": "healthy",
841                            "service": "mockforge-http",
842                            "uptime_seconds": 0
843                        }))
844                    }
845                }
846            }
847        }),
848    )
849    // Add SSE endpoints
850    .merge(sse::sse_router())
851    // Add file serving endpoints for generated mock files
852    .merge(file_server::file_serving_router());
853
854    // Clone state for routes_router since we'll use it for middleware too
855    let state_for_routes = state.clone();
856
857    // Create a router with state for the routes and coverage endpoints
858    let routes_router = Router::new()
859        .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
860        .route("/__mockforge/coverage", axum::routing::get(coverage::get_coverage_handler))
861        .with_state(state_for_routes);
862
863    // Merge the routes router with the main app
864    app = app.merge(routes_router);
865
866    // Add API docs page (Scalar-powered interactive explorer)
867    app = app.route("/__mockforge/docs", axum::routing::get(get_docs_handler));
868
869    // Add static coverage UI
870    // Determine the path to the coverage.html file
871    let coverage_html_path = std::env::var("MOCKFORGE_COVERAGE_UI_PATH")
872        .unwrap_or_else(|_| "crates/mockforge-http/static/coverage.html".to_string());
873
874    // Check if the file exists before serving it
875    if Path::new(&coverage_html_path).exists() {
876        app = app.nest_service(
877            "/__mockforge/coverage.html",
878            tower_http::services::ServeFile::new(&coverage_html_path),
879        );
880        debug!("Serving coverage UI from: {}", coverage_html_path);
881    } else {
882        debug!(
883            "Coverage UI file not found at: {}. Skipping static file serving.",
884            coverage_html_path
885        );
886    }
887
888    // Add management API endpoints
889    // Load spec for ManagementState so /__mockforge/api/spec can serve it
890    let mgmt_spec = if let Some(ref sp) = spec_path_for_mgmt {
891        match OpenApiSpec::from_file(sp).await {
892            Ok(s) => Some(Arc::new(s)),
893            Err(e) => {
894                debug!("Failed to load OpenAPI spec for management API: {}", e);
895                None
896            }
897        }
898    } else {
899        None
900    };
901    let mgmt_port = std::env::var("PORT")
902        .or_else(|_| std::env::var("MOCKFORGE_HTTP_PORT"))
903        .ok()
904        .and_then(|p| p.parse().ok())
905        .unwrap_or(3000);
906    let management_state = ManagementState::new(mgmt_spec, spec_path_for_mgmt, mgmt_port);
907
908    // Create WebSocket state and connect it to management state
909    use std::sync::Arc;
910    let ws_state = WsManagementState::new();
911    let ws_broadcast = Arc::new(ws_state.tx.clone());
912    let management_state = management_state.with_ws_broadcast(ws_broadcast);
913
914    // Note: ProxyConfig not available in this build function path
915    // Migration endpoints will work once ProxyConfig is passed to build_router_with_chains_and_multi_tenant
916
917    #[cfg(feature = "smtp")]
918    let management_state = {
919        if let Some(smtp_reg) = smtp_registry {
920            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
921                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
922                Err(e) => {
923                    error!(
924                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
925                        e.type_id()
926                    );
927                    management_state
928                }
929            }
930        } else {
931            management_state
932        }
933    };
934    #[cfg(not(feature = "smtp"))]
935    let management_state = management_state;
936    #[cfg(not(feature = "smtp"))]
937    let _ = smtp_registry;
938    let management_state_for_fallback = management_state.clone();
939    app = app.nest("/__mockforge/api", management_router(management_state));
940    // Serve any request that the rest of the router doesn't handle as a dynamic
941    // mock lookup. Lets the Node/Rust SDK register stubs via the management
942    // API and have requests actually reach them. Falls through to 404 when
943    // no mock matches.
944    app = app.fallback_service(
945        axum::routing::any(management::dynamic_mock_fallback)
946            .with_state(management_state_for_fallback),
947    );
948
949    // Add verification API endpoint
950    app = app.merge(verification_router());
951
952    // Add OIDC well-known endpoints
953    use crate::auth::oidc::oidc_router;
954    app = app.merge(oidc_router());
955
956    // Add access review API if enabled
957    {
958        use mockforge_core::security::get_global_access_review_service;
959        if let Some(service) = get_global_access_review_service().await {
960            use crate::handlers::access_review::{access_review_router, AccessReviewState};
961            let review_state = AccessReviewState { service };
962            app = app.nest("/api/v1/security/access-reviews", access_review_router(review_state));
963            debug!("Access review API mounted at /api/v1/security/access-reviews");
964        }
965    }
966
967    // Add privileged access API if enabled
968    {
969        use mockforge_core::security::get_global_privileged_access_manager;
970        if let Some(manager) = get_global_privileged_access_manager().await {
971            use crate::handlers::privileged_access::{
972                privileged_access_router, PrivilegedAccessState,
973            };
974            let privileged_state = PrivilegedAccessState { manager };
975            app = app.nest(
976                "/api/v1/security/privileged-access",
977                privileged_access_router(privileged_state),
978            );
979            debug!("Privileged access API mounted at /api/v1/security/privileged-access");
980        }
981    }
982
983    // Add change management API if enabled
984    {
985        use mockforge_core::security::get_global_change_management_engine;
986        if let Some(engine) = get_global_change_management_engine().await {
987            use crate::handlers::change_management::{
988                change_management_router, ChangeManagementState,
989            };
990            let change_state = ChangeManagementState { engine };
991            app = app.nest("/api/v1/change-management", change_management_router(change_state));
992            debug!("Change management API mounted at /api/v1/change-management");
993        }
994    }
995
996    // Add risk assessment API if enabled
997    {
998        use mockforge_core::security::get_global_risk_assessment_engine;
999        if let Some(engine) = get_global_risk_assessment_engine().await {
1000            use crate::handlers::risk_assessment::{risk_assessment_router, RiskAssessmentState};
1001            let risk_state = RiskAssessmentState { engine };
1002            app = app.nest("/api/v1/security", risk_assessment_router(risk_state));
1003            debug!("Risk assessment API mounted at /api/v1/security/risks");
1004        }
1005    }
1006
1007    // Add token lifecycle API
1008    {
1009        use crate::auth::token_lifecycle::TokenLifecycleManager;
1010        use crate::handlers::token_lifecycle::{token_lifecycle_router, TokenLifecycleState};
1011        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1012        let lifecycle_state = TokenLifecycleState {
1013            manager: lifecycle_manager,
1014        };
1015        app = app.nest("/api/v1/auth", token_lifecycle_router(lifecycle_state));
1016        debug!("Token lifecycle API mounted at /api/v1/auth");
1017    }
1018
1019    // Add OAuth2 server endpoints
1020    {
1021        use crate::auth::oidc::load_oidc_state;
1022        use crate::auth::token_lifecycle::TokenLifecycleManager;
1023        use crate::handlers::oauth2_server::{oauth2_server_router, OAuth2ServerState};
1024        // Load OIDC state from configuration (environment variables or config file)
1025        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
1026        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1027        let oauth2_state = OAuth2ServerState {
1028            oidc_state,
1029            lifecycle_manager,
1030            auth_codes: Arc::new(RwLock::new(HashMap::new())),
1031            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
1032        };
1033        app = app.merge(oauth2_server_router(oauth2_state));
1034        debug!("OAuth2 server endpoints mounted at /oauth2/authorize and /oauth2/token");
1035    }
1036
1037    // Add consent screen endpoints
1038    {
1039        use crate::auth::oidc::load_oidc_state;
1040        use crate::auth::risk_engine::RiskEngine;
1041        use crate::auth::token_lifecycle::TokenLifecycleManager;
1042        use crate::handlers::consent::{consent_router, ConsentState};
1043        use crate::handlers::oauth2_server::OAuth2ServerState;
1044        // Load OIDC state from configuration (environment variables or config file)
1045        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
1046        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1047        let oauth2_state = OAuth2ServerState {
1048            oidc_state: oidc_state.clone(),
1049            lifecycle_manager: lifecycle_manager.clone(),
1050            auth_codes: Arc::new(RwLock::new(HashMap::new())),
1051            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
1052        };
1053        let risk_engine = Arc::new(RiskEngine::default());
1054        let consent_state = ConsentState {
1055            oauth2_state,
1056            risk_engine,
1057        };
1058        app = app.merge(consent_router(consent_state));
1059        debug!("Consent screen endpoints mounted at /consent");
1060    }
1061
1062    // Add risk simulation API
1063    {
1064        use crate::auth::risk_engine::RiskEngine;
1065        use crate::handlers::risk_simulation::{risk_simulation_router, RiskSimulationState};
1066        let risk_engine = Arc::new(RiskEngine::default());
1067        let risk_state = RiskSimulationState { risk_engine };
1068        app = app.nest("/api/v1/auth", risk_simulation_router(risk_state));
1069        debug!("Risk simulation API mounted at /api/v1/auth/risk");
1070    }
1071
1072    // Add management WebSocket endpoint
1073    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
1074
1075    // Add request logging middleware to capture all requests
1076    app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
1077
1078    // Add security middleware for security event tracking (after logging, before contract diff)
1079    app = app.layer(axum::middleware::from_fn(middleware::security_middleware));
1080
1081    // Add contract diff middleware for automatic request capture
1082    // This captures requests for contract diff analysis (after logging)
1083    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
1084
1085    // Add rate limiting middleware (before logging to rate limit early)
1086    app = app.layer(from_fn_with_state(state.clone(), middleware::rate_limit_middleware));
1087
1088    // Add production headers middleware if configured
1089    if state.production_headers.is_some() {
1090        app =
1091            app.layer(from_fn_with_state(state.clone(), middleware::production_headers_middleware));
1092    }
1093
1094    // Optionally advertise `Connection: keep-alive` + `Keep-Alive: timeout=N,
1095    // max=M` on every response. Useful when MockForge sits behind a reverse
1096    // proxy (F5/Avi/HAProxy/nginx) that pools upstream sockets based on those
1097    // headers — see issue #79 (proxy reset after FIN on HTTP/1.0 upstream).
1098    // Off by default; opt in via `MOCKFORGE_HTTP_KEEPALIVE_HINT=1`.
1099    if middleware::is_keepalive_hint_enabled() {
1100        info!(
1101            "MOCKFORGE_HTTP_KEEPALIVE_HINT enabled — emitting Connection: keep-alive + Keep-Alive headers on all responses (Issue #79 workaround)"
1102        );
1103        app = app.layer(axum::middleware::from_fn(middleware::keepalive_hint_middleware));
1104    }
1105
1106    // Issue #79 (round 5): per-request log line with HTTP version + Connection
1107    // header MockForge actually sees, so users debugging proxy ↔ MockForge
1108    // negotiation can confirm whether their proxy is speaking HTTP/1.1 with
1109    // keep-alive or HTTP/1.0 (which forces hyper to FIN after each response).
1110    if middleware::is_conn_log_enabled() {
1111        info!(
1112            "MOCKFORGE_HTTP_LOG_CONN enabled — logging HTTP version + Connection headers per request (Issue #79 diagnostic)"
1113        );
1114        app = app.layer(axum::middleware::from_fn(middleware::conn_diag_middleware));
1115    }
1116
1117    // Add authentication middleware if OAuth is configured via deceptive deploy
1118    if let Some(auth_config) = deceptive_deploy_auth_config {
1119        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1120        use std::collections::HashMap;
1121        use std::sync::Arc;
1122        use tokio::sync::RwLock;
1123
1124        // Create OAuth2 client if configured
1125        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
1126            match create_oauth2_client(oauth2_config) {
1127                Ok(client) => Some(client),
1128                Err(e) => {
1129                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
1130                    None
1131                }
1132            }
1133        } else {
1134            None
1135        };
1136
1137        // Create auth state
1138        let auth_state = AuthState {
1139            config: auth_config,
1140            spec: None, // OpenAPI spec not available in this context
1141            oauth2_client,
1142            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1143        };
1144
1145        // Apply auth middleware
1146        app = app.layer(from_fn_with_state(auth_state, auth_middleware));
1147        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
1148    }
1149
1150    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
1151    app = apply_cors_middleware(app, final_cors_config);
1152
1153    // Add workspace routing middleware if multi-tenant is enabled
1154    if let Some(mt_config) = multi_tenant_config {
1155        if mt_config.enabled {
1156            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
1157            use std::sync::Arc;
1158
1159            info!(
1160                "Multi-tenant mode enabled with {} routing strategy",
1161                match mt_config.routing_strategy {
1162                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Path => "path-based",
1163                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Port => "port-based",
1164                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Both => "hybrid",
1165                }
1166            );
1167
1168            // Create the multi-tenant workspace registry
1169            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
1170
1171            // Register the default workspace before wrapping in Arc
1172            let default_workspace =
1173                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
1174            if let Err(e) =
1175                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
1176            {
1177                warn!("Failed to register default workspace: {}", e);
1178            } else {
1179                info!("Registered default workspace: '{}'", mt_config.default_workspace);
1180            }
1181
1182            // Auto-discover and register workspaces if configured
1183            if mt_config.auto_discover {
1184                if let Some(config_dir) = &mt_config.config_directory {
1185                    let config_path = Path::new(config_dir);
1186                    if config_path.exists() && config_path.is_dir() {
1187                        match fs::read_dir(config_path).await {
1188                            Ok(mut entries) => {
1189                                while let Ok(Some(entry)) = entries.next_entry().await {
1190                                    let path = entry.path();
1191                                    if path.extension() == Some(OsStr::new("yaml")) {
1192                                        match fs::read_to_string(&path).await {
1193                                            Ok(content) => {
1194                                                match serde_yaml::from_str::<
1195                                                    mockforge_core::Workspace,
1196                                                >(
1197                                                    &content
1198                                                ) {
1199                                                    Ok(workspace) => {
1200                                                        if let Err(e) = registry.register_workspace(
1201                                                            workspace.id.clone(),
1202                                                            workspace,
1203                                                        ) {
1204                                                            warn!("Failed to register auto-discovered workspace from {:?}: {}", path, e);
1205                                                        } else {
1206                                                            info!("Auto-registered workspace from {:?}", path);
1207                                                        }
1208                                                    }
1209                                                    Err(e) => {
1210                                                        warn!("Failed to parse workspace from {:?}: {}", path, e);
1211                                                    }
1212                                                }
1213                                            }
1214                                            Err(e) => {
1215                                                warn!(
1216                                                    "Failed to read workspace file {:?}: {}",
1217                                                    path, e
1218                                                );
1219                                            }
1220                                        }
1221                                    }
1222                                }
1223                            }
1224                            Err(e) => {
1225                                warn!("Failed to read config directory {:?}: {}", config_path, e);
1226                            }
1227                        }
1228                    } else {
1229                        warn!(
1230                            "Config directory {:?} does not exist or is not a directory",
1231                            config_path
1232                        );
1233                    }
1234                }
1235            }
1236
1237            // Wrap registry in Arc for shared access
1238            let registry = Arc::new(registry);
1239
1240            // Create workspace router and wrap the app with workspace middleware
1241            let _workspace_router = WorkspaceRouter::new(registry);
1242
1243            // Note: The actual middleware integration would need to be implemented
1244            // in the WorkspaceRouter to work with Axum's middleware system
1245            info!("Workspace routing middleware initialized for HTTP server");
1246        }
1247    }
1248
1249    let total_startup_duration = startup_start.elapsed();
1250    info!("HTTP router startup completed (total time: {:?})", total_startup_duration);
1251
1252    app
1253}
1254
1255/// Build the base HTTP router with authentication and latency support
1256pub async fn build_router_with_auth_and_latency(
1257    spec_path: Option<String>,
1258    _options: Option<()>,
1259    auth_config: Option<mockforge_core::config::AuthConfig>,
1260    latency_injector: Option<LatencyInjector>,
1261) -> Router {
1262    // Build auth router first, then layer latency on top
1263    let mut app = build_router_with_auth(spec_path.clone(), None, auth_config).await;
1264
1265    // Apply latency injection middleware if configured
1266    if let Some(injector) = latency_injector {
1267        let injector = Arc::new(injector);
1268        app = app.layer(axum::middleware::from_fn(move |req, next: axum::middleware::Next| {
1269            let injector = injector.clone();
1270            async move {
1271                let _ = injector.inject_latency(&[]).await;
1272                next.run(req).await
1273            }
1274        }));
1275    }
1276
1277    app
1278}
1279
1280/// Build the base HTTP router with latency injection support
1281pub async fn build_router_with_latency(
1282    spec_path: Option<String>,
1283    options: Option<ValidationOptions>,
1284    latency_injector: Option<LatencyInjector>,
1285) -> Router {
1286    if let Some(spec) = &spec_path {
1287        match OpenApiSpec::from_file(spec).await {
1288            Ok(openapi) => {
1289                let registry = if let Some(opts) = options {
1290                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1291                } else {
1292                    OpenApiRouteRegistry::new_with_env(openapi)
1293                };
1294
1295                if let Some(injector) = latency_injector {
1296                    return registry.build_router_with_latency(injector);
1297                } else {
1298                    return registry.build_router();
1299                }
1300            }
1301            Err(e) => {
1302                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec, e);
1303            }
1304        }
1305    }
1306
1307    build_router(None, None, None).await
1308}
1309
1310/// Build the base HTTP router with authentication support
1311pub async fn build_router_with_auth(
1312    spec_path: Option<String>,
1313    options: Option<ValidationOptions>,
1314    auth_config: Option<mockforge_core::config::AuthConfig>,
1315) -> Router {
1316    use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1317    use std::sync::Arc;
1318
1319    // If richer faker is available, register provider once (idempotent)
1320    #[cfg(feature = "data-faker")]
1321    {
1322        register_core_faker_provider();
1323    }
1324
1325    // Set up authentication state
1326    let spec = if let Some(spec_path) = &spec_path {
1327        match OpenApiSpec::from_file(&spec_path).await {
1328            Ok(spec) => Some(Arc::new(spec)),
1329            Err(e) => {
1330                warn!("Failed to load OpenAPI spec for auth: {}", e);
1331                None
1332            }
1333        }
1334    } else {
1335        None
1336    };
1337
1338    // Create OAuth2 client if configured
1339    let oauth2_client = if let Some(auth_config) = &auth_config {
1340        if let Some(oauth2_config) = &auth_config.oauth2 {
1341            match create_oauth2_client(oauth2_config) {
1342                Ok(client) => Some(client),
1343                Err(e) => {
1344                    warn!("Failed to create OAuth2 client: {}", e);
1345                    None
1346                }
1347            }
1348        } else {
1349            None
1350        }
1351    } else {
1352        None
1353    };
1354
1355    let auth_state = AuthState {
1356        config: auth_config.unwrap_or_default(),
1357        spec,
1358        oauth2_client,
1359        introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1360    };
1361
1362    // Set up the basic router with auth state
1363    let mut app = Router::new().with_state(auth_state.clone());
1364
1365    // If an OpenAPI spec is provided, integrate it
1366    if let Some(spec_path) = spec_path {
1367        match OpenApiSpec::from_file(&spec_path).await {
1368            Ok(openapi) => {
1369                info!("Loaded OpenAPI spec from {}", spec_path);
1370                let registry = if let Some(opts) = options {
1371                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1372                } else {
1373                    OpenApiRouteRegistry::new_with_env(openapi)
1374                };
1375
1376                app = registry.build_router();
1377            }
1378            Err(e) => {
1379                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
1380            }
1381        }
1382    }
1383
1384    // Add basic health check endpoint
1385    app = app.route(
1386        "/health",
1387        axum::routing::get(|| async {
1388            use mockforge_core::server_utils::health::HealthStatus;
1389            {
1390                // HealthStatus should always serialize, but handle errors gracefully
1391                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
1392                    Ok(value) => Json(value),
1393                    Err(e) => {
1394                        // Log error but return a simple healthy response
1395                        tracing::error!("Failed to serialize health status: {}", e);
1396                        Json(serde_json::json!({
1397                            "status": "healthy",
1398                            "service": "mockforge-http",
1399                            "uptime_seconds": 0
1400                        }))
1401                    }
1402                }
1403            }
1404        }),
1405    )
1406    // Add SSE endpoints
1407    .merge(sse::sse_router())
1408    // Add file serving endpoints for generated mock files
1409    .merge(file_server::file_serving_router())
1410    // Add authentication middleware (before logging)
1411    .layer(from_fn_with_state(auth_state.clone(), auth_middleware))
1412    // Add request logging middleware
1413    .layer(axum::middleware::from_fn(request_logging::log_http_requests));
1414
1415    app
1416}
1417
1418/// Serve a provided router on the given port.
1419pub async fn serve_router(
1420    port: u16,
1421    app: Router,
1422) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1423    serve_router_with_tls(port, app, None).await
1424}
1425
1426/// Serve a provided router on the given port with optional TLS support.
1427pub async fn serve_router_with_tls(
1428    port: u16,
1429    app: Router,
1430    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1431) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1432    serve_router_with_tls_notify(port, app, tls_config, None).await
1433}
1434
1435/// Serve a provided router on the given port with optional TLS support, notifying
1436/// the caller via `bound_port_tx` with the actual OS-assigned port once the
1437/// listener has bound. This matters when the requested `port` is `0` (ephemeral),
1438/// because callers such as the `@mockforge-dev/sdk` need to learn the real port
1439/// to connect to it. On the TLS path, `bound_port_tx` is currently ignored and
1440/// the requested `port` is reported verbatim — callers that need dynamic TLS
1441/// ports should bind on a known port.
1442pub async fn serve_router_with_tls_notify(
1443    port: u16,
1444    app: Router,
1445    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1446    bound_port_tx: Option<tokio::sync::oneshot::Sender<u16>>,
1447) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1448    serve_router_with_tls_notify_chaos(port, app, tls_config, bound_port_tx, None).await
1449}
1450
1451/// Serve a router with optional TLS *and* an optional shared chaos config.
1452///
1453/// When `chaos_config` is `Some`, the TCP listener is wrapped with
1454/// [`mockforge_chaos::ChaosTcpListener`], which can inject TCP-level
1455/// connection faults (RST or unclean FIN) before axum/hyper see the socket.
1456/// This is the only way to surface real transport-level failures rather than
1457/// HTTP 503 responses on healthy connections. TLS path does not yet support
1458/// chaos listener wrapping (axum-server uses its own accept loop).
1459pub async fn serve_router_with_tls_notify_chaos(
1460    port: u16,
1461    app: Router,
1462    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1463    bound_port_tx: Option<tokio::sync::oneshot::Sender<u16>>,
1464    chaos_config: Option<Arc<RwLock<mockforge_chaos::ChaosConfig>>>,
1465) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1466    use std::net::SocketAddr;
1467
1468    let addr = mockforge_core::wildcard_socket_addr(port);
1469
1470    if let Some(ref tls) = tls_config {
1471        if tls.enabled {
1472            info!("HTTPS listening on {}", addr);
1473            if let Some(tx) = bound_port_tx {
1474                let _ = tx.send(port);
1475            }
1476            return serve_with_tls(addr, app, tls).await;
1477        }
1478    }
1479
1480    let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
1481        format!(
1482            "Failed to bind HTTP server to port {}: {}\n\
1483             Hint: The port may already be in use. Try using a different port with --http-port or check if another process is using this port with: lsof -i :{} or netstat -tulpn | grep {}",
1484            port, e, port, port
1485        )
1486    })?;
1487
1488    let actual_port = listener.local_addr().map(|a| a.port()).unwrap_or(port);
1489    info!("HTTP listening on {}", listener.local_addr().unwrap_or(addr));
1490    if let Some(tx) = bound_port_tx {
1491        let _ = tx.send(actual_port);
1492    }
1493
1494    // Wrap the Router with OData URI rewrite.
1495    // Router::layer() only applies to matched routes, so we must wrap at the service level
1496    // to rewrite URIs BEFORE route matching occurs.
1497    let odata_app = tower::ServiceBuilder::new()
1498        .layer(mockforge_core::odata_rewrite::ODataRewriteLayer)
1499        .service(app);
1500    if let Some(cfg) = chaos_config {
1501        info!("HTTP listener wrapped with chaos TCP listener (RST/FIN injection enabled)");
1502        let chaos_listener = mockforge_chaos::ChaosTcpListener::new(listener, cfg);
1503        // Layer that copies ConnectInfo<ChaosClientAddr> → ConnectInfo<SocketAddr>
1504        // so existing handlers using `ConnectInfo<SocketAddr>` keep working.
1505        let app_with_addr_compat = tower::ServiceBuilder::new()
1506            .layer(axum::middleware::from_fn(copy_chaos_addr_to_socketaddr))
1507            .service(odata_app);
1508        let make_svc = axum::ServiceExt::<Request<Body>>::into_make_service_with_connect_info::<
1509            mockforge_chaos::ChaosClientAddr,
1510        >(app_with_addr_compat);
1511        // Bump the accept counter on each connection that gets through chaos.
1512        let counted = counting_listener::CountingMakeService::new(make_svc);
1513        axum::serve(chaos_listener, counted).await?;
1514    } else {
1515        let make_svc = axum::ServiceExt::<Request<Body>>::into_make_service_with_connect_info::<
1516            SocketAddr,
1517        >(odata_app);
1518        // Bump the accept counter once per accepted connection so the
1519        // dashboard sampler can derive CPS.
1520        let counted = counting_listener::CountingMakeService::new(make_svc);
1521        axum::serve(listener, counted).await?;
1522    }
1523    Ok(())
1524}
1525
1526/// Mirror `ConnectInfo<ChaosClientAddr>` (set by the chaos listener) onto
1527/// `ConnectInfo<SocketAddr>` so handlers extracting `ConnectInfo<SocketAddr>`
1528/// keep working when chaos TCP wrapping is enabled.
1529async fn copy_chaos_addr_to_socketaddr(
1530    mut req: Request<Body>,
1531    next: axum::middleware::Next,
1532) -> axum::response::Response {
1533    use axum::extract::ConnectInfo;
1534    if let Some(ConnectInfo(chaos_addr)) =
1535        req.extensions().get::<ConnectInfo<mockforge_chaos::ChaosClientAddr>>().copied()
1536    {
1537        let sock: std::net::SocketAddr = *chaos_addr;
1538        req.extensions_mut().insert(ConnectInfo(sock));
1539    }
1540    next.run(req).await
1541}
1542
1543/// Serve router with TLS/HTTPS support using axum-server
1544///
1545/// This function implements native TLS serving using axum-server and tokio-rustls.
1546/// It supports standard TLS and mutual TLS (mTLS) based on the configuration.
1547async fn serve_with_tls(
1548    addr: std::net::SocketAddr,
1549    app: Router,
1550    tls_config: &mockforge_core::config::HttpTlsConfig,
1551) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1552    use axum_server::tls_rustls::RustlsConfig;
1553    use std::net::SocketAddr;
1554
1555    // Initialize the rustls crypto provider (must be called before TLS operations)
1556    tls::init_crypto_provider();
1557
1558    info!("Loading TLS configuration for HTTPS server");
1559
1560    // Load TLS server configuration (supports mTLS)
1561    let server_config = tls::load_tls_server_config(tls_config)?;
1562
1563    // Create RustlsConfig from the ServerConfig
1564    // Note: axum-server's RustlsConfig can be created from a ServerConfig
1565    let rustls_config = RustlsConfig::from_config(server_config);
1566
1567    info!("Starting HTTPS server on {}", addr);
1568
1569    // Wrap the Router with OData URI rewrite (same as the non-TLS path).
1570    // Router::layer() only applies to matched routes, so we must wrap at the service level
1571    // to rewrite URIs BEFORE route matching occurs.
1572    let odata_app = tower::ServiceBuilder::new()
1573        .layer(mockforge_core::odata_rewrite::ODataRewriteLayer)
1574        .service(app);
1575    let make_svc = axum::ServiceExt::<Request<Body>>::into_make_service_with_connect_info::<
1576        SocketAddr,
1577    >(odata_app);
1578    // Bump the accept counter once per successful TLS handshake / connection
1579    // so the dashboard sampler can derive CPS for HTTPS-only setups.
1580    let counted = counting_listener::CountingMakeService::new(make_svc);
1581
1582    // Serve with TLS using axum-server
1583    axum_server::bind_rustls(addr, rustls_config)
1584        .serve(counted)
1585        .await
1586        .map_err(|e| format!("HTTPS server error: {}", e).into())
1587}
1588
1589/// Backwards-compatible start that builds + serves the base router.
1590pub async fn start(
1591    port: u16,
1592    spec_path: Option<String>,
1593    options: Option<ValidationOptions>,
1594) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1595    start_with_latency(port, spec_path, options, None).await
1596}
1597
1598/// Start HTTP server with authentication and latency support
1599pub async fn start_with_auth_and_latency(
1600    port: u16,
1601    spec_path: Option<String>,
1602    options: Option<ValidationOptions>,
1603    auth_config: Option<mockforge_core::config::AuthConfig>,
1604    latency_profile: Option<LatencyProfile>,
1605) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1606    start_with_auth_and_injectors(port, spec_path, options, auth_config, latency_profile, None)
1607        .await
1608}
1609
1610/// Start HTTP server with authentication and injectors support
1611pub async fn start_with_auth_and_injectors(
1612    port: u16,
1613    spec_path: Option<String>,
1614    options: Option<ValidationOptions>,
1615    auth_config: Option<mockforge_core::config::AuthConfig>,
1616    _latency_profile: Option<LatencyProfile>,
1617    _failure_injector: Option<FailureInjector>,
1618) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1619    // For now, ignore latency and failure injectors and just use auth
1620    let app = build_router_with_auth(spec_path, options, auth_config).await;
1621    serve_router(port, app).await
1622}
1623
1624/// Start HTTP server with latency injection support
1625pub async fn start_with_latency(
1626    port: u16,
1627    spec_path: Option<String>,
1628    options: Option<ValidationOptions>,
1629    latency_profile: Option<LatencyProfile>,
1630) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1631    let latency_injector =
1632        latency_profile.map(|profile| LatencyInjector::new(profile, Default::default()));
1633
1634    let app = build_router_with_latency(spec_path, options, latency_injector).await;
1635    serve_router(port, app).await
1636}
1637
1638/// Build the base HTTP router with chaining support
1639pub async fn build_router_with_chains(
1640    spec_path: Option<String>,
1641    options: Option<ValidationOptions>,
1642    circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1643) -> Router {
1644    build_router_with_chains_and_multi_tenant(
1645        spec_path,
1646        options,
1647        circling_config,
1648        None,
1649        None,
1650        None,
1651        None,
1652        None,
1653        None,
1654        None,
1655        false,
1656        None, // health_manager
1657        None, // mockai
1658        None, // deceptive_deploy_config
1659        None, // proxy_config
1660    )
1661    .await
1662}
1663
1664// Template expansion is now handled by mockforge_core::template_expansion
1665// which is explicitly isolated from the templating module to avoid Send issues
1666
1667/// Helper function to apply route chaos injection (fault injection and latency)
1668/// This is extracted to avoid capturing RouteChaosInjector in the closure, which causes Send issues
1669/// Uses mockforge-route-chaos crate which is isolated from mockforge-core to avoid Send issues
1670/// Uses the RouteChaosInjectorTrait from mockforge-core to avoid circular dependency
1671async fn apply_route_chaos(
1672    injector: Option<&dyn mockforge_core::priority_handler::RouteChaosInjectorTrait>,
1673    method: &http::Method,
1674    uri: &http::Uri,
1675) -> Option<axum::response::Response> {
1676    use axum::http::StatusCode;
1677    use axum::response::IntoResponse;
1678
1679    if let Some(injector) = injector {
1680        // Check for fault injection first
1681        if let Some(fault_response) = injector.get_fault_response(method, uri) {
1682            // Return fault response
1683            let mut response = Json(serde_json::json!({
1684                "error": fault_response.error_message,
1685                "fault_type": fault_response.fault_type,
1686            }))
1687            .into_response();
1688            *response.status_mut() = StatusCode::from_u16(fault_response.status_code)
1689                .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
1690            return Some(response);
1691        }
1692
1693        // Inject latency if configured (this is async and may delay the request)
1694        if let Err(e) = injector.inject_latency(method, uri).await {
1695            tracing::warn!("Failed to inject latency: {}", e);
1696        }
1697    }
1698
1699    None // No fault response, processing should continue
1700}
1701
1702/// Build the base HTTP router with chaining and multi-tenant support
1703#[allow(clippy::too_many_arguments)]
1704#[allow(deprecated)] // uses core engines (DriftBudgetEngine, ThreatAnalyzer, Forecaster, ProtocolContractRegistry, MockAI, MultiTenantWorkspaceRegistry, etc.) that stay in core
1705pub async fn build_router_with_chains_and_multi_tenant(
1706    spec_path: Option<String>,
1707    options: Option<ValidationOptions>,
1708    _circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1709    multi_tenant_config: Option<mockforge_foundation::multi_tenant_types::MultiTenantConfig>,
1710    route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
1711    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
1712    _ai_generator: Option<Arc<dyn mockforge_openapi::response::AiGenerator + Send + Sync>>,
1713    smtp_registry: Option<Arc<dyn std::any::Any + Send + Sync>>,
1714    mqtt_broker: Option<Arc<dyn std::any::Any + Send + Sync>>,
1715    traffic_shaper: Option<mockforge_core::traffic_shaping::TrafficShaper>,
1716    traffic_shaping_enabled: bool,
1717    health_manager: Option<Arc<HealthManager>>,
1718    mockai: Option<Arc<RwLock<mockforge_core::intelligent_behavior::MockAI>>>,
1719    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
1720    proxy_config: Option<mockforge_proxy::config::ProxyConfig>,
1721) -> Router {
1722    use crate::latency_profiles::LatencyProfiles;
1723    use crate::op_middleware::Shared;
1724    use mockforge_core::Overrides;
1725
1726    // Extract template expansion setting before options is moved (used in OpenAPI routes and custom routes)
1727    let template_expand =
1728        options.as_ref().map(|o| o.response_template_expand).unwrap_or_else(|| {
1729            std::env::var("MOCKFORGE_RESPONSE_TEMPLATE_EXPAND")
1730                .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1731                .unwrap_or(false)
1732        });
1733
1734    let _shared = Shared {
1735        profiles: LatencyProfiles::default(),
1736        overrides: Overrides::default(),
1737        failure_injector: None,
1738        traffic_shaper,
1739        overrides_enabled: false,
1740        traffic_shaping_enabled,
1741    };
1742
1743    // Start with basic router
1744    let mut app = Router::new();
1745    let mut include_default_health = true;
1746    let mut captured_routes: Vec<RouteInfo> = Vec::new();
1747
1748    // If an OpenAPI spec is provided, integrate it
1749    if let Some(ref spec) = spec_path {
1750        match OpenApiSpec::from_file(&spec).await {
1751            Ok(openapi) => {
1752                info!("Loaded OpenAPI spec from {}", spec);
1753
1754                // Try to load persona from config if available
1755                let persona = load_persona_from_config().await;
1756
1757                let mut registry = if let Some(opts) = options {
1758                    tracing::debug!("Using custom validation options");
1759                    if let Some(ref persona) = persona {
1760                        tracing::info!("Using persona '{}' for route generation", persona.name);
1761                    }
1762                    OpenApiRouteRegistry::new_with_options_and_persona(openapi, opts, persona)
1763                } else {
1764                    tracing::debug!("Using environment-based options");
1765                    if let Some(ref persona) = persona {
1766                        tracing::info!("Using persona '{}' for route generation", persona.name);
1767                    }
1768                    OpenApiRouteRegistry::new_with_env_and_persona(openapi, persona)
1769                };
1770
1771                // Load custom fixtures if enabled
1772                let fixtures_dir = std::env::var("MOCKFORGE_FIXTURES_DIR")
1773                    .unwrap_or_else(|_| "/app/fixtures".to_string());
1774                let custom_fixtures_enabled = std::env::var("MOCKFORGE_CUSTOM_FIXTURES_ENABLED")
1775                    .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1776                    .unwrap_or(true); // Enabled by default
1777
1778                if custom_fixtures_enabled {
1779                    use mockforge_openapi::CustomFixtureLoader;
1780                    use std::path::PathBuf;
1781                    use std::sync::Arc;
1782
1783                    let fixtures_path = PathBuf::from(&fixtures_dir);
1784                    let mut custom_loader = CustomFixtureLoader::new(fixtures_path, true);
1785
1786                    if let Err(e) = custom_loader.load_fixtures().await {
1787                        tracing::warn!("Failed to load custom fixtures: {}", e);
1788                    } else {
1789                        tracing::info!("Custom fixtures loaded from {}", fixtures_dir);
1790                        registry = registry.with_custom_fixture_loader(Arc::new(custom_loader));
1791                    }
1792                }
1793
1794                if registry
1795                    .routes()
1796                    .iter()
1797                    .any(|route| route.method == "GET" && route.path == "/health")
1798                {
1799                    include_default_health = false;
1800                }
1801                // Capture route info for the /__mockforge/routes endpoint
1802                captured_routes = registry
1803                    .routes()
1804                    .iter()
1805                    .map(|r| RouteInfo {
1806                        method: r.method.clone(),
1807                        path: r.path.clone(),
1808                        operation_id: r.operation.operation_id.clone(),
1809                        summary: r.operation.summary.clone(),
1810                        description: r.operation.description.clone(),
1811                        parameters: r.parameters.clone(),
1812                    })
1813                    .collect();
1814
1815                // Store routes in the global route store so the admin server
1816                // can serve them without proxying back to the HTTP server.
1817                {
1818                    let global_routes: Vec<mockforge_core::request_logger::GlobalRouteInfo> =
1819                        captured_routes
1820                            .iter()
1821                            .map(|r| mockforge_core::request_logger::GlobalRouteInfo {
1822                                method: r.method.clone(),
1823                                path: r.path.clone(),
1824                                operation_id: r.operation_id.clone(),
1825                                summary: r.summary.clone(),
1826                                description: r.description.clone(),
1827                                parameters: r.parameters.clone(),
1828                            })
1829                            .collect();
1830                    mockforge_core::request_logger::set_global_routes(global_routes);
1831                    tracing::info!("Stored {} routes in global route store", captured_routes.len());
1832                }
1833
1834                // Use MockAI if available, otherwise use standard router
1835                let spec_router = if let Some(ref mockai_instance) = mockai {
1836                    tracing::debug!("Building router with MockAI support");
1837                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
1838                } else {
1839                    registry.build_router()
1840                };
1841                app = app.merge(spec_router);
1842            }
1843            Err(e) => {
1844                warn!("Failed to load OpenAPI spec from {:?}: {}. Starting without OpenAPI integration.", spec_path, e);
1845            }
1846        }
1847    }
1848
1849    // Register custom routes from config with advanced routing features
1850    // Create RouteChaosInjector for advanced fault injection and latency
1851    // Store as trait object to avoid circular dependency (RouteChaosInjectorTrait is in mockforge-core)
1852    let route_chaos_injector: Option<
1853        std::sync::Arc<dyn mockforge_core::priority_handler::RouteChaosInjectorTrait>,
1854    > = if let Some(ref route_configs) = route_configs {
1855        if !route_configs.is_empty() {
1856            // Convert to the type expected by RouteChaosInjector
1857            // Note: Both use the same mockforge-core, but we need to ensure type compatibility
1858            let route_configs_converted: Vec<mockforge_core::config::RouteConfig> =
1859                route_configs.to_vec();
1860            match mockforge_route_chaos::RouteChaosInjector::new(route_configs_converted) {
1861                Ok(injector) => {
1862                    info!(
1863                        "Initialized advanced routing features for {} route(s)",
1864                        route_configs.len()
1865                    );
1866                    // RouteChaosInjector implements RouteChaosInjectorTrait, so we can cast it
1867                    // The trait is implemented in mockforge-route-chaos/src/lib.rs
1868                    Some(std::sync::Arc::new(injector)
1869                        as std::sync::Arc<
1870                            dyn mockforge_core::priority_handler::RouteChaosInjectorTrait,
1871                        >)
1872                }
1873                Err(e) => {
1874                    warn!(
1875                        "Failed to initialize advanced routing features: {}. Using basic routing.",
1876                        e
1877                    );
1878                    None
1879                }
1880            }
1881        } else {
1882            None
1883        }
1884    } else {
1885        None
1886    };
1887
1888    if let Some(route_configs) = route_configs {
1889        use axum::http::StatusCode;
1890        use axum::response::IntoResponse;
1891
1892        if !route_configs.is_empty() {
1893            info!("Registering {} custom route(s) from config", route_configs.len());
1894        }
1895
1896        let injector = route_chaos_injector.clone();
1897        for route_config in route_configs {
1898            let status = route_config.response.status;
1899            let body = route_config.response.body.clone();
1900            let headers = route_config.response.headers.clone();
1901            let path = route_config.path.clone();
1902            let method = route_config.method.clone();
1903
1904            // Create handler that returns the configured response with template expansion
1905            // Supports both basic templates ({{uuid}}, {{now}}) and request-aware templates
1906            // ({{request.query.name}}, {{request.path.id}}, {{request.headers.name}})
1907            // Register route using `any()` since we need full Request access for template expansion
1908            let expected_method = method.to_uppercase();
1909            // Clone Arc for the closure - Arc is Send-safe
1910            // Note: RouteChaosInjector is marked as Send+Sync via unsafe impl, so we have to
1911            // clone the Arc rather than move the inner injector into the route handler.
1912            let injector_clone = injector.clone();
1913            app = app.route(
1914                &path,
1915                #[allow(clippy::non_send_fields_in_send_ty)]
1916                axum::routing::any(move |req: Request<Body>| {
1917                    let body = body.clone();
1918                    let headers = headers.clone();
1919                    let expand = template_expand;
1920                    let expected = expected_method.clone();
1921                    let status_code = status;
1922                    // Clone Arc again for the async block
1923                    let injector_for_chaos = injector_clone.clone();
1924
1925                    async move {
1926                        // Check if request method matches expected method
1927                        if req.method().as_str() != expected.as_str() {
1928                            // Return 405 Method Not Allowed for wrong method
1929                            return axum::response::Response::builder()
1930                                .status(StatusCode::METHOD_NOT_ALLOWED)
1931                                .header("Allow", &expected)
1932                                .body(Body::empty())
1933                                .unwrap()
1934                                .into_response();
1935                        }
1936
1937                        // Apply advanced routing features (fault injection and latency) if available
1938                        // Use helper function to avoid capturing RouteChaosInjector in closure
1939                        // Pass the Arc as a reference to the helper function
1940                        if let Some(fault_response) = apply_route_chaos(
1941                            injector_for_chaos.as_deref(),
1942                            req.method(),
1943                            req.uri(),
1944                        )
1945                        .await
1946                        {
1947                            return fault_response;
1948                        }
1949
1950                        // Create JSON response from body, or empty object if None
1951                        let mut body_value = body.unwrap_or(serde_json::json!({}));
1952
1953                        // Apply template expansion if enabled
1954                        // Use mockforge-template-expansion crate which is completely isolated
1955                        // from mockforge-core to avoid Send issues (no rng() in dependency chain)
1956                        if expand {
1957                            use mockforge_template_expansion::RequestContext;
1958                            use serde_json::Value;
1959                            use std::collections::HashMap;
1960
1961                            // Extract request data for template expansion
1962                            let method = req.method().to_string();
1963                            let path = req.uri().path().to_string();
1964
1965                            // Extract query parameters
1966                            let query_params: HashMap<String, Value> = req
1967                                .uri()
1968                                .query()
1969                                .map(|q| {
1970                                    url::form_urlencoded::parse(q.as_bytes())
1971                                        .into_owned()
1972                                        .map(|(k, v)| (k, Value::String(v)))
1973                                        .collect()
1974                                })
1975                                .unwrap_or_default();
1976
1977                            // Extract headers
1978                            let headers: HashMap<String, Value> = req
1979                                .headers()
1980                                .iter()
1981                                .map(|(k, v)| {
1982                                    (
1983                                        k.to_string(),
1984                                        Value::String(v.to_str().unwrap_or_default().to_string()),
1985                                    )
1986                                })
1987                                .collect();
1988
1989                            // Create RequestContext for expand_prompt_template
1990                            // Using RequestContext from mockforge-template-expansion (not mockforge-core)
1991                            // to avoid bringing rng() into scope
1992                            let context = RequestContext {
1993                                method,
1994                                path,
1995                                query_params,
1996                                headers,
1997                                body: None, // Body extraction would require reading the request stream
1998                                path_params: HashMap::new(),
1999                                multipart_fields: HashMap::new(),
2000                                multipart_files: HashMap::new(),
2001                            };
2002
2003                            // Perform template expansion in spawn_blocking to ensure Send safety
2004                            // The template expansion crate is completely isolated from mockforge-core
2005                            // and doesn't have rng() in its dependency chain
2006                            let body_value_clone = body_value.clone();
2007                            let context_clone = context.clone();
2008                            body_value = match tokio::task::spawn_blocking(move || {
2009                                mockforge_template_expansion::expand_templates_in_json(
2010                                    body_value_clone,
2011                                    &context_clone,
2012                                )
2013                            })
2014                            .await
2015                            {
2016                                Ok(result) => result,
2017                                Err(_) => body_value, // Fallback to original on error
2018                            };
2019                        }
2020
2021                        let mut response = Json(body_value).into_response();
2022
2023                        // Set status code
2024                        *response.status_mut() =
2025                            StatusCode::from_u16(status_code).unwrap_or(StatusCode::OK);
2026
2027                        // Add custom headers
2028                        for (key, value) in headers {
2029                            if let Ok(header_name) = http::HeaderName::from_bytes(key.as_bytes()) {
2030                                if let Ok(header_value) = http::HeaderValue::from_str(&value) {
2031                                    response.headers_mut().insert(header_name, header_value);
2032                                }
2033                            }
2034                        }
2035
2036                        response
2037                    }
2038                }),
2039            );
2040
2041            debug!("Registered route: {} {}", method, path);
2042        }
2043    }
2044
2045    // Add health check endpoints
2046    if let Some(health) = health_manager {
2047        // Use comprehensive health check router with all probe endpoints
2048        app = app.merge(health::health_router(health));
2049        info!(
2050            "Health check endpoints enabled: /health, /health/live, /health/ready, /health/startup"
2051        );
2052    } else if include_default_health {
2053        // Fallback to basic health endpoint for backwards compatibility
2054        app = app.route(
2055            "/health",
2056            axum::routing::get(|| async {
2057                use mockforge_core::server_utils::health::HealthStatus;
2058                {
2059                    // HealthStatus should always serialize, but handle errors gracefully
2060                    match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
2061                        Ok(value) => Json(value),
2062                        Err(e) => {
2063                            // Log error but return a simple healthy response
2064                            tracing::error!("Failed to serialize health status: {}", e);
2065                            Json(serde_json::json!({
2066                                "status": "healthy",
2067                                "service": "mockforge-http",
2068                                "uptime_seconds": 0
2069                            }))
2070                        }
2071                    }
2072                }
2073            }),
2074        );
2075    }
2076
2077    app = app.merge(sse::sse_router());
2078    // Add file serving endpoints for generated mock files
2079    app = app.merge(file_server::file_serving_router());
2080
2081    // Add management API endpoints
2082    // Load spec for ManagementState so /__mockforge/api/spec can serve it
2083    let mgmt_spec = if let Some(ref sp) = spec_path {
2084        match OpenApiSpec::from_file(sp).await {
2085            Ok(s) => Some(Arc::new(s)),
2086            Err(e) => {
2087                debug!("Failed to load OpenAPI spec for management API: {}", e);
2088                None
2089            }
2090        }
2091    } else {
2092        None
2093    };
2094    let spec_path_clone = spec_path.clone();
2095    let mgmt_port = std::env::var("PORT")
2096        .or_else(|_| std::env::var("MOCKFORGE_HTTP_PORT"))
2097        .ok()
2098        .and_then(|p| p.parse().ok())
2099        .unwrap_or(3000);
2100    let management_state = ManagementState::new(mgmt_spec, spec_path_clone, mgmt_port);
2101
2102    // Create WebSocket state and connect it to management state
2103    use std::sync::Arc;
2104    let ws_state = WsManagementState::new();
2105    let ws_broadcast = Arc::new(ws_state.tx.clone());
2106    let management_state = management_state.with_ws_broadcast(ws_broadcast);
2107
2108    // Add proxy config to management state if available
2109    let management_state = if let Some(proxy_cfg) = proxy_config {
2110        use tokio::sync::RwLock;
2111        let proxy_config_arc = Arc::new(RwLock::new(proxy_cfg));
2112        management_state.with_proxy_config(proxy_config_arc)
2113    } else {
2114        management_state
2115    };
2116
2117    #[cfg(feature = "smtp")]
2118    let management_state = {
2119        if let Some(smtp_reg) = smtp_registry {
2120            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
2121                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
2122                Err(e) => {
2123                    error!(
2124                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
2125                        e.type_id()
2126                    );
2127                    management_state
2128                }
2129            }
2130        } else {
2131            management_state
2132        }
2133    };
2134    #[cfg(not(feature = "smtp"))]
2135    let management_state = {
2136        let _ = smtp_registry;
2137        management_state
2138    };
2139    #[cfg(feature = "mqtt")]
2140    let management_state = {
2141        if let Some(broker) = mqtt_broker {
2142            match broker.downcast::<mockforge_mqtt::MqttBroker>() {
2143                Ok(broker) => management_state.with_mqtt_broker(broker),
2144                Err(e) => {
2145                    error!(
2146                        "Invalid MQTT broker passed to HTTP management state: {:?}",
2147                        e.type_id()
2148                    );
2149                    management_state
2150                }
2151            }
2152        } else {
2153            management_state
2154        }
2155    };
2156    #[cfg(not(feature = "mqtt"))]
2157    let management_state = {
2158        let _ = mqtt_broker;
2159        management_state
2160    };
2161    let management_state_for_fallback = management_state.clone();
2162    app = app.nest("/__mockforge/api", management_router(management_state));
2163    // Dynamic-mock fallback; see identical block earlier in this file.
2164    app = app.fallback_service(
2165        axum::routing::any(management::dynamic_mock_fallback)
2166            .with_state(management_state_for_fallback),
2167    );
2168
2169    // Add verification API endpoint
2170    app = app.merge(verification_router());
2171
2172    // Mount the request-chains management API at the prefix the UI's
2173    // ChainsPage expects (`/__mockforge/chains/*`). Without this nest the
2174    // page 404s on every call. The registry/engine are constructed from
2175    // the chain_config param when supplied, otherwise from defaults.
2176    {
2177        use crate::chain_handlers::{chains_router, create_chain_state};
2178        let chain_config = _circling_config.clone().unwrap_or_default();
2179        let chain_registry = Arc::new(mockforge_core::request_chaining::RequestChainRegistry::new(
2180            chain_config.clone(),
2181        ));
2182        let chain_engine = Arc::new(mockforge_core::chain_execution::ChainExecutionEngine::new(
2183            chain_registry.clone(),
2184            chain_config,
2185        ));
2186        app = app.nest(
2187            "/__mockforge/chains",
2188            chains_router(create_chain_state(chain_registry, chain_engine)),
2189        );
2190    }
2191
2192    // Contract-diff retrieval API. The `capture_for_contract_diff`
2193    // middleware (layered earlier) populates the global capture manager
2194    // on every request; this surface lets operators read the captures
2195    // and run the analyser against the current OpenAPI spec.
2196    {
2197        use crate::contract_diff_api::{contract_diff_api_router, ContractDiffApiState};
2198        let cd_state = Arc::new(ContractDiffApiState::new(spec_path.clone()));
2199        app = app.nest("/__mockforge/api/contract-diff", contract_diff_api_router(cd_state));
2200    }
2201
2202    // Fixtures management API. Lives on the admin server too (port 9080),
2203    // but hosted-mock Fly machines only expose port 3000 publicly — so
2204    // we mount the same surface on the main HTTP app. Newly-uploaded
2205    // fixtures land in `MOCKFORGE_FIXTURES_DIR` and are picked up by
2206    // the startup-time loader on the next deploy/restart; live reload
2207    // is a separate concern.
2208    {
2209        use crate::fixtures_api::{fixtures_api_router, FixturesApiState};
2210        let fx_state = FixturesApiState::from_env();
2211        app = app.nest("/__mockforge/fixtures", fixtures_api_router(fx_state));
2212    }
2213
2214    // Standalone MockAI API. Until now MockAI was only invoked from the
2215    // OpenAPI route generator — so a hosted mock without a spec, or a
2216    // user wanting to prototype an "AI persona" mock without registering
2217    // routes, had no way to reach the engine. The endpoint surfaces 503
2218    // when MockAI isn't configured (no API key) so callers get a clear
2219    // signal rather than a silent failure.
2220    {
2221        use crate::mockai_api::{mockai_api_router, MockAiApiState};
2222        let api_state = MockAiApiState::new(mockai.clone());
2223        app = app.nest("/__mockforge/api/mockai", mockai_api_router(api_state));
2224    }
2225
2226    // Time-travel runtime API. The admin server mounts these routes on
2227    // its own port (9080), but hosted-mock Fly machines only expose
2228    // port 3000 publicly — so until now operators couldn't set / advance
2229    // virtual time on a deployed mock. The handlers consult a process-
2230    // wide TimeTravelManager that serve.rs initialises alongside the
2231    // existing admin-server registration; both paths see the same Arc.
2232    app = app.nest("/__mockforge/time-travel", time_travel_api::time_travel_router());
2233
2234    // Runtime route-chaos rules API + middleware. This sits in front of
2235    // the static per-route handlers so operators can add/remove fault and
2236    // latency rules without redeploying. Static rules from the YAML
2237    // config still apply to their routes; runtime rules are additive.
2238    {
2239        use crate::route_chaos_runtime::{
2240            route_chaos_api_router, runtime_route_chaos_middleware, RuntimeRouteChaosState,
2241        };
2242        let runtime_state = RuntimeRouteChaosState::new(Vec::new());
2243        let middleware_state = runtime_state.clone();
2244        app = app.layer(from_fn_with_state(middleware_state, runtime_route_chaos_middleware));
2245        app = app.nest("/__mockforge/api/route-chaos", route_chaos_api_router(runtime_state));
2246    }
2247
2248    // Runtime network-profile switching. Operators activate a named
2249    // catalog profile (e.g., "mobile_3g") and the middleware applies its
2250    // latency to every subsequent request — useful for "what does my
2251    // consumer look like under bad network" probes against a live
2252    // hosted mock without a redeploy.
2253    {
2254        use crate::network_profile_runtime::{
2255            network_profile_api_router, network_profile_middleware, NetworkProfileRuntimeState,
2256        };
2257        let runtime_state = NetworkProfileRuntimeState::new(
2258            mockforge_core::network_profiles::NetworkProfileCatalog::new(),
2259        );
2260        let middleware_state = runtime_state.clone();
2261        app = app.layer(from_fn_with_state(middleware_state, network_profile_middleware));
2262        app = app
2263            .nest("/__mockforge/api/network-profiles", network_profile_api_router(runtime_state));
2264    }
2265
2266    // Add OIDC well-known endpoints
2267    use crate::auth::oidc::oidc_router;
2268    app = app.merge(oidc_router());
2269
2270    // Add access review API if enabled
2271    {
2272        use mockforge_core::security::get_global_access_review_service;
2273        if let Some(service) = get_global_access_review_service().await {
2274            use crate::handlers::access_review::{access_review_router, AccessReviewState};
2275            let review_state = AccessReviewState { service };
2276            app = app.nest("/api/v1/security/access-reviews", access_review_router(review_state));
2277            debug!("Access review API mounted at /api/v1/security/access-reviews");
2278        }
2279    }
2280
2281    // Add privileged access API if enabled
2282    {
2283        use mockforge_core::security::get_global_privileged_access_manager;
2284        if let Some(manager) = get_global_privileged_access_manager().await {
2285            use crate::handlers::privileged_access::{
2286                privileged_access_router, PrivilegedAccessState,
2287            };
2288            let privileged_state = PrivilegedAccessState { manager };
2289            app = app.nest(
2290                "/api/v1/security/privileged-access",
2291                privileged_access_router(privileged_state),
2292            );
2293            debug!("Privileged access API mounted at /api/v1/security/privileged-access");
2294        }
2295    }
2296
2297    // Add change management API if enabled
2298    {
2299        use mockforge_core::security::get_global_change_management_engine;
2300        if let Some(engine) = get_global_change_management_engine().await {
2301            use crate::handlers::change_management::{
2302                change_management_router, ChangeManagementState,
2303            };
2304            let change_state = ChangeManagementState { engine };
2305            app = app.nest("/api/v1/change-management", change_management_router(change_state));
2306            debug!("Change management API mounted at /api/v1/change-management");
2307        }
2308    }
2309
2310    // Add risk assessment API if enabled
2311    {
2312        use mockforge_core::security::get_global_risk_assessment_engine;
2313        if let Some(engine) = get_global_risk_assessment_engine().await {
2314            use crate::handlers::risk_assessment::{risk_assessment_router, RiskAssessmentState};
2315            let risk_state = RiskAssessmentState { engine };
2316            app = app.nest("/api/v1/security", risk_assessment_router(risk_state));
2317            debug!("Risk assessment API mounted at /api/v1/security/risks");
2318        }
2319    }
2320
2321    // Add token lifecycle API
2322    {
2323        use crate::auth::token_lifecycle::TokenLifecycleManager;
2324        use crate::handlers::token_lifecycle::{token_lifecycle_router, TokenLifecycleState};
2325        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2326        let lifecycle_state = TokenLifecycleState {
2327            manager: lifecycle_manager,
2328        };
2329        app = app.nest("/api/v1/auth", token_lifecycle_router(lifecycle_state));
2330        debug!("Token lifecycle API mounted at /api/v1/auth");
2331    }
2332
2333    // Add OAuth2 server endpoints
2334    {
2335        use crate::auth::oidc::load_oidc_state;
2336        use crate::auth::token_lifecycle::TokenLifecycleManager;
2337        use crate::handlers::oauth2_server::{oauth2_server_router, OAuth2ServerState};
2338        // Load OIDC state from configuration (environment variables or config file)
2339        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
2340        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2341        let oauth2_state = OAuth2ServerState {
2342            oidc_state,
2343            lifecycle_manager,
2344            auth_codes: Arc::new(RwLock::new(HashMap::new())),
2345            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
2346        };
2347        app = app.merge(oauth2_server_router(oauth2_state));
2348        debug!("OAuth2 server endpoints mounted at /oauth2/authorize and /oauth2/token");
2349    }
2350
2351    // Add consent screen endpoints
2352    {
2353        use crate::auth::oidc::load_oidc_state;
2354        use crate::auth::risk_engine::RiskEngine;
2355        use crate::auth::token_lifecycle::TokenLifecycleManager;
2356        use crate::handlers::consent::{consent_router, ConsentState};
2357        use crate::handlers::oauth2_server::OAuth2ServerState;
2358        // Load OIDC state from configuration (environment variables or config file)
2359        let oidc_state = Arc::new(RwLock::new(load_oidc_state()));
2360        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
2361        let oauth2_state = OAuth2ServerState {
2362            oidc_state: oidc_state.clone(),
2363            lifecycle_manager: lifecycle_manager.clone(),
2364            auth_codes: Arc::new(RwLock::new(HashMap::new())),
2365            refresh_tokens: Arc::new(RwLock::new(HashMap::new())),
2366        };
2367        let risk_engine = Arc::new(RiskEngine::default());
2368        let consent_state = ConsentState {
2369            oauth2_state,
2370            risk_engine,
2371        };
2372        app = app.merge(consent_router(consent_state));
2373        debug!("Consent screen endpoints mounted at /consent");
2374    }
2375
2376    // Add risk simulation API
2377    {
2378        use crate::auth::risk_engine::RiskEngine;
2379        use crate::handlers::risk_simulation::{risk_simulation_router, RiskSimulationState};
2380        let risk_engine = Arc::new(RiskEngine::default());
2381        let risk_state = RiskSimulationState { risk_engine };
2382        app = app.nest("/api/v1/auth", risk_simulation_router(risk_state));
2383        debug!("Risk simulation API mounted at /api/v1/auth/risk");
2384    }
2385
2386    // Initialize database connection (optional)
2387    #[cfg(feature = "database")]
2388    let database = {
2389        use crate::database::Database;
2390        let database_url = std::env::var("DATABASE_URL").ok();
2391        match Database::connect_optional(database_url.as_deref()).await {
2392            Ok(db) => {
2393                if db.is_connected() {
2394                    // Run migrations if database is connected
2395                    if let Err(e) = db.migrate_if_connected().await {
2396                        warn!("Failed to run database migrations: {}", e);
2397                    } else {
2398                        info!("Database connected and migrations applied");
2399                    }
2400                }
2401                Some(db)
2402            }
2403            Err(e) => {
2404                warn!("Failed to connect to database: {}. Continuing without database support.", e);
2405                None
2406            }
2407        }
2408    };
2409
2410    // Add drift budget and incident management endpoints
2411    // Initialize shared components for drift tracking and protocol contracts
2412    let (drift_engine, incident_manager, drift_config) = {
2413        use mockforge_core::contract_drift::{DriftBudgetConfig, DriftBudgetEngine};
2414        use mockforge_core::incidents::{IncidentManager, IncidentStore};
2415        use std::sync::Arc;
2416
2417        // Initialize drift budget engine with default config
2418        let drift_config = DriftBudgetConfig::default();
2419        let drift_engine = Arc::new(DriftBudgetEngine::new(drift_config.clone()));
2420
2421        // Initialize incident store and manager
2422        let incident_store = Arc::new(IncidentStore::default());
2423        let incident_manager = Arc::new(IncidentManager::new(incident_store.clone()));
2424
2425        (drift_engine, incident_manager, drift_config)
2426    };
2427
2428    {
2429        use crate::handlers::drift_budget::{drift_budget_router, DriftBudgetState};
2430        use crate::middleware::drift_tracking::DriftTrackingState;
2431        use mockforge_contracts::consumer_contracts::{
2432            ConsumerBreakingChangeDetector, UsageRecorder,
2433        };
2434        use mockforge_core::ai_contract_diff::ContractDiffAnalyzer;
2435        use std::sync::Arc;
2436
2437        // Initialize usage recorder and consumer detector
2438        let usage_recorder = Arc::new(UsageRecorder::default());
2439        let consumer_detector =
2440            Arc::new(ConsumerBreakingChangeDetector::new(usage_recorder.clone()));
2441
2442        // Initialize contract diff analyzer if enabled
2443        let diff_analyzer = if drift_config.enabled {
2444            match ContractDiffAnalyzer::new(
2445                mockforge_core::ai_contract_diff::ContractDiffConfig::default(),
2446            ) {
2447                Ok(analyzer) => Some(Arc::new(analyzer)),
2448                Err(e) => {
2449                    warn!("Failed to create contract diff analyzer: {}", e);
2450                    None
2451                }
2452            }
2453        } else {
2454            None
2455        };
2456
2457        // Get OpenAPI spec if available
2458        // Note: Load from spec_path if available, or leave as None for manual configuration.
2459        let spec = if let Some(ref spec_path) = spec_path {
2460            match OpenApiSpec::from_file(spec_path).await {
2461                Ok(s) => Some(Arc::new(s)),
2462                Err(e) => {
2463                    debug!("Failed to load OpenAPI spec for drift tracking: {}", e);
2464                    None
2465                }
2466            }
2467        } else {
2468            None
2469        };
2470
2471        // Create drift tracking state
2472        let drift_tracking_state = DriftTrackingState {
2473            diff_analyzer,
2474            spec,
2475            drift_engine: drift_engine.clone(),
2476            incident_manager: incident_manager.clone(),
2477            usage_recorder,
2478            consumer_detector,
2479            enabled: drift_config.enabled,
2480        };
2481
2482        // Add response body buffering middleware (before drift tracking)
2483        app = app.layer(axum::middleware::from_fn(middleware::buffer_response_middleware));
2484
2485        // Add drift tracking middleware (after response buffering)
2486        // Use a wrapper that inserts state into extensions before calling the middleware
2487        let drift_tracking_state_clone = drift_tracking_state.clone();
2488        app = app.layer(axum::middleware::from_fn(
2489            move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2490                let state = drift_tracking_state_clone.clone();
2491                async move {
2492                    // Insert state into extensions if not already present
2493                    if req.extensions().get::<DriftTrackingState>().is_none() {
2494                        req.extensions_mut().insert(state);
2495                    }
2496                    // Call the middleware function
2497                    middleware::drift_tracking::drift_tracking_middleware_with_extensions(req, next)
2498                        .await
2499                }
2500            },
2501        ));
2502
2503        let drift_state = DriftBudgetState {
2504            engine: drift_engine.clone(),
2505            incident_manager: incident_manager.clone(),
2506            gitops_handler: None, // Can be initialized later if GitOps is configured
2507        };
2508
2509        app = app.merge(drift_budget_router(drift_state));
2510        debug!("Drift budget and incident management endpoints mounted at /api/v1/drift");
2511    }
2512
2513    // Add pipeline management endpoints (MockOps)
2514    #[cfg(feature = "pipelines")]
2515    {
2516        use crate::handlers::pipelines::{pipeline_router, PipelineState};
2517
2518        let pipeline_state = PipelineState::new();
2519        app = app.merge(pipeline_router(pipeline_state));
2520        debug!("Pipeline management endpoints mounted at /api/v1/pipelines");
2521    }
2522
2523    // Add governance endpoints (forecasting, semantic drift, threat modeling, contract health)
2524    {
2525        use crate::handlers::contract_health::{contract_health_router, ContractHealthState};
2526        use crate::handlers::forecasting::{forecasting_router, ForecastingState};
2527        use mockforge_contracts::contract_drift::forecasting::{Forecaster, ForecastingConfig};
2528        use mockforge_core::contract_drift::threat_modeling::ThreatAnalyzer;
2529        use mockforge_core::incidents::semantic_manager::SemanticIncidentManager;
2530        use mockforge_foundation::threat_modeling_types::ThreatModelingConfig;
2531        use mockforge_intelligence::handlers::semantic_drift::{
2532            semantic_drift_router, SemanticDriftState,
2533        };
2534        use mockforge_intelligence::handlers::threat_modeling::{
2535            threat_modeling_router, ThreatModelingState,
2536        };
2537        use std::sync::Arc;
2538
2539        // Initialize forecasting
2540        let forecasting_config = ForecastingConfig::default();
2541        let forecaster = Arc::new(Forecaster::new(forecasting_config));
2542        let forecasting_state = ForecastingState {
2543            forecaster,
2544            #[cfg(feature = "database")]
2545            database: database.clone(),
2546        };
2547
2548        // Initialize semantic drift manager
2549        let semantic_manager = Arc::new(SemanticIncidentManager::new());
2550        let semantic_state = SemanticDriftState {
2551            manager: semantic_manager,
2552            #[cfg(feature = "database")]
2553            database: database.clone(),
2554        };
2555
2556        // Initialize threat analyzer
2557        let threat_config = ThreatModelingConfig::default();
2558        let threat_analyzer = match ThreatAnalyzer::new(threat_config) {
2559            Ok(analyzer) => Arc::new(analyzer),
2560            Err(e) => {
2561                warn!("Failed to create threat analyzer: {}. Using default.", e);
2562                Arc::new(ThreatAnalyzer::new(ThreatModelingConfig::default()).unwrap_or_else(
2563                    |_| {
2564                        // Fallback to a minimal config if default also fails
2565                        ThreatAnalyzer::new(ThreatModelingConfig {
2566                            enabled: false,
2567                            ..Default::default()
2568                        })
2569                        .expect("Failed to create fallback threat analyzer")
2570                    },
2571                ))
2572            }
2573        };
2574        // Load webhook configs from ServerConfig
2575        let mut webhook_configs = Vec::new();
2576        let config_paths = [
2577            "config.yaml",
2578            "mockforge.yaml",
2579            "tools/mockforge/config.yaml",
2580            "../tools/mockforge/config.yaml",
2581        ];
2582
2583        for path in &config_paths {
2584            if let Ok(config) = mockforge_core::config::load_config(path).await {
2585                if !config.incidents.webhooks.is_empty() {
2586                    webhook_configs = config.incidents.webhooks.clone();
2587                    info!("Loaded {} webhook configs from config: {}", webhook_configs.len(), path);
2588                    break;
2589                }
2590            }
2591        }
2592
2593        if webhook_configs.is_empty() {
2594            debug!("No webhook configs found in config files, using empty list");
2595        }
2596
2597        let threat_state = ThreatModelingState {
2598            analyzer: threat_analyzer,
2599            webhook_configs,
2600            #[cfg(feature = "database")]
2601            database: database.clone(),
2602        };
2603
2604        // Initialize contract health state
2605        let contract_health_state = ContractHealthState {
2606            incident_manager: incident_manager.clone(),
2607            semantic_manager: Arc::new(SemanticIncidentManager::new()),
2608            #[cfg(feature = "database")]
2609            database: database.clone(),
2610        };
2611
2612        // Register routers
2613        app = app.merge(forecasting_router(forecasting_state));
2614        debug!("Forecasting endpoints mounted at /api/v1/forecasts");
2615
2616        app = app.merge(semantic_drift_router(semantic_state));
2617        debug!("Semantic drift endpoints mounted at /api/v1/semantic-drift");
2618
2619        app = app.merge(threat_modeling_router(threat_state));
2620        debug!("Threat modeling endpoints mounted at /api/v1/threats");
2621
2622        app = app.merge(contract_health_router(contract_health_state));
2623        debug!("Contract health endpoints mounted at /api/v1/contract-health");
2624    }
2625
2626    // Add protocol contracts endpoints with fitness registry initialization
2627    {
2628        use crate::handlers::protocol_contracts::{
2629            protocol_contracts_router, ProtocolContractState,
2630        };
2631        use mockforge_core::contract_drift::{
2632            ConsumerImpactAnalyzer, FitnessFunctionRegistry, ProtocolContractRegistry,
2633        };
2634        use std::sync::Arc;
2635        use tokio::sync::RwLock;
2636
2637        // Initialize protocol contract registry
2638        let contract_registry = Arc::new(RwLock::new(ProtocolContractRegistry::new()));
2639
2640        // Initialize fitness function registry and load from config
2641        let mut fitness_registry = FitnessFunctionRegistry::new();
2642
2643        // Try to load config and populate fitness rules
2644        let config_paths = [
2645            "config.yaml",
2646            "mockforge.yaml",
2647            "tools/mockforge/config.yaml",
2648            "../tools/mockforge/config.yaml",
2649        ];
2650
2651        let mut config_loaded = false;
2652        for path in &config_paths {
2653            if let Ok(config) = mockforge_core::config::load_config(path).await {
2654                if !config.contracts.fitness_rules.is_empty() {
2655                    if let Err(e) =
2656                        fitness_registry.load_from_config(&config.contracts.fitness_rules)
2657                    {
2658                        warn!("Failed to load fitness rules from config {}: {}", path, e);
2659                    } else {
2660                        info!(
2661                            "Loaded {} fitness rules from config: {}",
2662                            config.contracts.fitness_rules.len(),
2663                            path
2664                        );
2665                        config_loaded = true;
2666                        break;
2667                    }
2668                }
2669            }
2670        }
2671
2672        if !config_loaded {
2673            debug!("No fitness rules found in config files, using empty registry");
2674        }
2675
2676        let fitness_registry = Arc::new(RwLock::new(fitness_registry));
2677
2678        // Reuse drift engine and incident manager from drift budget section
2679
2680        // Initialize consumer impact analyzer
2681        let consumer_mapping_registry =
2682            mockforge_core::contract_drift::ConsumerMappingRegistry::new();
2683        let consumer_analyzer =
2684            Arc::new(RwLock::new(ConsumerImpactAnalyzer::new(consumer_mapping_registry)));
2685
2686        let protocol_state = ProtocolContractState {
2687            registry: contract_registry,
2688            drift_engine: Some(drift_engine.clone()),
2689            incident_manager: Some(incident_manager.clone()),
2690            fitness_registry: Some(fitness_registry),
2691            consumer_analyzer: Some(consumer_analyzer),
2692        };
2693
2694        app = app.nest("/api/v1/contracts", protocol_contracts_router(protocol_state));
2695        debug!("Protocol contracts endpoints mounted at /api/v1/contracts");
2696    }
2697
2698    // Add behavioral cloning middleware (optional - applies learned behavior to requests)
2699    #[cfg(feature = "behavioral-cloning")]
2700    {
2701        use crate::middleware::behavioral_cloning::BehavioralCloningMiddlewareState;
2702        use std::path::PathBuf;
2703
2704        // Determine database path (defaults to ./recordings.db)
2705        let db_path = std::env::var("RECORDER_DATABASE_PATH")
2706            .ok()
2707            .map(PathBuf::from)
2708            .or_else(|| std::env::current_dir().ok().map(|p| p.join("recordings.db")));
2709
2710        let bc_middleware_state = if let Some(path) = db_path {
2711            BehavioralCloningMiddlewareState::with_database_path(path)
2712        } else {
2713            BehavioralCloningMiddlewareState::new()
2714        };
2715
2716        // Only enable if BEHAVIORAL_CLONING_ENABLED is set to true
2717        let enabled = std::env::var("BEHAVIORAL_CLONING_ENABLED")
2718            .ok()
2719            .and_then(|v| v.parse::<bool>().ok())
2720            .unwrap_or(false);
2721
2722        if enabled {
2723            let bc_state_clone = bc_middleware_state.clone();
2724            app = app.layer(axum::middleware::from_fn(
2725                move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2726                    let state = bc_state_clone.clone();
2727                    async move {
2728                        // Insert state into extensions if not already present
2729                        if req.extensions().get::<BehavioralCloningMiddlewareState>().is_none() {
2730                            req.extensions_mut().insert(state);
2731                        }
2732                        // Call the middleware function
2733                        middleware::behavioral_cloning::behavioral_cloning_middleware(req, next)
2734                            .await
2735                    }
2736                },
2737            ));
2738            debug!("Behavioral cloning middleware enabled (applies learned behavior to requests)");
2739        }
2740    }
2741
2742    // Add consumer contracts endpoints
2743    {
2744        use crate::handlers::consumer_contracts::{
2745            consumer_contracts_router, ConsumerContractsState,
2746        };
2747        use mockforge_contracts::consumer_contracts::{
2748            ConsumerBreakingChangeDetector, ConsumerRegistry, UsageRecorder,
2749        };
2750        use std::sync::Arc;
2751
2752        // Initialize consumer registry
2753        let registry = Arc::new(ConsumerRegistry::default());
2754
2755        // Initialize usage recorder
2756        let usage_recorder = Arc::new(UsageRecorder::default());
2757
2758        // Initialize breaking change detector
2759        let detector = Arc::new(ConsumerBreakingChangeDetector::new(usage_recorder.clone()));
2760
2761        let consumer_state = ConsumerContractsState {
2762            registry,
2763            usage_recorder,
2764            detector,
2765            violations: Arc::new(RwLock::new(HashMap::new())),
2766        };
2767
2768        app = app.merge(consumer_contracts_router(consumer_state));
2769        debug!("Consumer contracts endpoints mounted at /api/v1/consumers");
2770    }
2771
2772    // Add behavioral cloning endpoints
2773    #[cfg(feature = "behavioral-cloning")]
2774    {
2775        use crate::handlers::behavioral_cloning::{
2776            behavioral_cloning_router, BehavioralCloningState,
2777        };
2778        use std::path::PathBuf;
2779
2780        // Determine database path (defaults to ./recordings.db)
2781        let db_path = std::env::var("RECORDER_DATABASE_PATH")
2782            .ok()
2783            .map(PathBuf::from)
2784            .or_else(|| std::env::current_dir().ok().map(|p| p.join("recordings.db")));
2785
2786        let bc_state = if let Some(path) = db_path {
2787            BehavioralCloningState::with_database_path(path)
2788        } else {
2789            BehavioralCloningState::new()
2790        };
2791
2792        app = app.merge(behavioral_cloning_router(bc_state));
2793        debug!("Behavioral cloning endpoints mounted at /api/v1/behavioral-cloning");
2794    }
2795
2796    // Add consistency engine and cross-protocol state management
2797    {
2798        use crate::consistency::{ConsistencyMiddlewareState, HttpAdapter};
2799        use crate::handlers::consistency::{consistency_router, ConsistencyState};
2800        use mockforge_core::consistency::ConsistencyEngine;
2801        use std::sync::Arc;
2802
2803        // Initialize consistency engine
2804        let consistency_engine = Arc::new(ConsistencyEngine::new());
2805
2806        // Create and register HTTP adapter
2807        let http_adapter = Arc::new(HttpAdapter::new(consistency_engine.clone()));
2808        consistency_engine.register_adapter(http_adapter.clone()).await;
2809
2810        // Create consistency state for handlers
2811        let consistency_state = ConsistencyState {
2812            engine: consistency_engine.clone(),
2813        };
2814
2815        // Create X-Ray state first (needed for middleware)
2816        use crate::handlers::xray::XRayState;
2817        let xray_state = Arc::new(XRayState {
2818            engine: consistency_engine.clone(),
2819            request_contexts: std::sync::Arc::new(RwLock::new(HashMap::new())),
2820        });
2821
2822        // Create consistency middleware state
2823        let consistency_middleware_state = ConsistencyMiddlewareState {
2824            engine: consistency_engine.clone(),
2825            adapter: http_adapter,
2826            xray_state: Some(xray_state.clone()),
2827        };
2828
2829        // Reality-driven mock/proxy middleware (#222). Must be added
2830        // BEFORE the consistency layer so it ends up inner — that way
2831        // each request hits consistency first (which injects UnifiedState
2832        // including reality_continuum_ratio), then this middleware reads
2833        // that state and either forwards to upstream or falls through to
2834        // the mock chain. Activated only when MOCKFORGE_PROXY_UPSTREAM is
2835        // set; absent that, the layer isn't even installed.
2836        if let Some(reality_cfg) = reality_proxy::RealityProxyConfig::from_env() {
2837            tracing::info!(
2838                upstream = %reality_cfg.upstream_base,
2839                "Reality-driven proxy middleware enabled — requests will be split between mock and upstream based on reality_continuum_ratio"
2840            );
2841            app = app.layer(axum::middleware::from_fn(
2842                move |req: axum::extract::Request, next: axum::middleware::Next| {
2843                    let cfg = reality_cfg.clone();
2844                    async move { reality_proxy::reality_proxy_middleware(cfg, req, next).await }
2845                },
2846            ));
2847        }
2848
2849        // Add consistency middleware (before other middleware to inject state early)
2850        let consistency_middleware_state_clone = consistency_middleware_state.clone();
2851        app = app.layer(axum::middleware::from_fn(
2852            move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2853                let state = consistency_middleware_state_clone.clone();
2854                async move {
2855                    // Insert state into extensions if not already present
2856                    if req.extensions().get::<ConsistencyMiddlewareState>().is_none() {
2857                        req.extensions_mut().insert(state);
2858                    }
2859                    // Call the middleware function
2860                    consistency::middleware::consistency_middleware(req, next).await
2861                }
2862            },
2863        ));
2864
2865        // Add consistency API endpoints
2866        app = app.merge(consistency_router(consistency_state));
2867        debug!("Consistency engine initialized and endpoints mounted at /api/v1/consistency");
2868
2869        // Runtime named-scenario activation API. Locally-installed
2870        // scenarios (from `~/.mockforge/scenario-metadata/*.json`) become
2871        // listable + activatable via HTTP; activation writes through to
2872        // the consistency engine's active_scenario slot for the chosen
2873        // workspace. Failure to load the scenario index is non-fatal —
2874        // the API just shows an empty list, which matches the natural
2875        // state of a fresh deployment with no scenarios installed yet.
2876        #[cfg(feature = "scenario-engine")]
2877        {
2878            use crate::scenarios_runtime::{scenarios_api_router, ScenarioRuntimeState};
2879            let mut scenario_storage = match mockforge_scenarios::ScenarioStorage::new() {
2880                Ok(s) => s,
2881                Err(e) => {
2882                    tracing::warn!(
2883                        error = %e,
2884                        "Failed to init scenario storage; runtime scenarios API will list empty"
2885                    );
2886                    // Construct an isolated tempdir-backed storage so the
2887                    // API still mounts but lists nothing.
2888                    let tmp = std::env::temp_dir().join("mockforge-empty-scenarios");
2889                    mockforge_scenarios::ScenarioStorage::with_dir(&tmp)
2890                        .expect("temp scenario storage")
2891                }
2892            };
2893            if let Err(e) = scenario_storage.load().await {
2894                tracing::warn!(
2895                    error = %e,
2896                    "Failed to load installed scenarios; API will list empty until scenarios are installed"
2897                );
2898            }
2899            let scenarios_state =
2900                ScenarioRuntimeState::new(scenario_storage, consistency_engine.clone());
2901            app = app.nest("/__mockforge/api/scenarios", scenarios_api_router(scenarios_state));
2902            debug!("Scenario runtime API mounted at /__mockforge/api/scenarios");
2903        }
2904
2905        // Add fidelity score endpoints
2906        {
2907            use crate::handlers::fidelity::{fidelity_router, FidelityState};
2908            let fidelity_state = FidelityState::new();
2909            app = app.merge(fidelity_router(fidelity_state));
2910            debug!("Fidelity score endpoints mounted at /api/v1/workspace/:workspace_id/fidelity");
2911        }
2912
2913        // Add scenario studio endpoints
2914        {
2915            use crate::handlers::scenario_studio::{scenario_studio_router, ScenarioStudioState};
2916            let scenario_studio_state = ScenarioStudioState::new();
2917            app = app.merge(scenario_studio_router(scenario_studio_state));
2918            debug!("Scenario Studio endpoints mounted at /api/v1/scenario-studio");
2919        }
2920
2921        // Add performance mode endpoints
2922        {
2923            use crate::handlers::performance::{performance_router, PerformanceState};
2924            let performance_state = PerformanceState::new();
2925            app = app.nest("/api/performance", performance_router(performance_state));
2926            debug!("Performance mode endpoints mounted at /api/performance");
2927        }
2928
2929        // Add world state endpoints
2930        {
2931            use crate::handlers::world_state::{world_state_router, WorldStateState};
2932            use mockforge_world_state::WorldStateEngine;
2933            use std::sync::Arc;
2934            use tokio::sync::RwLock;
2935
2936            let world_state_engine = Arc::new(RwLock::new(WorldStateEngine::new()));
2937            let world_state_state = WorldStateState {
2938                engine: world_state_engine,
2939            };
2940            app = app.nest("/api/world-state", world_state_router().with_state(world_state_state));
2941            debug!("World state endpoints mounted at /api/world-state");
2942        }
2943
2944        // Add snapshot management endpoints
2945        {
2946            use crate::handlers::snapshots::{snapshot_router, SnapshotState};
2947            use mockforge_core::snapshots::SnapshotManager;
2948            use std::path::PathBuf;
2949
2950            let snapshot_dir = std::env::var("MOCKFORGE_SNAPSHOT_DIR").ok().map(PathBuf::from);
2951            let snapshot_manager = Arc::new(SnapshotManager::new(snapshot_dir));
2952
2953            let snapshot_state = SnapshotState {
2954                manager: snapshot_manager,
2955                consistency_engine: Some(consistency_engine.clone()),
2956                workspace_persistence: None, // Can be initialized later if workspace persistence is available
2957                vbr_engine: None, // Can be initialized when VBR engine is available in server state
2958                recorder: None,   // Can be initialized when Recorder is available in server state
2959            };
2960
2961            app = app.merge(snapshot_router(snapshot_state));
2962            debug!("Snapshot management endpoints mounted at /api/v1/snapshots");
2963
2964            // Add X-Ray API endpoints for browser extension
2965            {
2966                use crate::handlers::xray::xray_router;
2967                app = app.merge(xray_router((*xray_state).clone()));
2968                debug!("X-Ray API endpoints mounted at /api/v1/xray");
2969            }
2970        }
2971
2972        // Add A/B testing endpoints and middleware
2973        {
2974            use crate::handlers::ab_testing::{ab_testing_router, ABTestingState};
2975            use crate::middleware::ab_testing::ab_testing_middleware;
2976
2977            let ab_testing_state = ABTestingState::new();
2978
2979            // Add A/B testing middleware (before other response middleware)
2980            let ab_testing_state_clone = ab_testing_state.clone();
2981            app = app.layer(axum::middleware::from_fn(
2982                move |mut req: axum::extract::Request, next: axum::middleware::Next| {
2983                    let state = ab_testing_state_clone.clone();
2984                    async move {
2985                        // Insert state into extensions if not already present
2986                        if req.extensions().get::<ABTestingState>().is_none() {
2987                            req.extensions_mut().insert(state);
2988                        }
2989                        // Call the middleware function
2990                        ab_testing_middleware(req, next).await
2991                    }
2992                },
2993            ));
2994
2995            // Add A/B testing API endpoints
2996            app = app.merge(ab_testing_router(ab_testing_state));
2997            debug!("A/B testing endpoints mounted at /api/v1/ab-tests");
2998        }
2999    }
3000
3001    // Add PR generation endpoints (optional - only if configured)
3002    {
3003        use crate::handlers::pr_generation::{pr_generation_router, PRGenerationState};
3004        use mockforge_intelligence::pr_generation::{PRGenerator, PRProvider};
3005        use std::sync::Arc;
3006
3007        // Load PR generation config from environment or use default
3008        let pr_config = mockforge_intelligence::pr_generation::PRGenerationConfig::from_env();
3009
3010        let generator = if pr_config.enabled && pr_config.token.is_some() {
3011            let token = pr_config.token.as_ref().unwrap().clone();
3012            let generator = match pr_config.provider {
3013                PRProvider::GitHub => PRGenerator::new_github(
3014                    pr_config.owner.clone(),
3015                    pr_config.repo.clone(),
3016                    token,
3017                    pr_config.base_branch.clone(),
3018                ),
3019                PRProvider::GitLab => PRGenerator::new_gitlab(
3020                    pr_config.owner.clone(),
3021                    pr_config.repo.clone(),
3022                    token,
3023                    pr_config.base_branch.clone(),
3024                ),
3025            };
3026            Some(Arc::new(generator))
3027        } else {
3028            None
3029        };
3030
3031        let pr_state = PRGenerationState {
3032            generator: generator.clone(),
3033        };
3034
3035        app = app.merge(pr_generation_router(pr_state));
3036        if generator.is_some() {
3037            debug!(
3038                "PR generation endpoints mounted at /api/v1/pr (configured for {:?})",
3039                pr_config.provider
3040            );
3041        } else {
3042            debug!("PR generation endpoints mounted at /api/v1/pr (not configured - set GITHUB_TOKEN/GITLAB_TOKEN and PR_REPO_OWNER/PR_REPO_NAME)");
3043        }
3044    }
3045
3046    // Add management WebSocket endpoint
3047    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
3048
3049    // Add workspace routing middleware if multi-tenant is enabled
3050    if let Some(mt_config) = multi_tenant_config {
3051        if mt_config.enabled {
3052            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
3053            use std::sync::Arc;
3054
3055            info!(
3056                "Multi-tenant mode enabled with {} routing strategy",
3057                match mt_config.routing_strategy {
3058                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Path => "path-based",
3059                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Port => "port-based",
3060                    mockforge_foundation::multi_tenant_types::RoutingStrategy::Both => "hybrid",
3061                }
3062            );
3063
3064            // Create the multi-tenant workspace registry
3065            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
3066
3067            // Register the default workspace before wrapping in Arc
3068            let default_workspace =
3069                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
3070            if let Err(e) =
3071                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
3072            {
3073                warn!("Failed to register default workspace: {}", e);
3074            } else {
3075                info!("Registered default workspace: '{}'", mt_config.default_workspace);
3076            }
3077
3078            // Wrap registry in Arc for shared access
3079            let registry = Arc::new(registry);
3080
3081            // Create workspace router
3082            let _workspace_router = WorkspaceRouter::new(registry);
3083            info!("Workspace routing middleware initialized for HTTP server");
3084        }
3085    }
3086
3087    // Apply deceptive deploy configuration if enabled
3088    let mut final_cors_config = cors_config;
3089    let mut production_headers: Option<std::sync::Arc<HashMap<String, String>>> = None;
3090    // Auth config from deceptive deploy OAuth (if configured)
3091    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
3092    let mut rate_limit_config = middleware::RateLimitConfig {
3093        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
3094            .ok()
3095            .and_then(|v| v.parse().ok())
3096            .unwrap_or(1000),
3097        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
3098            .ok()
3099            .and_then(|v| v.parse().ok())
3100            .unwrap_or(2000),
3101        per_ip: true,
3102        per_endpoint: false,
3103    };
3104
3105    if let Some(deploy_config) = &deceptive_deploy_config {
3106        if deploy_config.enabled {
3107            info!("Deceptive deploy mode enabled - applying production-like configuration");
3108
3109            // Override CORS config if provided
3110            if let Some(prod_cors) = &deploy_config.cors {
3111                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
3112                    enabled: true,
3113                    allowed_origins: prod_cors.allowed_origins.clone(),
3114                    allowed_methods: prod_cors.allowed_methods.clone(),
3115                    allowed_headers: prod_cors.allowed_headers.clone(),
3116                    allow_credentials: prod_cors.allow_credentials,
3117                });
3118                info!("Applied production-like CORS configuration");
3119            }
3120
3121            // Override rate limit config if provided
3122            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
3123                rate_limit_config = middleware::RateLimitConfig {
3124                    requests_per_minute: prod_rate_limit.requests_per_minute,
3125                    burst: prod_rate_limit.burst,
3126                    per_ip: prod_rate_limit.per_ip,
3127                    per_endpoint: false,
3128                };
3129                info!(
3130                    "Applied production-like rate limiting: {} req/min, burst: {}",
3131                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
3132                );
3133            }
3134
3135            // Set production headers
3136            if !deploy_config.headers.is_empty() {
3137                let headers_map: HashMap<String, String> = deploy_config.headers.clone();
3138                production_headers = Some(std::sync::Arc::new(headers_map));
3139                info!("Configured {} production headers", deploy_config.headers.len());
3140            }
3141
3142            // Integrate OAuth config from deceptive deploy
3143            if let Some(prod_oauth) = &deploy_config.oauth {
3144                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
3145                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
3146                    oauth2: Some(oauth2_config),
3147                    ..Default::default()
3148                });
3149                info!("Applied production-like OAuth configuration for deceptive deploy");
3150            }
3151        }
3152    }
3153
3154    // Initialize rate limiter and state
3155    let rate_limit_disabled = middleware::is_rate_limit_disabled();
3156    let rate_limiter =
3157        std::sync::Arc::new(middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
3158
3159    let mut state = HttpServerState::new();
3160    if rate_limit_disabled {
3161        info!(
3162            "HTTP rate limiting disabled (MOCKFORGE_RATE_LIMIT_ENABLED=false or --no-rate-limit)"
3163        );
3164    } else {
3165        state = state.with_rate_limiter(rate_limiter.clone());
3166    }
3167
3168    // Add production headers to state if configured
3169    if let Some(headers) = production_headers.clone() {
3170        state = state.with_production_headers(headers);
3171    }
3172
3173    // Add rate limiting middleware (no-op when state.rate_limiter is None)
3174    app = app.layer(from_fn_with_state(state.clone(), middleware::rate_limit_middleware));
3175
3176    // Add production headers middleware if configured
3177    if state.production_headers.is_some() {
3178        app =
3179            app.layer(from_fn_with_state(state.clone(), middleware::production_headers_middleware));
3180    }
3181
3182    // Optionally advertise `Connection: keep-alive` + `Keep-Alive: timeout=N,
3183    // max=M` on every response (Issue #79 workaround for HTTP/1.0 upstream
3184    // proxies that observe FIN from MockForge after each response). Opt in
3185    // via `MOCKFORGE_HTTP_KEEPALIVE_HINT=1`.
3186    if middleware::is_keepalive_hint_enabled() {
3187        info!(
3188            "MOCKFORGE_HTTP_KEEPALIVE_HINT enabled — emitting Connection: keep-alive + Keep-Alive headers on all responses (Issue #79 workaround)"
3189        );
3190        app = app.layer(axum::middleware::from_fn(middleware::keepalive_hint_middleware));
3191    }
3192
3193    // Issue #79 (round 5): per-request log line with HTTP version + Connection
3194    // header MockForge actually sees, so users debugging proxy ↔ MockForge
3195    // negotiation can confirm whether their proxy is speaking HTTP/1.1 with
3196    // keep-alive or HTTP/1.0 (which forces hyper to FIN after each response).
3197    if middleware::is_conn_log_enabled() {
3198        info!(
3199            "MOCKFORGE_HTTP_LOG_CONN enabled — logging HTTP version + Connection headers per request (Issue #79 diagnostic)"
3200        );
3201        app = app.layer(axum::middleware::from_fn(middleware::conn_diag_middleware));
3202    }
3203
3204    // Add authentication middleware if OAuth is configured via deceptive deploy
3205    if let Some(auth_config) = deceptive_deploy_auth_config {
3206        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
3207        use std::collections::HashMap;
3208        use std::sync::Arc;
3209        use tokio::sync::RwLock;
3210
3211        // Create OAuth2 client if configured
3212        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
3213            match create_oauth2_client(oauth2_config) {
3214                Ok(client) => Some(client),
3215                Err(e) => {
3216                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
3217                    None
3218                }
3219            }
3220        } else {
3221            None
3222        };
3223
3224        // Create auth state
3225        let auth_state = AuthState {
3226            config: auth_config,
3227            spec: None, // OpenAPI spec not available in this context
3228            oauth2_client,
3229            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
3230        };
3231
3232        // Apply auth middleware
3233        app = app.layer(from_fn_with_state(auth_state, auth_middleware));
3234        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
3235    }
3236
3237    // Add runtime daemon 404 detection middleware (if enabled)
3238    #[cfg(feature = "runtime-daemon")]
3239    {
3240        use mockforge_runtime_daemon::{AutoGenerator, NotFoundDetector, RuntimeDaemonConfig};
3241        use std::sync::Arc;
3242
3243        // Load runtime daemon config from environment
3244        let daemon_config = RuntimeDaemonConfig::from_env();
3245
3246        if daemon_config.enabled {
3247            info!("Runtime daemon enabled - auto-creating mocks from 404s");
3248
3249            // Determine management API URL (default to localhost:3000)
3250            let management_api_url =
3251                std::env::var("MOCKFORGE_MANAGEMENT_API_URL").unwrap_or_else(|_| {
3252                    let port =
3253                        std::env::var("MOCKFORGE_HTTP_PORT").unwrap_or_else(|_| "3000".to_string());
3254                    format!("http://localhost:{}", port)
3255                });
3256
3257            // Create auto-generator
3258            let generator = Arc::new(AutoGenerator::new(daemon_config.clone(), management_api_url));
3259
3260            // Create detector and set generator
3261            let detector = NotFoundDetector::new(daemon_config.clone());
3262            detector.set_generator(generator).await;
3263
3264            // Add middleware layer
3265            let detector_clone = detector.clone();
3266            app = app.layer(axum::middleware::from_fn(
3267                move |req: axum::extract::Request, next: axum::middleware::Next| {
3268                    let detector = detector_clone.clone();
3269                    async move { detector.detect_and_auto_create(req, next).await }
3270                },
3271            ));
3272
3273            debug!("Runtime daemon 404 detection middleware added");
3274        }
3275    }
3276
3277    // Add /__mockforge/routes endpoint so the admin UI can discover registered routes
3278    {
3279        let routes_state = HttpServerState::with_routes(captured_routes);
3280        let routes_router = Router::new()
3281            .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
3282            .with_state(routes_state);
3283        app = app.merge(routes_router);
3284    }
3285
3286    // Add API docs page (Scalar-powered interactive explorer)
3287    app = app.route("/__mockforge/docs", axum::routing::get(get_docs_handler));
3288
3289    // Note: OData URI rewrite is applied at the service level in serve_router_with_tls()
3290    // because Router::layer() only applies to matched routes, not unmatched ones.
3291
3292    // Add request logging middleware to capture all requests for the admin dashboard
3293    app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
3294
3295    // Add contract diff middleware for automatic request capture
3296    // This captures requests for contract diff analysis
3297    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
3298
3299    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
3300    app = apply_cors_middleware(app, final_cors_config);
3301
3302    app
3303}
3304
3305// Note: start_with_traffic_shaping function removed due to compilation issues
3306// Use build_router_with_traffic_shaping_and_multi_tenant directly instead
3307
3308#[test]
3309fn test_route_info_clone() {
3310    let route = RouteInfo {
3311        method: "POST".to_string(),
3312        path: "/users".to_string(),
3313        operation_id: Some("createUser".to_string()),
3314        summary: None,
3315        description: None,
3316        parameters: vec![],
3317    };
3318
3319    let cloned = route.clone();
3320    assert_eq!(route.method, cloned.method);
3321    assert_eq!(route.path, cloned.path);
3322    assert_eq!(route.operation_id, cloned.operation_id);
3323}
3324
3325#[test]
3326fn test_http_server_state_new() {
3327    let state = HttpServerState::new();
3328    assert_eq!(state.routes.len(), 0);
3329}
3330
3331#[test]
3332fn test_http_server_state_with_routes() {
3333    let routes = vec![
3334        RouteInfo {
3335            method: "GET".to_string(),
3336            path: "/users".to_string(),
3337            operation_id: Some("getUsers".to_string()),
3338            summary: None,
3339            description: None,
3340            parameters: vec![],
3341        },
3342        RouteInfo {
3343            method: "POST".to_string(),
3344            path: "/users".to_string(),
3345            operation_id: Some("createUser".to_string()),
3346            summary: None,
3347            description: None,
3348            parameters: vec![],
3349        },
3350    ];
3351
3352    let state = HttpServerState::with_routes(routes.clone());
3353    assert_eq!(state.routes.len(), 2);
3354    assert_eq!(state.routes[0].method, "GET");
3355    assert_eq!(state.routes[1].method, "POST");
3356}
3357
3358#[test]
3359fn test_http_server_state_clone() {
3360    let routes = vec![RouteInfo {
3361        method: "GET".to_string(),
3362        path: "/test".to_string(),
3363        operation_id: None,
3364        summary: None,
3365        description: None,
3366        parameters: vec![],
3367    }];
3368
3369    let state = HttpServerState::with_routes(routes);
3370    let cloned = state.clone();
3371
3372    assert_eq!(state.routes.len(), cloned.routes.len());
3373    assert_eq!(state.routes[0].method, cloned.routes[0].method);
3374}
3375
3376#[tokio::test]
3377async fn test_build_router_without_openapi() {
3378    let _router = build_router(None, None, None).await;
3379    // Should succeed without OpenAPI spec
3380}
3381
3382#[tokio::test]
3383async fn test_build_router_with_nonexistent_spec() {
3384    let _router = build_router(Some("/nonexistent/spec.yaml".to_string()), None, None).await;
3385    // Should succeed but log a warning
3386}
3387
3388#[tokio::test]
3389async fn test_build_router_with_auth_and_latency() {
3390    let _router = build_router_with_auth_and_latency(None, None, None, None).await;
3391    // Should succeed without parameters
3392}
3393
3394#[tokio::test]
3395async fn test_build_router_with_latency() {
3396    let _router = build_router_with_latency(None, None, None).await;
3397    // Should succeed without parameters
3398}
3399
3400#[tokio::test]
3401async fn test_build_router_with_auth() {
3402    let _router = build_router_with_auth(None, None, None).await;
3403    // Should succeed without parameters
3404}
3405
3406#[tokio::test]
3407async fn test_build_router_with_chains() {
3408    let _router = build_router_with_chains(None, None, None).await;
3409    // Should succeed without parameters
3410}
3411
3412#[test]
3413fn test_route_info_with_all_fields() {
3414    let route = RouteInfo {
3415        method: "PUT".to_string(),
3416        path: "/users/{id}".to_string(),
3417        operation_id: Some("updateUser".to_string()),
3418        summary: Some("Update user".to_string()),
3419        description: Some("Updates an existing user".to_string()),
3420        parameters: vec!["id".to_string(), "body".to_string()],
3421    };
3422
3423    assert!(route.operation_id.is_some());
3424    assert!(route.summary.is_some());
3425    assert!(route.description.is_some());
3426    assert_eq!(route.parameters.len(), 2);
3427}
3428
3429#[test]
3430fn test_route_info_with_minimal_fields() {
3431    let route = RouteInfo {
3432        method: "DELETE".to_string(),
3433        path: "/users/{id}".to_string(),
3434        operation_id: None,
3435        summary: None,
3436        description: None,
3437        parameters: vec![],
3438    };
3439
3440    assert!(route.operation_id.is_none());
3441    assert!(route.summary.is_none());
3442    assert!(route.description.is_none());
3443    assert_eq!(route.parameters.len(), 0);
3444}
3445
3446#[test]
3447fn test_http_server_state_empty_routes() {
3448    let state = HttpServerState::with_routes(vec![]);
3449    assert_eq!(state.routes.len(), 0);
3450}
3451
3452#[test]
3453fn test_http_server_state_multiple_routes() {
3454    let routes = vec![
3455        RouteInfo {
3456            method: "GET".to_string(),
3457            path: "/users".to_string(),
3458            operation_id: Some("listUsers".to_string()),
3459            summary: Some("List all users".to_string()),
3460            description: None,
3461            parameters: vec![],
3462        },
3463        RouteInfo {
3464            method: "GET".to_string(),
3465            path: "/users/{id}".to_string(),
3466            operation_id: Some("getUser".to_string()),
3467            summary: Some("Get a user".to_string()),
3468            description: None,
3469            parameters: vec!["id".to_string()],
3470        },
3471        RouteInfo {
3472            method: "POST".to_string(),
3473            path: "/users".to_string(),
3474            operation_id: Some("createUser".to_string()),
3475            summary: Some("Create a user".to_string()),
3476            description: None,
3477            parameters: vec!["body".to_string()],
3478        },
3479    ];
3480
3481    let state = HttpServerState::with_routes(routes);
3482    assert_eq!(state.routes.len(), 3);
3483
3484    // Verify different HTTP methods
3485    let methods: Vec<&str> = state.routes.iter().map(|r| r.method.as_str()).collect();
3486    assert!(methods.contains(&"GET"));
3487    assert!(methods.contains(&"POST"));
3488}
3489
3490#[test]
3491fn test_http_server_state_with_rate_limiter() {
3492    use std::sync::Arc;
3493
3494    let config = middleware::RateLimitConfig::default();
3495    let rate_limiter = Arc::new(middleware::GlobalRateLimiter::new(config));
3496
3497    let state = HttpServerState::new().with_rate_limiter(rate_limiter);
3498
3499    assert!(state.rate_limiter.is_some());
3500    assert_eq!(state.routes.len(), 0);
3501}
3502
3503#[tokio::test]
3504async fn test_build_router_includes_rate_limiter() {
3505    let _router = build_router(None, None, None).await;
3506    // Router should be created successfully with rate limiter initialized
3507}