mockforge_http/
lib.rs

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