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/// Contract diff middleware for automatic request capture
169pub mod contract_diff_middleware;
170pub mod coverage;
171/// File generation service for creating mock PDF, CSV, JSON files
172pub mod file_generator;
173/// File serving for generated mock files
174pub mod file_server;
175/// Kubernetes-native health check endpoints (liveness, readiness, startup probes)
176pub mod health;
177pub mod http_tracing_middleware;
178/// Latency profile configuration for HTTP request simulation
179pub mod latency_profiles;
180/// Management API for server control and monitoring
181pub mod management;
182/// WebSocket-based management API for real-time updates
183pub mod management_ws;
184pub mod metrics_middleware;
185pub mod middleware;
186pub mod op_middleware;
187/// Browser/Mobile Proxy Server
188pub mod proxy_server;
189/// Quick mock generation utilities
190pub mod quick_mock;
191/// RAG-powered AI response generation
192pub mod rag_ai_generator;
193/// Replay listing and fixture management
194pub mod replay_listing;
195pub mod request_logging;
196/// Specification import API for OpenAPI and AsyncAPI
197pub mod spec_import;
198/// Server-Sent Events for streaming logs and metrics
199pub mod sse;
200/// State machine API for scenario state machines
201pub mod state_machine_api;
202/// TLS/HTTPS support
203pub mod tls;
204/// Token response utilities
205pub mod token_response;
206/// UI Builder API for low-code mock endpoint creation
207pub mod ui_builder;
208/// Verification API for request verification
209pub mod verification;
210
211// Access review handlers
212pub mod handlers;
213
214// Re-export AI handler utilities
215pub use ai_handler::{process_response_with_ai, AiResponseConfig, AiResponseHandler};
216// Re-export health check utilities
217pub use health::{HealthManager, ServiceStatus};
218
219// Re-export management API utilities
220pub use management::{
221    management_router, management_router_with_ui_builder, ManagementState, MockConfig,
222    ServerConfig, ServerStats,
223};
224
225// Re-export UI Builder utilities
226pub use ui_builder::{create_ui_builder_router, EndpointConfig, UIBuilderState};
227
228// Re-export management WebSocket utilities
229pub use management_ws::{ws_management_router, MockEvent, WsManagementState};
230
231// Re-export verification API utilities
232pub use verification::verification_router;
233
234// Re-export metrics middleware
235pub use metrics_middleware::collect_http_metrics;
236
237// Re-export tracing middleware
238pub use http_tracing_middleware::http_tracing_middleware;
239
240// Re-export coverage utilities
241pub use coverage::{calculate_coverage, CoverageReport, MethodCoverage, RouteCoverage};
242
243/// Helper function to load persona from config file
244/// Tries to load from common config locations: config.yaml, mockforge.yaml, tools/mockforge/config.yaml
245async fn load_persona_from_config() -> Option<Arc<Persona>> {
246    use mockforge_core::config::load_config;
247
248    // Try common config file locations
249    let config_paths = [
250        "config.yaml",
251        "mockforge.yaml",
252        "tools/mockforge/config.yaml",
253        "../tools/mockforge/config.yaml",
254    ];
255
256    for path in &config_paths {
257        if let Ok(config) = load_config(path).await {
258            // Access intelligent_behavior through mockai config
259            // Note: Config structure is mockai.intelligent_behavior.personas
260            if let Some(persona) = config.mockai.intelligent_behavior.personas.get_active_persona() {
261                tracing::info!(
262                    "Loaded active persona '{}' from config file: {}",
263                    persona.name,
264                    path
265                );
266                return Some(Arc::new(persona.clone()));
267            } else {
268                tracing::debug!(
269                    "No active persona found in config file: {} (personas count: {})",
270                    path,
271                    config.mockai.intelligent_behavior.personas.personas.len()
272                );
273            }
274        } else {
275            tracing::debug!("Could not load config from: {}", path);
276        }
277    }
278
279    tracing::debug!("No persona found in config files, persona-based generation will be disabled");
280    None
281}
282
283use axum::middleware::from_fn_with_state;
284use axum::{extract::State, response::Json, Router};
285use mockforge_core::failure_injection::{FailureConfig, FailureInjector};
286use mockforge_core::latency::LatencyInjector;
287use mockforge_core::openapi::OpenApiSpec;
288use mockforge_core::openapi_routes::OpenApiRouteRegistry;
289use mockforge_core::openapi_routes::ValidationOptions;
290use mockforge_core::intelligent_behavior::config::Persona;
291use std::sync::Arc;
292use tower_http::cors::{Any, CorsLayer};
293
294use mockforge_core::LatencyProfile;
295#[cfg(feature = "data-faker")]
296use mockforge_data::provider::register_core_faker_provider;
297use std::collections::HashMap;
298use std::ffi::OsStr;
299use std::path::Path;
300use tokio::fs;
301use tokio::sync::RwLock;
302use tracing::*;
303
304/// Route info for storing in state
305#[derive(Clone)]
306pub struct RouteInfo {
307    /// HTTP method (GET, POST, PUT, etc.)
308    pub method: String,
309    /// API path pattern (e.g., "/api/users/{id}")
310    pub path: String,
311    /// OpenAPI operation ID if available
312    pub operation_id: Option<String>,
313    /// Operation summary from OpenAPI spec
314    pub summary: Option<String>,
315    /// Operation description from OpenAPI spec
316    pub description: Option<String>,
317    /// List of parameter names for this route
318    pub parameters: Vec<String>,
319}
320
321/// Shared state for tracking OpenAPI routes
322#[derive(Clone)]
323pub struct HttpServerState {
324    /// List of registered routes from OpenAPI spec
325    pub routes: Vec<RouteInfo>,
326    /// Optional global rate limiter for request throttling
327    pub rate_limiter: Option<std::sync::Arc<crate::middleware::rate_limit::GlobalRateLimiter>>,
328    /// Production headers to add to all responses (for deceptive deploy)
329    pub production_headers: Option<std::sync::Arc<std::collections::HashMap<String, String>>>,
330}
331
332impl Default for HttpServerState {
333    fn default() -> Self {
334        Self::new()
335    }
336}
337
338impl HttpServerState {
339    /// Create a new empty HTTP server state
340    pub fn new() -> Self {
341        Self {
342            routes: Vec::new(),
343            rate_limiter: None,
344            production_headers: None,
345        }
346    }
347
348    /// Create HTTP server state with pre-configured routes
349    pub fn with_routes(routes: Vec<RouteInfo>) -> Self {
350        Self {
351            routes,
352            rate_limiter: None,
353            production_headers: None,
354        }
355    }
356
357    /// Add a rate limiter to the HTTP server state
358    pub fn with_rate_limiter(
359        mut self,
360        rate_limiter: std::sync::Arc<crate::middleware::rate_limit::GlobalRateLimiter>,
361    ) -> Self {
362        self.rate_limiter = Some(rate_limiter);
363        self
364    }
365
366    /// Add production headers to the HTTP server state
367    pub fn with_production_headers(
368        mut self,
369        headers: std::sync::Arc<std::collections::HashMap<String, String>>,
370    ) -> Self {
371        self.production_headers = Some(headers);
372        self
373    }
374}
375
376/// Handler to return OpenAPI routes information
377async fn get_routes_handler(State(state): State<HttpServerState>) -> Json<serde_json::Value> {
378    let route_info: Vec<serde_json::Value> = state
379        .routes
380        .iter()
381        .map(|route| {
382            serde_json::json!({
383                "method": route.method,
384                "path": route.path,
385                "operation_id": route.operation_id,
386                "summary": route.summary,
387                "description": route.description,
388                "parameters": route.parameters
389            })
390        })
391        .collect();
392
393    Json(serde_json::json!({
394        "routes": route_info,
395        "total": state.routes.len()
396    }))
397}
398
399/// Build the base HTTP router, optionally from an OpenAPI spec.
400pub async fn build_router(
401    spec_path: Option<String>,
402    options: Option<ValidationOptions>,
403    failure_config: Option<FailureConfig>,
404) -> Router {
405    build_router_with_multi_tenant(
406        spec_path,
407        options,
408        failure_config,
409        None,
410        None,
411        None,
412        None,
413        None,
414        None,
415        None,
416    )
417    .await
418}
419
420/// Apply CORS middleware to the router based on configuration
421fn apply_cors_middleware(
422    app: Router,
423    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
424) -> Router {
425    use http::Method;
426    use tower_http::cors::AllowOrigin;
427
428    if let Some(config) = cors_config {
429        if !config.enabled {
430            return app;
431        }
432
433        let mut cors_layer = CorsLayer::new();
434        let mut is_wildcard_origin = false;
435
436        // Configure allowed origins
437        if config.allowed_origins.contains(&"*".to_string()) {
438            cors_layer = cors_layer.allow_origin(Any);
439            is_wildcard_origin = true;
440        } else if !config.allowed_origins.is_empty() {
441            // Try to parse each origin, fallback to permissive if parsing fails
442            let origins: Vec<_> = config
443                .allowed_origins
444                .iter()
445                .filter_map(|origin| {
446                    origin.parse::<http::HeaderValue>().ok().map(|hv| AllowOrigin::exact(hv))
447                })
448                .collect();
449
450            if origins.is_empty() {
451                // If no valid origins, use permissive for development
452                warn!("No valid CORS origins configured, using permissive CORS");
453                cors_layer = cors_layer.allow_origin(Any);
454                is_wildcard_origin = true;
455            } else {
456                // Use the first origin as exact match (tower-http limitation)
457                // For multiple origins, we'd need a custom implementation
458                if origins.len() == 1 {
459                    cors_layer = cors_layer.allow_origin(origins[0].clone());
460                    is_wildcard_origin = false;
461                } else {
462                    // Multiple origins - use permissive for now
463                    warn!(
464                        "Multiple CORS origins configured, using permissive CORS. \
465                        Consider using '*' for all origins."
466                    );
467                    cors_layer = cors_layer.allow_origin(Any);
468                    is_wildcard_origin = true;
469                }
470            }
471        } else {
472            // No origins specified, use permissive for development
473            cors_layer = cors_layer.allow_origin(Any);
474            is_wildcard_origin = true;
475        }
476
477        // Configure allowed methods
478        if !config.allowed_methods.is_empty() {
479            let methods: Vec<Method> =
480                config.allowed_methods.iter().filter_map(|m| m.parse().ok()).collect();
481            if !methods.is_empty() {
482                cors_layer = cors_layer.allow_methods(methods);
483            }
484        } else {
485            // Default to common HTTP methods
486            cors_layer = cors_layer.allow_methods([
487                Method::GET,
488                Method::POST,
489                Method::PUT,
490                Method::DELETE,
491                Method::PATCH,
492                Method::OPTIONS,
493            ]);
494        }
495
496        // Configure allowed headers
497        if !config.allowed_headers.is_empty() {
498            let headers: Vec<_> = config
499                .allowed_headers
500                .iter()
501                .filter_map(|h| h.parse::<http::HeaderName>().ok())
502                .collect();
503            if !headers.is_empty() {
504                cors_layer = cors_layer.allow_headers(headers);
505            }
506        } else {
507            // Default headers
508            cors_layer =
509                cors_layer.allow_headers([http::header::CONTENT_TYPE, http::header::AUTHORIZATION]);
510        }
511
512        // Configure credentials - cannot allow credentials with wildcard origin
513        // Determine if credentials should be allowed
514        // Cannot allow credentials with wildcard origin per CORS spec
515        let should_allow_credentials = if is_wildcard_origin {
516            // Wildcard origin - credentials must be false
517            false
518        } else {
519            // Specific origins - use config value (defaults to false)
520            config.allow_credentials
521        };
522
523        cors_layer = cors_layer.allow_credentials(should_allow_credentials);
524
525        info!(
526            "CORS middleware enabled with configured settings (credentials: {})",
527            should_allow_credentials
528        );
529        app.layer(cors_layer)
530    } else {
531        // No CORS config provided - use permissive CORS for development
532        // Note: permissive() allows credentials, but since it uses wildcard origin,
533        // we need to disable credentials to avoid CORS spec violation
534        debug!("No CORS config provided, using permissive CORS for development");
535        // Create a permissive CORS layer but disable credentials to avoid CORS spec violation
536        // (cannot combine credentials with wildcard origin)
537        app.layer(CorsLayer::permissive().allow_credentials(false))
538    }
539}
540
541/// Build the base HTTP router with multi-tenant workspace support
542#[allow(clippy::too_many_arguments)]
543pub async fn build_router_with_multi_tenant(
544    spec_path: Option<String>,
545    options: Option<ValidationOptions>,
546    failure_config: Option<FailureConfig>,
547    multi_tenant_config: Option<mockforge_core::MultiTenantConfig>,
548    _route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
549    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
550    ai_generator: Option<
551        std::sync::Arc<dyn mockforge_core::openapi::response::AiGenerator + Send + Sync>,
552    >,
553    smtp_registry: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
554    mockai: Option<
555        std::sync::Arc<tokio::sync::RwLock<mockforge_core::intelligent_behavior::MockAI>>,
556    >,
557    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
558) -> Router {
559    use std::time::Instant;
560
561    let startup_start = Instant::now();
562
563    // Set up the basic router
564    let mut app = Router::new();
565
566    // Initialize rate limiter with default configuration
567    // Can be customized via environment variables or config
568    let mut rate_limit_config = crate::middleware::RateLimitConfig {
569        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
570            .ok()
571            .and_then(|v| v.parse().ok())
572            .unwrap_or(1000),
573        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
574            .ok()
575            .and_then(|v| v.parse().ok())
576            .unwrap_or(2000),
577        per_ip: true,
578        per_endpoint: false,
579    };
580
581    // Apply deceptive deploy configuration if enabled
582    let mut final_cors_config = cors_config;
583    let mut production_headers: Option<std::sync::Arc<std::collections::HashMap<String, String>>> =
584        None;
585    // Auth config from deceptive deploy OAuth (if configured)
586    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
587
588    if let Some(deploy_config) = &deceptive_deploy_config {
589        if deploy_config.enabled {
590            info!("Deceptive deploy mode enabled - applying production-like configuration");
591
592            // Override CORS config if provided
593            if let Some(prod_cors) = &deploy_config.cors {
594                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
595                    enabled: true,
596                    allowed_origins: prod_cors.allowed_origins.clone(),
597                    allowed_methods: prod_cors.allowed_methods.clone(),
598                    allowed_headers: prod_cors.allowed_headers.clone(),
599                    allow_credentials: prod_cors.allow_credentials,
600                });
601                info!("Applied production-like CORS configuration");
602            }
603
604            // Override rate limit config if provided
605            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
606                rate_limit_config = crate::middleware::RateLimitConfig {
607                    requests_per_minute: prod_rate_limit.requests_per_minute,
608                    burst: prod_rate_limit.burst,
609                    per_ip: prod_rate_limit.per_ip,
610                    per_endpoint: false,
611                };
612                info!(
613                    "Applied production-like rate limiting: {} req/min, burst: {}",
614                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
615                );
616            }
617
618            // Set production headers
619            if !deploy_config.headers.is_empty() {
620                let headers_map: std::collections::HashMap<String, String> =
621                    deploy_config.headers.clone();
622                production_headers = Some(std::sync::Arc::new(headers_map));
623                info!("Configured {} production headers", deploy_config.headers.len());
624            }
625
626            // Integrate OAuth config from deceptive deploy
627            if let Some(prod_oauth) = &deploy_config.oauth {
628                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
629                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
630                    oauth2: Some(oauth2_config),
631                    ..Default::default()
632                });
633                info!("Applied production-like OAuth configuration for deceptive deploy");
634            }
635        }
636    }
637
638    let rate_limiter =
639        std::sync::Arc::new(crate::middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
640
641    let mut state = HttpServerState::new().with_rate_limiter(rate_limiter.clone());
642
643    // Add production headers to state if configured
644    if let Some(headers) = production_headers.clone() {
645        state = state.with_production_headers(headers);
646    }
647
648    // Clone spec_path for later use
649    let spec_path_for_mgmt = spec_path.clone();
650
651    // If an OpenAPI spec is provided, integrate it
652    if let Some(spec_path) = spec_path {
653        tracing::debug!("Processing OpenAPI spec path: {}", spec_path);
654
655        // Measure OpenAPI spec loading
656        let spec_load_start = Instant::now();
657        match OpenApiSpec::from_file(&spec_path).await {
658            Ok(openapi) => {
659                let spec_load_duration = spec_load_start.elapsed();
660                info!(
661                    "Successfully loaded OpenAPI spec from {} (took {:?})",
662                    spec_path, spec_load_duration
663                );
664
665                // Measure route registry creation
666                tracing::debug!("Creating OpenAPI route registry...");
667                let registry_start = Instant::now();
668
669                // Try to load persona from config if available
670                let persona = load_persona_from_config().await;
671
672                let registry = if let Some(opts) = options {
673                    tracing::debug!("Using custom validation options");
674                    if let Some(ref persona) = persona {
675                        tracing::info!("Using persona '{}' for route generation", persona.name);
676                    }
677                    OpenApiRouteRegistry::new_with_options_and_persona(openapi, opts, persona)
678                } else {
679                    tracing::debug!("Using environment-based options");
680                    if let Some(ref persona) = persona {
681                        tracing::info!("Using persona '{}' for route generation", persona.name);
682                    }
683                    OpenApiRouteRegistry::new_with_env_and_persona(openapi, persona)
684                };
685                let registry_duration = registry_start.elapsed();
686                info!(
687                    "Created OpenAPI route registry with {} routes (took {:?})",
688                    registry.routes().len(),
689                    registry_duration
690                );
691
692                // Measure route extraction
693                let extract_start = Instant::now();
694                let route_info: Vec<RouteInfo> = registry
695                    .routes()
696                    .iter()
697                    .map(|route| RouteInfo {
698                        method: route.method.clone(),
699                        path: route.path.clone(),
700                        operation_id: route.operation.operation_id.clone(),
701                        summary: route.operation.summary.clone(),
702                        description: route.operation.description.clone(),
703                        parameters: route.parameters.clone(),
704                    })
705                    .collect();
706                state.routes = route_info;
707                let extract_duration = extract_start.elapsed();
708                debug!("Extracted route information (took {:?})", extract_duration);
709
710                // Measure overrides loading
711                let overrides = if std::env::var("MOCKFORGE_HTTP_OVERRIDES_GLOB").is_ok() {
712                    tracing::debug!("Loading overrides from environment variable");
713                    let overrides_start = Instant::now();
714                    match mockforge_core::Overrides::load_from_globs(&[]).await {
715                        Ok(overrides) => {
716                            let overrides_duration = overrides_start.elapsed();
717                            info!(
718                                "Loaded {} override rules (took {:?})",
719                                overrides.rules().len(),
720                                overrides_duration
721                            );
722                            Some(overrides)
723                        }
724                        Err(e) => {
725                            tracing::warn!("Failed to load overrides: {}", e);
726                            None
727                        }
728                    }
729                } else {
730                    None
731                };
732
733                // Measure router building
734                let router_build_start = Instant::now();
735                let overrides_enabled = overrides.is_some();
736                let openapi_router = if let Some(mockai_instance) = &mockai {
737                    tracing::debug!("Building router with MockAI support");
738                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
739                } else if let Some(ai_generator) = &ai_generator {
740                    tracing::debug!("Building router with AI generator support");
741                    registry.build_router_with_ai(Some(ai_generator.clone()))
742                } else if let Some(failure_config) = &failure_config {
743                    tracing::debug!("Building router with failure injection and overrides");
744                    let failure_injector = FailureInjector::new(Some(failure_config.clone()), true);
745                    registry.build_router_with_injectors_and_overrides(
746                        LatencyInjector::default(),
747                        Some(failure_injector),
748                        overrides,
749                        overrides_enabled,
750                    )
751                } else {
752                    tracing::debug!("Building router with overrides");
753                    registry.build_router_with_injectors_and_overrides(
754                        LatencyInjector::default(),
755                        None,
756                        overrides,
757                        overrides_enabled,
758                    )
759                };
760                let router_build_duration = router_build_start.elapsed();
761                debug!("Built OpenAPI router (took {:?})", router_build_duration);
762
763                tracing::debug!("Merging OpenAPI router with main router");
764                app = app.merge(openapi_router);
765                tracing::debug!("Router built successfully");
766            }
767            Err(e) => {
768                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
769            }
770        }
771    }
772
773    // Add basic health check endpoint
774    app = app.route(
775        "/health",
776        axum::routing::get(|| async {
777            use mockforge_core::server_utils::health::HealthStatus;
778            {
779                // HealthStatus should always serialize, but handle errors gracefully
780                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
781                    Ok(value) => axum::Json(value),
782                    Err(e) => {
783                        // Log error but return a simple healthy response
784                        tracing::error!("Failed to serialize health status: {}", e);
785                        axum::Json(serde_json::json!({
786                            "status": "healthy",
787                            "service": "mockforge-http",
788                            "uptime_seconds": 0
789                        }))
790                    }
791                }
792            }
793        }),
794    )
795    // Add SSE endpoints
796    .merge(sse::sse_router())
797    // Add file serving endpoints for generated mock files
798    .merge(file_server::file_serving_router());
799
800    // Clone state for routes_router since we'll use it for middleware too
801    let state_for_routes = state.clone();
802
803    // Create a router with state for the routes and coverage endpoints
804    let routes_router = Router::new()
805        .route("/__mockforge/routes", axum::routing::get(get_routes_handler))
806        .route("/__mockforge/coverage", axum::routing::get(coverage::get_coverage_handler))
807        .with_state(state_for_routes);
808
809    // Merge the routes router with the main app
810    app = app.merge(routes_router);
811
812    // Add static coverage UI
813    // Determine the path to the coverage.html file
814    let coverage_html_path = std::env::var("MOCKFORGE_COVERAGE_UI_PATH")
815        .unwrap_or_else(|_| "crates/mockforge-http/static/coverage.html".to_string());
816
817    // Check if the file exists before serving it
818    if std::path::Path::new(&coverage_html_path).exists() {
819        app = app.nest_service(
820            "/__mockforge/coverage.html",
821            tower_http::services::ServeFile::new(&coverage_html_path),
822        );
823        debug!("Serving coverage UI from: {}", coverage_html_path);
824    } else {
825        debug!(
826            "Coverage UI file not found at: {}. Skipping static file serving.",
827            coverage_html_path
828        );
829    }
830
831    // Add management API endpoints
832    let mut management_state = ManagementState::new(None, spec_path_for_mgmt, 3000); // Port will be updated when we know the actual port
833
834    // Create WebSocket state and connect it to management state
835    use std::sync::Arc;
836    let ws_state = WsManagementState::new();
837    let ws_broadcast = Arc::new(ws_state.tx.clone());
838    let management_state = management_state.with_ws_broadcast(ws_broadcast);
839
840    // Note: ProxyConfig not available in this build function path
841    // Migration endpoints will work once ProxyConfig is passed to build_router_with_chains_and_multi_tenant
842
843    #[cfg(feature = "smtp")]
844    let management_state = {
845        if let Some(smtp_reg) = smtp_registry {
846            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
847                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
848                Err(e) => {
849                    error!(
850                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
851                        e.type_id()
852                    );
853                    management_state
854                }
855            }
856        } else {
857            management_state
858        }
859    };
860    #[cfg(not(feature = "smtp"))]
861    let management_state = management_state;
862    #[cfg(not(feature = "smtp"))]
863    let _ = smtp_registry;
864    app = app.nest("/__mockforge/api", management_router(management_state));
865
866    // Add verification API endpoint
867    app = app.merge(verification_router());
868
869    // Add OIDC well-known endpoints
870    use crate::auth::oidc::oidc_router;
871    app = app.merge(oidc_router());
872
873    // Add access review API if enabled
874    {
875        use mockforge_core::security::get_global_access_review_service;
876        if let Some(service) = get_global_access_review_service().await {
877            use crate::handlers::access_review::{access_review_router, AccessReviewState};
878            let review_state = AccessReviewState { service };
879            app = app.nest("/api/v1/security/access-reviews", access_review_router(review_state));
880            debug!("Access review API mounted at /api/v1/security/access-reviews");
881        }
882    }
883
884    // Add privileged access API if enabled
885    {
886        use mockforge_core::security::get_global_privileged_access_manager;
887        if let Some(manager) = get_global_privileged_access_manager().await {
888            use crate::handlers::privileged_access::{privileged_access_router, PrivilegedAccessState};
889            let privileged_state = PrivilegedAccessState { manager };
890            app = app.nest("/api/v1/security/privileged-access", privileged_access_router(privileged_state));
891            debug!("Privileged access API mounted at /api/v1/security/privileged-access");
892        }
893    }
894
895    // Add change management API if enabled
896    {
897        use mockforge_core::security::get_global_change_management_engine;
898        if let Some(engine) = get_global_change_management_engine().await {
899            use crate::handlers::change_management::{change_management_router, ChangeManagementState};
900            let change_state = ChangeManagementState { engine };
901            app = app.nest("/api/v1/change-management", change_management_router(change_state));
902            debug!("Change management API mounted at /api/v1/change-management");
903        }
904    }
905
906    // Add risk assessment API if enabled
907    {
908        use mockforge_core::security::get_global_risk_assessment_engine;
909        if let Some(engine) = get_global_risk_assessment_engine().await {
910            use crate::handlers::risk_assessment::{risk_assessment_router, RiskAssessmentState};
911            let risk_state = RiskAssessmentState { engine };
912            app = app.nest("/api/v1/security", risk_assessment_router(risk_state));
913            debug!("Risk assessment API mounted at /api/v1/security/risks");
914        }
915    }
916
917    // Add token lifecycle API
918    {
919        use crate::auth::token_lifecycle::TokenLifecycleManager;
920        use crate::handlers::token_lifecycle::{token_lifecycle_router, TokenLifecycleState};
921        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
922        let lifecycle_state = TokenLifecycleState {
923            manager: lifecycle_manager,
924        };
925        app = app.nest("/api/v1/auth", token_lifecycle_router(lifecycle_state));
926        debug!("Token lifecycle API mounted at /api/v1/auth");
927    }
928
929    // Add OAuth2 server endpoints
930    {
931        use crate::auth::oidc::OidcState;
932        use crate::auth::token_lifecycle::TokenLifecycleManager;
933        use crate::handlers::oauth2_server::{oauth2_server_router, OAuth2ServerState};
934        let oidc_state = Arc::new(RwLock::new(None::<OidcState>)); // TODO: Load from config
935        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
936        let oauth2_state = OAuth2ServerState {
937            oidc_state,
938            lifecycle_manager,
939            auth_codes: Arc::new(RwLock::new(HashMap::new())),
940        };
941        app = app.merge(oauth2_server_router(oauth2_state));
942        debug!("OAuth2 server endpoints mounted at /oauth2/authorize and /oauth2/token");
943    }
944
945    // Add consent screen endpoints
946    {
947        use crate::auth::risk_engine::RiskEngine;
948        use crate::auth::token_lifecycle::TokenLifecycleManager;
949        use crate::handlers::consent::{consent_router, ConsentState};
950        use crate::handlers::oauth2_server::OAuth2ServerState;
951        use crate::auth::oidc::OidcState;
952        let oidc_state = Arc::new(RwLock::new(None::<OidcState>)); // TODO: Load from config
953        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
954        let oauth2_state = OAuth2ServerState {
955            oidc_state: oidc_state.clone(),
956            lifecycle_manager: lifecycle_manager.clone(),
957            auth_codes: Arc::new(RwLock::new(HashMap::new())),
958        };
959        let risk_engine = Arc::new(RiskEngine::default());
960        let consent_state = ConsentState {
961            oauth2_state,
962            risk_engine,
963        };
964        app = app.merge(consent_router(consent_state));
965        debug!("Consent screen endpoints mounted at /consent");
966    }
967
968    // Add risk simulation API
969    {
970        use crate::auth::risk_engine::RiskEngine;
971        use crate::handlers::risk_simulation::{risk_simulation_router, RiskSimulationState};
972        let risk_engine = Arc::new(RiskEngine::default());
973        let risk_state = RiskSimulationState { risk_engine };
974        app = app.nest("/api/v1/auth", risk_simulation_router(risk_state));
975        debug!("Risk simulation API mounted at /api/v1/auth/risk");
976    }
977
978    // Add management WebSocket endpoint
979    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
980
981    // Add request logging middleware to capture all requests
982    app = app.layer(axum::middleware::from_fn(request_logging::log_http_requests));
983
984    // Add security middleware for security event tracking (after logging, before contract diff)
985    app = app.layer(axum::middleware::from_fn(crate::middleware::security_middleware));
986
987    // Add contract diff middleware for automatic request capture
988    // This captures requests for contract diff analysis (after logging)
989    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
990
991    // Add rate limiting middleware (before logging to rate limit early)
992    app = app.layer(from_fn_with_state(state.clone(), crate::middleware::rate_limit_middleware));
993
994    // Add production headers middleware if configured
995    if state.production_headers.is_some() {
996        app = app.layer(from_fn_with_state(
997            state.clone(),
998            crate::middleware::production_headers_middleware,
999        ));
1000    }
1001
1002    // Add authentication middleware if OAuth is configured via deceptive deploy
1003    if let Some(auth_config) = deceptive_deploy_auth_config {
1004        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1005        use std::collections::HashMap;
1006        use std::sync::Arc;
1007        use tokio::sync::RwLock;
1008
1009        // Create OAuth2 client if configured
1010        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
1011            match create_oauth2_client(oauth2_config) {
1012                Ok(client) => Some(client),
1013                Err(e) => {
1014                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
1015                    None
1016                }
1017            }
1018        } else {
1019            None
1020        };
1021
1022        // Create auth state
1023        let auth_state = AuthState {
1024            config: auth_config,
1025            spec: None, // OpenAPI spec not available in this context
1026            oauth2_client,
1027            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1028        };
1029
1030        // Apply auth middleware
1031        app = app.layer(axum::middleware::from_fn_with_state(auth_state, auth_middleware));
1032        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
1033    }
1034
1035    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
1036    app = apply_cors_middleware(app, final_cors_config);
1037
1038    // Add workspace routing middleware if multi-tenant is enabled
1039    if let Some(mt_config) = multi_tenant_config {
1040        if mt_config.enabled {
1041            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
1042            use std::sync::Arc;
1043
1044            info!(
1045                "Multi-tenant mode enabled with {} routing strategy",
1046                match mt_config.routing_strategy {
1047                    mockforge_core::RoutingStrategy::Path => "path-based",
1048                    mockforge_core::RoutingStrategy::Port => "port-based",
1049                    mockforge_core::RoutingStrategy::Both => "hybrid",
1050                }
1051            );
1052
1053            // Create the multi-tenant workspace registry
1054            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
1055
1056            // Register the default workspace before wrapping in Arc
1057            let default_workspace =
1058                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
1059            if let Err(e) =
1060                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
1061            {
1062                warn!("Failed to register default workspace: {}", e);
1063            } else {
1064                info!("Registered default workspace: '{}'", mt_config.default_workspace);
1065            }
1066
1067            // Auto-discover and register workspaces if configured
1068            if mt_config.auto_discover {
1069                if let Some(config_dir) = &mt_config.config_directory {
1070                    let config_path = Path::new(config_dir);
1071                    if config_path.exists() && config_path.is_dir() {
1072                        match fs::read_dir(config_path).await {
1073                            Ok(mut entries) => {
1074                                while let Ok(Some(entry)) = entries.next_entry().await {
1075                                    let path = entry.path();
1076                                    if path.extension() == Some(OsStr::new("yaml")) {
1077                                        match fs::read_to_string(&path).await {
1078                                            Ok(content) => {
1079                                                match serde_yaml::from_str::<
1080                                                    mockforge_core::Workspace,
1081                                                >(
1082                                                    &content
1083                                                ) {
1084                                                    Ok(workspace) => {
1085                                                        if let Err(e) = registry.register_workspace(
1086                                                            workspace.id.clone(),
1087                                                            workspace,
1088                                                        ) {
1089                                                            warn!("Failed to register auto-discovered workspace from {:?}: {}", path, e);
1090                                                        } else {
1091                                                            info!("Auto-registered workspace from {:?}", path);
1092                                                        }
1093                                                    }
1094                                                    Err(e) => {
1095                                                        warn!("Failed to parse workspace from {:?}: {}", path, e);
1096                                                    }
1097                                                }
1098                                            }
1099                                            Err(e) => {
1100                                                warn!(
1101                                                    "Failed to read workspace file {:?}: {}",
1102                                                    path, e
1103                                                );
1104                                            }
1105                                        }
1106                                    }
1107                                }
1108                            }
1109                            Err(e) => {
1110                                warn!("Failed to read config directory {:?}: {}", config_path, e);
1111                            }
1112                        }
1113                    } else {
1114                        warn!(
1115                            "Config directory {:?} does not exist or is not a directory",
1116                            config_path
1117                        );
1118                    }
1119                }
1120            }
1121
1122            // Wrap registry in Arc for shared access
1123            let registry = Arc::new(registry);
1124
1125            // Create workspace router and wrap the app with workspace middleware
1126            let _workspace_router = WorkspaceRouter::new(registry);
1127
1128            // Note: The actual middleware integration would need to be implemented
1129            // in the WorkspaceRouter to work with Axum's middleware system
1130            info!("Workspace routing middleware initialized for HTTP server");
1131        }
1132    }
1133
1134    let total_startup_duration = startup_start.elapsed();
1135    info!("HTTP router startup completed (total time: {:?})", total_startup_duration);
1136
1137    app
1138}
1139
1140/// Build the base HTTP router with authentication and latency support
1141pub async fn build_router_with_auth_and_latency(
1142    _spec_path: Option<String>,
1143    _options: Option<()>,
1144    _auth_config: Option<mockforge_core::config::AuthConfig>,
1145    _latency_injector: Option<LatencyInjector>,
1146) -> Router {
1147    // For now, just use the basic router. Full auth and latency support can be added later.
1148    build_router(None, None, None).await
1149}
1150
1151/// Build the base HTTP router with latency injection support
1152pub async fn build_router_with_latency(
1153    _spec_path: Option<String>,
1154    _options: Option<ValidationOptions>,
1155    _latency_injector: Option<LatencyInjector>,
1156) -> Router {
1157    // For now, fall back to basic router since injectors are complex to implement
1158    build_router(None, None, None).await
1159}
1160
1161/// Build the base HTTP router with authentication support
1162pub async fn build_router_with_auth(
1163    spec_path: Option<String>,
1164    options: Option<ValidationOptions>,
1165    auth_config: Option<mockforge_core::config::AuthConfig>,
1166) -> Router {
1167    use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
1168    use std::sync::Arc;
1169
1170    // If richer faker is available, register provider once (idempotent)
1171    #[cfg(feature = "data-faker")]
1172    {
1173        register_core_faker_provider();
1174    }
1175
1176    // Set up authentication state
1177    let spec = if let Some(spec_path) = &spec_path {
1178        match mockforge_core::openapi::OpenApiSpec::from_file(&spec_path).await {
1179            Ok(spec) => Some(Arc::new(spec)),
1180            Err(e) => {
1181                warn!("Failed to load OpenAPI spec for auth: {}", e);
1182                None
1183            }
1184        }
1185    } else {
1186        None
1187    };
1188
1189    // Create OAuth2 client if configured
1190    let oauth2_client = if let Some(auth_config) = &auth_config {
1191        if let Some(oauth2_config) = &auth_config.oauth2 {
1192            match create_oauth2_client(oauth2_config) {
1193                Ok(client) => Some(client),
1194                Err(e) => {
1195                    warn!("Failed to create OAuth2 client: {}", e);
1196                    None
1197                }
1198            }
1199        } else {
1200            None
1201        }
1202    } else {
1203        None
1204    };
1205
1206    let auth_state = AuthState {
1207        config: auth_config.unwrap_or_default(),
1208        spec,
1209        oauth2_client,
1210        introspection_cache: Arc::new(RwLock::new(HashMap::new())),
1211    };
1212
1213    // Set up the basic router with auth state
1214    let mut app = Router::new().with_state(auth_state.clone());
1215
1216    // If an OpenAPI spec is provided, integrate it
1217    if let Some(spec_path) = spec_path {
1218        match OpenApiSpec::from_file(&spec_path).await {
1219            Ok(openapi) => {
1220                info!("Loaded OpenAPI spec from {}", spec_path);
1221                let registry = if let Some(opts) = options {
1222                    OpenApiRouteRegistry::new_with_options(openapi, opts)
1223                } else {
1224                    OpenApiRouteRegistry::new_with_env(openapi)
1225                };
1226
1227                app = registry.build_router();
1228            }
1229            Err(e) => {
1230                warn!("Failed to load OpenAPI spec from {}: {}. Starting without OpenAPI integration.", spec_path, e);
1231            }
1232        }
1233    }
1234
1235    // Add basic health check endpoint
1236    app = app.route(
1237        "/health",
1238        axum::routing::get(|| async {
1239            use mockforge_core::server_utils::health::HealthStatus;
1240            {
1241                // HealthStatus should always serialize, but handle errors gracefully
1242                match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
1243                    Ok(value) => axum::Json(value),
1244                    Err(e) => {
1245                        // Log error but return a simple healthy response
1246                        tracing::error!("Failed to serialize health status: {}", e);
1247                        axum::Json(serde_json::json!({
1248                            "status": "healthy",
1249                            "service": "mockforge-http",
1250                            "uptime_seconds": 0
1251                        }))
1252                    }
1253                }
1254            }
1255        }),
1256    )
1257    // Add SSE endpoints
1258    .merge(sse::sse_router())
1259    // Add file serving endpoints for generated mock files
1260    .merge(file_server::file_serving_router())
1261    // Add authentication middleware (before logging)
1262    .layer(axum::middleware::from_fn_with_state(auth_state.clone(), auth_middleware))
1263    // Add request logging middleware
1264    .layer(axum::middleware::from_fn(request_logging::log_http_requests));
1265
1266    app
1267}
1268
1269/// Serve a provided router on the given port.
1270pub async fn serve_router(
1271    port: u16,
1272    app: Router,
1273) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1274    serve_router_with_tls(port, app, None).await
1275}
1276
1277/// Serve a provided router on the given port with optional TLS support.
1278pub async fn serve_router_with_tls(
1279    port: u16,
1280    app: Router,
1281    tls_config: Option<mockforge_core::config::HttpTlsConfig>,
1282) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1283    use std::net::SocketAddr;
1284
1285    let addr = mockforge_core::wildcard_socket_addr(port);
1286
1287    if let Some(ref tls) = tls_config {
1288        if tls.enabled {
1289            info!("HTTPS listening on {}", addr);
1290            return serve_with_tls(addr, app, tls).await;
1291        }
1292    }
1293
1294    info!("HTTP listening on {}", addr);
1295
1296    let listener = tokio::net::TcpListener::bind(addr).await.map_err(|e| {
1297        format!(
1298            "Failed to bind HTTP server to port {}: {}\n\
1299             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 {}",
1300            port, e, port, port
1301        )
1302    })?;
1303
1304    axum::serve(listener, app.into_make_service_with_connect_info::<SocketAddr>()).await?;
1305    Ok(())
1306}
1307
1308/// Serve router with TLS/HTTPS support
1309///
1310/// Note: This is a simplified implementation. For production use, consider using
1311/// a reverse proxy (nginx) for TLS termination, or use axum-server crate.
1312/// This implementation validates TLS configuration but recommends using a reverse proxy.
1313async fn serve_with_tls(
1314    addr: std::net::SocketAddr,
1315    _app: Router,
1316    tls_config: &mockforge_core::config::HttpTlsConfig,
1317) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1318    // Validate TLS configuration by attempting to load certificates
1319    let _acceptor = tls::load_tls_acceptor(tls_config)?;
1320
1321    // For now, return an informative error suggesting reverse proxy usage
1322    // Full TLS implementation with axum requires axum-server or similar
1323    Err(format!(
1324        "TLS/HTTPS support is configured but requires a reverse proxy (nginx) for production use.\n\
1325         Certificate validation passed: {} and {}\n\
1326         For native TLS support, please use a reverse proxy or wait for axum-server integration.\n\
1327         You can configure nginx with TLS termination pointing to the HTTP server on port {}.",
1328        tls_config.cert_file,
1329        tls_config.key_file,
1330        addr.port()
1331    )
1332    .into())
1333}
1334
1335/// Backwards-compatible start that builds + serves the base router.
1336pub async fn start(
1337    port: u16,
1338    spec_path: Option<String>,
1339    options: Option<ValidationOptions>,
1340) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1341    start_with_latency(port, spec_path, options, None).await
1342}
1343
1344/// Start HTTP server with authentication and latency support
1345pub async fn start_with_auth_and_latency(
1346    port: u16,
1347    spec_path: Option<String>,
1348    options: Option<ValidationOptions>,
1349    auth_config: Option<mockforge_core::config::AuthConfig>,
1350    latency_profile: Option<LatencyProfile>,
1351) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1352    start_with_auth_and_injectors(port, spec_path, options, auth_config, latency_profile, None)
1353        .await
1354}
1355
1356/// Start HTTP server with authentication and injectors support
1357pub async fn start_with_auth_and_injectors(
1358    port: u16,
1359    spec_path: Option<String>,
1360    options: Option<ValidationOptions>,
1361    auth_config: Option<mockforge_core::config::AuthConfig>,
1362    _latency_profile: Option<LatencyProfile>,
1363    _failure_injector: Option<mockforge_core::FailureInjector>,
1364) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1365    // For now, ignore latency and failure injectors and just use auth
1366    let app = build_router_with_auth(spec_path, options, auth_config).await;
1367    serve_router(port, app).await
1368}
1369
1370/// Start HTTP server with latency injection support
1371pub async fn start_with_latency(
1372    port: u16,
1373    spec_path: Option<String>,
1374    options: Option<ValidationOptions>,
1375    latency_profile: Option<LatencyProfile>,
1376) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
1377    let latency_injector =
1378        latency_profile.map(|profile| LatencyInjector::new(profile, Default::default()));
1379
1380    let app = build_router_with_latency(spec_path, options, latency_injector).await;
1381    serve_router(port, app).await
1382}
1383
1384/// Build the base HTTP router with chaining support
1385pub async fn build_router_with_chains(
1386    spec_path: Option<String>,
1387    options: Option<ValidationOptions>,
1388    circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1389) -> Router {
1390    build_router_with_chains_and_multi_tenant(
1391        spec_path,
1392        options,
1393        circling_config,
1394        None,
1395        None,
1396        None,
1397        None,
1398        None,
1399        None,
1400        None,
1401        false,
1402        None, // health_manager
1403        None, // mockai
1404        None, // deceptive_deploy_config
1405        None, // proxy_config
1406    )
1407    .await
1408}
1409
1410/// Build the base HTTP router with chaining and multi-tenant support
1411#[allow(clippy::too_many_arguments)]
1412pub async fn build_router_with_chains_and_multi_tenant(
1413    spec_path: Option<String>,
1414    options: Option<ValidationOptions>,
1415    _circling_config: Option<mockforge_core::request_chaining::ChainConfig>,
1416    multi_tenant_config: Option<mockforge_core::MultiTenantConfig>,
1417    route_configs: Option<Vec<mockforge_core::config::RouteConfig>>,
1418    cors_config: Option<mockforge_core::config::HttpCorsConfig>,
1419    _ai_generator: Option<
1420        std::sync::Arc<dyn mockforge_core::openapi::response::AiGenerator + Send + Sync>,
1421    >,
1422    smtp_registry: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
1423    mqtt_broker: Option<std::sync::Arc<dyn std::any::Any + Send + Sync>>,
1424    traffic_shaper: Option<mockforge_core::traffic_shaping::TrafficShaper>,
1425    traffic_shaping_enabled: bool,
1426    health_manager: Option<std::sync::Arc<health::HealthManager>>,
1427    _mockai: Option<
1428        std::sync::Arc<tokio::sync::RwLock<mockforge_core::intelligent_behavior::MockAI>>,
1429    >,
1430    deceptive_deploy_config: Option<mockforge_core::config::DeceptiveDeployConfig>,
1431    proxy_config: Option<mockforge_core::proxy::config::ProxyConfig>,
1432) -> Router {
1433    use crate::latency_profiles::LatencyProfiles;
1434    use crate::op_middleware::Shared;
1435    use mockforge_core::Overrides;
1436
1437    // Extract template expansion setting before options is moved (used in OpenAPI routes and custom routes)
1438    let template_expand = options.as_ref()
1439        .map(|o| o.response_template_expand)
1440        .unwrap_or_else(|| {
1441            std::env::var("MOCKFORGE_RESPONSE_TEMPLATE_EXPAND")
1442                .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1443                .unwrap_or(false)
1444        });
1445
1446    let _shared = Shared {
1447        profiles: LatencyProfiles::default(),
1448        overrides: Overrides::default(),
1449        failure_injector: None,
1450        traffic_shaper,
1451        overrides_enabled: false,
1452        traffic_shaping_enabled,
1453    };
1454
1455    // Start with basic router
1456    let mut app = Router::new();
1457    let mut include_default_health = true;
1458
1459    // If an OpenAPI spec is provided, integrate it
1460    if let Some(ref spec) = spec_path {
1461        match OpenApiSpec::from_file(&spec).await {
1462            Ok(openapi) => {
1463                info!("Loaded OpenAPI spec from {}", spec);
1464
1465                // Try to load persona from config if available
1466                let persona = load_persona_from_config().await;
1467
1468                let mut registry = if let Some(opts) = options {
1469                    tracing::debug!("Using custom validation options");
1470                    if let Some(ref persona) = persona {
1471                        tracing::info!("Using persona '{}' for route generation", persona.name);
1472                    }
1473                    OpenApiRouteRegistry::new_with_options_and_persona(openapi, opts, persona)
1474                } else {
1475                    tracing::debug!("Using environment-based options");
1476                    if let Some(ref persona) = persona {
1477                        tracing::info!("Using persona '{}' for route generation", persona.name);
1478                    }
1479                    OpenApiRouteRegistry::new_with_env_and_persona(openapi, persona)
1480                };
1481
1482                // Load custom fixtures if enabled
1483                let fixtures_dir = std::env::var("MOCKFORGE_FIXTURES_DIR")
1484                    .unwrap_or_else(|_| "/app/fixtures".to_string());
1485                let custom_fixtures_enabled = std::env::var("MOCKFORGE_CUSTOM_FIXTURES_ENABLED")
1486                    .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
1487                    .unwrap_or(true); // Enabled by default
1488
1489                if custom_fixtures_enabled {
1490                    use mockforge_core::CustomFixtureLoader;
1491                    use std::path::PathBuf;
1492                    use std::sync::Arc;
1493
1494                    let fixtures_path = PathBuf::from(&fixtures_dir);
1495                    let mut custom_loader = CustomFixtureLoader::new(fixtures_path, true);
1496
1497                    if let Err(e) = custom_loader.load_fixtures().await {
1498                        tracing::warn!("Failed to load custom fixtures: {}", e);
1499                    } else {
1500                        tracing::info!("Custom fixtures loaded from {}", fixtures_dir);
1501                        registry = registry.with_custom_fixture_loader(Arc::new(custom_loader));
1502                    }
1503                }
1504
1505                if registry
1506                    .routes()
1507                    .iter()
1508                    .any(|route| route.method == "GET" && route.path == "/health")
1509                {
1510                    include_default_health = false;
1511                }
1512                // Use MockAI if available, otherwise use standard router
1513                let spec_router = if let Some(ref mockai_instance) = _mockai {
1514                    tracing::debug!("Building router with MockAI support");
1515                    registry.build_router_with_mockai(Some(mockai_instance.clone()))
1516                } else {
1517                    registry.build_router()
1518                };
1519                app = app.merge(spec_router);
1520            }
1521            Err(e) => {
1522                warn!("Failed to load OpenAPI spec from {:?}: {}. Starting without OpenAPI integration.", spec_path, e);
1523            }
1524        }
1525    }
1526
1527    // Helper function to recursively expand templates in JSON values
1528    fn expand_templates_in_json(value: &serde_json::Value, context: &mockforge_core::ai_response::RequestContext) -> serde_json::Value {
1529        use mockforge_core::ai_response::expand_prompt_template;
1530        use serde_json::Value;
1531
1532        match value {
1533            Value::String(s) => {
1534                // Normalize {{request.query.name}} to {{query.name}} format
1535                let normalized = s
1536                    .replace("{{request.query.", "{{query.")
1537                    .replace("{{request.path.", "{{path.")
1538                    .replace("{{request.headers.", "{{headers.")
1539                    .replace("{{request.body.", "{{body.")
1540                    .replace("{{request.method}}", "{{method}}")
1541                    .replace("{{request.path}}", "{{path}}");
1542
1543                // Handle || operator: extract template part and default value separately
1544                // Pattern: "Hello {{query.name || \"world\"}}" -> extract "Hello {{query.name}}" and "world"
1545                let (template_part, default_value) = if normalized.contains("||") {
1546                    // Find the template part before || and default after ||
1547                    // Pattern: "Hello {{query.name || \"world\"}}"
1548                    // We need to find {{... || "..."}} and split it
1549                    if let Some(open_idx) = normalized.find("{{") {
1550                        if let Some(close_idx) = normalized[open_idx..].find("}}") {
1551                            let template_block = &normalized[open_idx..open_idx+close_idx+2];
1552                            if let Some(pipe_idx) = template_block.find("||") {
1553                                // Split: "{{query.name || \"world\"}}" -> "{{query.name " and " \"world\"}}"
1554                                let before_pipe = &template_block[..pipe_idx].trim();
1555                                let after_pipe = &template_block[pipe_idx+2..].trim();
1556
1557                                // Extract template variable name (remove {{ and trim)
1558                                let template_var = before_pipe.trim_start_matches("{{").trim();
1559                                // Replace the entire template block with just the template variable
1560                                let replacement = format!("{{{{{}}}}}}}", template_var);
1561                                let template = normalized.replace(template_block, &replacement);
1562
1563                                // Extract default value: " \"world\"}}" -> "world"
1564                                let mut default = after_pipe.trim_end_matches("}}").trim().to_string();
1565                                // Remove quotes
1566                                default = default.trim_matches('"').trim_matches('\'').trim_matches('\\').to_string();
1567                                default = default.trim().to_string();
1568
1569                                (template, Some(default))
1570                            } else {
1571                                (normalized, None)
1572                            }
1573                        } else {
1574                            (normalized, None)
1575                        }
1576                    } else {
1577                        (normalized, None)
1578                    }
1579                } else {
1580                    (normalized, None)
1581                };
1582
1583                // Expand the template part
1584                let mut expanded = expand_prompt_template(&template_part, context);
1585
1586                // If template wasn't fully expanded and we have a default, use default
1587                // Otherwise use the expanded value
1588                let final_expanded = if (expanded.contains("{{query.") || expanded.contains("{{path.") || expanded.contains("{{headers."))
1589                    && default_value.is_some() {
1590                    default_value.unwrap()
1591                } else {
1592                    // Clean up any stray closing braces that might remain
1593                    // This can happen if template replacement left partial braces
1594                    while expanded.ends_with('}') && !expanded.ends_with("}}") {
1595                        expanded.pop();
1596                    }
1597                    expanded
1598                };
1599
1600                Value::String(final_expanded)
1601            }
1602            Value::Array(arr) => {
1603                Value::Array(arr.iter().map(|v| expand_templates_in_json(v, context)).collect())
1604            }
1605            Value::Object(obj) => {
1606                let mut new_obj = serde_json::Map::new();
1607                for (k, v) in obj {
1608                    new_obj.insert(k.clone(), expand_templates_in_json(v, context));
1609                }
1610                Value::Object(new_obj)
1611            }
1612            _ => value.clone(),
1613        }
1614    }
1615
1616    // Register custom routes from config
1617    if let Some(route_configs) = route_configs {
1618        use axum::http::StatusCode;
1619        use axum::response::IntoResponse;
1620
1621        if !route_configs.is_empty() {
1622            info!("Registering {} custom route(s) from config", route_configs.len());
1623        }
1624
1625        for route_config in route_configs {
1626            let status = route_config.response.status;
1627            let body = route_config.response.body.clone();
1628            let headers = route_config.response.headers.clone();
1629            let path = route_config.path.clone();
1630            let method = route_config.method.clone();
1631            let latency_config = route_config.latency.clone();
1632
1633            // Create handler that returns the configured response with template expansion
1634            // Supports both basic templates ({{uuid}}, {{now}}) and request-aware templates
1635            // ({{request.query.name}}, {{request.path.id}}, {{request.headers.name}})
1636            // Register route using `any()` since we need full Request access for template expansion
1637            let expected_method = method.to_uppercase();
1638            app = app.route(&path, axum::routing::any(move |req: axum::http::Request<axum::body::Body>| {
1639                let body = body.clone();
1640                let headers = headers.clone();
1641                let expand = template_expand;
1642                let latency = latency_config.clone();
1643                let expected = expected_method.clone();
1644                let status_code = status;
1645
1646                async move {
1647                    // Check if request method matches expected method
1648                    if req.method().as_str() != expected.as_str() {
1649                        // Return 405 Method Not Allowed for wrong method
1650                        return axum::response::Response::builder()
1651                            .status(axum::http::StatusCode::METHOD_NOT_ALLOWED)
1652                            .header("Allow", &expected)
1653                            .body(axum::body::Body::empty())
1654                            .unwrap()
1655                            .into_response();
1656                    }
1657
1658                    // Apply latency injection if configured
1659                    // Calculate delay before any await to avoid Send issues
1660                    let delay_ms = if let Some(ref lat) = latency {
1661                        if lat.enabled {
1662                            use rand::{rng, Rng};
1663
1664                            // Check probability - generate all random values before await
1665                            let mut rng = rng();
1666                            let roll: f64 = rng.random();
1667
1668                            if roll < lat.probability {
1669                                if let Some(fixed) = lat.fixed_delay_ms {
1670                                    // Fixed delay with optional jitter
1671                                    let jitter = (fixed as f64 * lat.jitter_percent / 100.0) as u64;
1672                                    let jitter_amount = if jitter > 0 {
1673                                        rng.random_range(0..=jitter)
1674                                    } else {
1675                                        0
1676                                    };
1677                                    Some(fixed + jitter_amount)
1678                                } else if let Some((min, max)) = lat.random_delay_range_ms {
1679                                    // Random delay range
1680                                    Some(rng.random_range(min..=max))
1681                                } else {
1682                                    // Default to 0 if no delay specified
1683                                    Some(0)
1684                                }
1685                            } else {
1686                                None
1687                            }
1688                        } else {
1689                            None
1690                        }
1691                    } else {
1692                        None
1693                    };
1694
1695                    // Apply delay if calculated (after all random generation is done)
1696                    if let Some(delay) = delay_ms {
1697                        if delay > 0 {
1698                            use tokio::time::{sleep, Duration};
1699                            sleep(Duration::from_millis(delay)).await;
1700                        }
1701                    }
1702
1703                    // Create JSON response from body, or empty object if None
1704                    let mut body_value = body.unwrap_or(serde_json::json!({}));
1705
1706                    // Apply template expansion if enabled
1707                    if expand {
1708                        use mockforge_core::ai_response::RequestContext;
1709                        use std::collections::HashMap;
1710                        use serde_json::Value;
1711
1712                        // Extract request data for template expansion
1713                        let method = req.method().to_string();
1714                        let path = req.uri().path().to_string();
1715
1716                        // Extract query parameters
1717                        let query_params: HashMap<String, Value> = req
1718                            .uri()
1719                            .query()
1720                            .map(|q| {
1721                                url::form_urlencoded::parse(q.as_bytes())
1722                                    .into_owned()
1723                                    .map(|(k, v)| (k, Value::String(v)))
1724                                    .collect()
1725                            })
1726                            .unwrap_or_default();
1727
1728                        // Extract headers
1729                        let request_headers: HashMap<String, Value> = req
1730                            .headers()
1731                            .iter()
1732                            .filter_map(|(name, value)| {
1733                                value.to_str().ok().map(|v| {
1734                                    (name.to_string(), Value::String(v.to_string()))
1735                                })
1736                            })
1737                            .collect();
1738
1739                        // Note: Request body extraction for {{request.body.field}} would go here
1740                        // For now, we skip it to avoid consuming the body
1741
1742                        // Build request context
1743                        let context = RequestContext::new(method.clone(), path.clone())
1744                            .with_query_params(query_params)
1745                            .with_headers(request_headers);
1746
1747                        // Recursively expand templates in JSON structure
1748                        body_value = expand_templates_in_json(&body_value, &context);
1749                    }
1750
1751                    let mut response = axum::Json(body_value).into_response();
1752
1753                    // Set status code
1754                    *response.status_mut() = StatusCode::from_u16(status_code)
1755                        .unwrap_or(StatusCode::OK);
1756
1757                    // Add custom headers
1758                    for (key, value) in headers {
1759                        if let Ok(header_name) = axum::http::HeaderName::from_bytes(key.as_bytes()) {
1760                            if let Ok(header_value) = axum::http::HeaderValue::from_str(&value) {
1761                                response.headers_mut().insert(header_name, header_value);
1762                            }
1763                        }
1764                    }
1765
1766                    response
1767                }
1768            }));
1769
1770            debug!("Registered route: {} {}", method, path);
1771        }
1772    }
1773
1774    // Add health check endpoints
1775    if let Some(health) = health_manager {
1776        // Use comprehensive health check router with all probe endpoints
1777        app = app.merge(health::health_router(health));
1778        info!(
1779            "Health check endpoints enabled: /health, /health/live, /health/ready, /health/startup"
1780        );
1781    } else if include_default_health {
1782        // Fallback to basic health endpoint for backwards compatibility
1783        app = app.route(
1784            "/health",
1785            axum::routing::get(|| async {
1786                use mockforge_core::server_utils::health::HealthStatus;
1787                {
1788                    // HealthStatus should always serialize, but handle errors gracefully
1789                    match serde_json::to_value(HealthStatus::healthy(0, "mockforge-http")) {
1790                        Ok(value) => axum::Json(value),
1791                        Err(e) => {
1792                            // Log error but return a simple healthy response
1793                            tracing::error!("Failed to serialize health status: {}", e);
1794                            axum::Json(serde_json::json!({
1795                                "status": "healthy",
1796                                "service": "mockforge-http",
1797                                "uptime_seconds": 0
1798                            }))
1799                        }
1800                    }
1801                }
1802            }),
1803        );
1804    }
1805
1806    app = app.merge(sse::sse_router());
1807    // Add file serving endpoints for generated mock files
1808    app = app.merge(file_server::file_serving_router());
1809
1810    // Add management API endpoints
1811    let mut management_state = ManagementState::new(None, spec_path, 3000); // Port will be updated when we know the actual port
1812
1813    // Create WebSocket state and connect it to management state
1814    use std::sync::Arc;
1815    let ws_state = WsManagementState::new();
1816    let ws_broadcast = Arc::new(ws_state.tx.clone());
1817    let management_state = management_state.with_ws_broadcast(ws_broadcast);
1818
1819    // Add proxy config to management state if available
1820    let management_state = if let Some(proxy_cfg) = proxy_config {
1821        use tokio::sync::RwLock;
1822        let proxy_config_arc = Arc::new(RwLock::new(proxy_cfg));
1823        management_state.with_proxy_config(proxy_config_arc)
1824    } else {
1825        management_state
1826    };
1827
1828    #[cfg(feature = "smtp")]
1829    let management_state = {
1830        if let Some(smtp_reg) = smtp_registry {
1831            match smtp_reg.downcast::<mockforge_smtp::SmtpSpecRegistry>() {
1832                Ok(smtp_reg) => management_state.with_smtp_registry(smtp_reg),
1833                Err(e) => {
1834                    error!(
1835                        "Invalid SMTP registry type passed to HTTP management state: {:?}",
1836                        e.type_id()
1837                    );
1838                    management_state
1839                }
1840            }
1841        } else {
1842            management_state
1843        }
1844    };
1845    #[cfg(not(feature = "smtp"))]
1846    let management_state = {
1847        let _ = smtp_registry;
1848        management_state
1849    };
1850    #[cfg(feature = "mqtt")]
1851    let management_state = {
1852        if let Some(broker) = mqtt_broker {
1853            match broker.downcast::<mockforge_mqtt::MqttBroker>() {
1854                Ok(broker) => management_state.with_mqtt_broker(broker),
1855                Err(e) => {
1856                    error!(
1857                        "Invalid MQTT broker passed to HTTP management state: {:?}",
1858                        e.type_id()
1859                    );
1860                    management_state
1861                }
1862            }
1863        } else {
1864            management_state
1865        }
1866    };
1867    #[cfg(not(feature = "mqtt"))]
1868    let management_state = {
1869        let _ = mqtt_broker;
1870        management_state
1871    };
1872    app = app.nest("/__mockforge/api", management_router(management_state));
1873
1874    // Add verification API endpoint
1875    app = app.merge(verification_router());
1876
1877    // Add OIDC well-known endpoints
1878    use crate::auth::oidc::oidc_router;
1879    app = app.merge(oidc_router());
1880
1881    // Add access review API if enabled
1882    {
1883        use mockforge_core::security::get_global_access_review_service;
1884        if let Some(service) = get_global_access_review_service().await {
1885            use crate::handlers::access_review::{access_review_router, AccessReviewState};
1886            let review_state = AccessReviewState { service };
1887            app = app.nest("/api/v1/security/access-reviews", access_review_router(review_state));
1888            debug!("Access review API mounted at /api/v1/security/access-reviews");
1889        }
1890    }
1891
1892    // Add privileged access API if enabled
1893    {
1894        use mockforge_core::security::get_global_privileged_access_manager;
1895        if let Some(manager) = get_global_privileged_access_manager().await {
1896            use crate::handlers::privileged_access::{privileged_access_router, PrivilegedAccessState};
1897            let privileged_state = PrivilegedAccessState { manager };
1898            app = app.nest("/api/v1/security/privileged-access", privileged_access_router(privileged_state));
1899            debug!("Privileged access API mounted at /api/v1/security/privileged-access");
1900        }
1901    }
1902
1903    // Add change management API if enabled
1904    {
1905        use mockforge_core::security::get_global_change_management_engine;
1906        if let Some(engine) = get_global_change_management_engine().await {
1907            use crate::handlers::change_management::{change_management_router, ChangeManagementState};
1908            let change_state = ChangeManagementState { engine };
1909            app = app.nest("/api/v1/change-management", change_management_router(change_state));
1910            debug!("Change management API mounted at /api/v1/change-management");
1911        }
1912    }
1913
1914    // Add risk assessment API if enabled
1915    {
1916        use mockforge_core::security::get_global_risk_assessment_engine;
1917        if let Some(engine) = get_global_risk_assessment_engine().await {
1918            use crate::handlers::risk_assessment::{risk_assessment_router, RiskAssessmentState};
1919            let risk_state = RiskAssessmentState { engine };
1920            app = app.nest("/api/v1/security", risk_assessment_router(risk_state));
1921            debug!("Risk assessment API mounted at /api/v1/security/risks");
1922        }
1923    }
1924
1925    // Add token lifecycle API
1926    {
1927        use crate::auth::token_lifecycle::TokenLifecycleManager;
1928        use crate::handlers::token_lifecycle::{token_lifecycle_router, TokenLifecycleState};
1929        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1930        let lifecycle_state = TokenLifecycleState {
1931            manager: lifecycle_manager,
1932        };
1933        app = app.nest("/api/v1/auth", token_lifecycle_router(lifecycle_state));
1934        debug!("Token lifecycle API mounted at /api/v1/auth");
1935    }
1936
1937    // Add OAuth2 server endpoints
1938    {
1939        use crate::auth::oidc::OidcState;
1940        use crate::auth::token_lifecycle::TokenLifecycleManager;
1941        use crate::handlers::oauth2_server::{oauth2_server_router, OAuth2ServerState};
1942        let oidc_state = Arc::new(RwLock::new(None::<OidcState>)); // TODO: Load from config
1943        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1944        let oauth2_state = OAuth2ServerState {
1945            oidc_state,
1946            lifecycle_manager,
1947            auth_codes: Arc::new(RwLock::new(HashMap::new())),
1948        };
1949        app = app.merge(oauth2_server_router(oauth2_state));
1950        debug!("OAuth2 server endpoints mounted at /oauth2/authorize and /oauth2/token");
1951    }
1952
1953    // Add consent screen endpoints
1954    {
1955        use crate::auth::risk_engine::RiskEngine;
1956        use crate::auth::token_lifecycle::TokenLifecycleManager;
1957        use crate::handlers::consent::{consent_router, ConsentState};
1958        use crate::handlers::oauth2_server::OAuth2ServerState;
1959        use crate::auth::oidc::OidcState;
1960        let oidc_state = Arc::new(RwLock::new(None::<OidcState>)); // TODO: Load from config
1961        let lifecycle_manager = Arc::new(TokenLifecycleManager::default());
1962        let oauth2_state = OAuth2ServerState {
1963            oidc_state: oidc_state.clone(),
1964            lifecycle_manager: lifecycle_manager.clone(),
1965            auth_codes: Arc::new(RwLock::new(HashMap::new())),
1966        };
1967        let risk_engine = Arc::new(RiskEngine::default());
1968        let consent_state = ConsentState {
1969            oauth2_state,
1970            risk_engine,
1971        };
1972        app = app.merge(consent_router(consent_state));
1973        debug!("Consent screen endpoints mounted at /consent");
1974    }
1975
1976    // Add risk simulation API
1977    {
1978        use crate::auth::risk_engine::RiskEngine;
1979        use crate::handlers::risk_simulation::{risk_simulation_router, RiskSimulationState};
1980        let risk_engine = Arc::new(RiskEngine::default());
1981        let risk_state = RiskSimulationState { risk_engine };
1982        app = app.nest("/api/v1/auth", risk_simulation_router(risk_state));
1983        debug!("Risk simulation API mounted at /api/v1/auth/risk");
1984    }
1985
1986    // Add management WebSocket endpoint
1987    app = app.nest("/__mockforge/ws", ws_management_router(ws_state));
1988
1989    // Add workspace routing middleware if multi-tenant is enabled
1990    if let Some(mt_config) = multi_tenant_config {
1991        if mt_config.enabled {
1992            use mockforge_core::{MultiTenantWorkspaceRegistry, WorkspaceRouter};
1993            use std::sync::Arc;
1994
1995            info!(
1996                "Multi-tenant mode enabled with {} routing strategy",
1997                match mt_config.routing_strategy {
1998                    mockforge_core::RoutingStrategy::Path => "path-based",
1999                    mockforge_core::RoutingStrategy::Port => "port-based",
2000                    mockforge_core::RoutingStrategy::Both => "hybrid",
2001                }
2002            );
2003
2004            // Create the multi-tenant workspace registry
2005            let mut registry = MultiTenantWorkspaceRegistry::new(mt_config.clone());
2006
2007            // Register the default workspace before wrapping in Arc
2008            let default_workspace =
2009                mockforge_core::Workspace::new(mt_config.default_workspace.clone());
2010            if let Err(e) =
2011                registry.register_workspace(mt_config.default_workspace.clone(), default_workspace)
2012            {
2013                warn!("Failed to register default workspace: {}", e);
2014            } else {
2015                info!("Registered default workspace: '{}'", mt_config.default_workspace);
2016            }
2017
2018            // Wrap registry in Arc for shared access
2019            let registry = Arc::new(registry);
2020
2021            // Create workspace router
2022            let _workspace_router = WorkspaceRouter::new(registry);
2023            info!("Workspace routing middleware initialized for HTTP server");
2024        }
2025    }
2026
2027    // Apply deceptive deploy configuration if enabled
2028    let mut final_cors_config = cors_config;
2029    let mut production_headers: Option<std::sync::Arc<std::collections::HashMap<String, String>>> =
2030        None;
2031    // Auth config from deceptive deploy OAuth (if configured)
2032    let mut deceptive_deploy_auth_config: Option<mockforge_core::config::AuthConfig> = None;
2033    let mut rate_limit_config = crate::middleware::RateLimitConfig {
2034        requests_per_minute: std::env::var("MOCKFORGE_RATE_LIMIT_RPM")
2035            .ok()
2036            .and_then(|v| v.parse().ok())
2037            .unwrap_or(1000),
2038        burst: std::env::var("MOCKFORGE_RATE_LIMIT_BURST")
2039            .ok()
2040            .and_then(|v| v.parse().ok())
2041            .unwrap_or(2000),
2042        per_ip: true,
2043        per_endpoint: false,
2044    };
2045
2046    if let Some(deploy_config) = &deceptive_deploy_config {
2047        if deploy_config.enabled {
2048            info!("Deceptive deploy mode enabled - applying production-like configuration");
2049
2050            // Override CORS config if provided
2051            if let Some(prod_cors) = &deploy_config.cors {
2052                final_cors_config = Some(mockforge_core::config::HttpCorsConfig {
2053                    enabled: true,
2054                    allowed_origins: prod_cors.allowed_origins.clone(),
2055                    allowed_methods: prod_cors.allowed_methods.clone(),
2056                    allowed_headers: prod_cors.allowed_headers.clone(),
2057                    allow_credentials: prod_cors.allow_credentials,
2058                });
2059                info!("Applied production-like CORS configuration");
2060            }
2061
2062            // Override rate limit config if provided
2063            if let Some(prod_rate_limit) = &deploy_config.rate_limit {
2064                rate_limit_config = crate::middleware::RateLimitConfig {
2065                    requests_per_minute: prod_rate_limit.requests_per_minute,
2066                    burst: prod_rate_limit.burst,
2067                    per_ip: prod_rate_limit.per_ip,
2068                    per_endpoint: false,
2069                };
2070                info!(
2071                    "Applied production-like rate limiting: {} req/min, burst: {}",
2072                    prod_rate_limit.requests_per_minute, prod_rate_limit.burst
2073                );
2074            }
2075
2076            // Set production headers
2077            if !deploy_config.headers.is_empty() {
2078                let headers_map: std::collections::HashMap<String, String> =
2079                    deploy_config.headers.clone();
2080                production_headers = Some(std::sync::Arc::new(headers_map));
2081                info!("Configured {} production headers", deploy_config.headers.len());
2082            }
2083
2084            // Integrate OAuth config from deceptive deploy
2085            if let Some(prod_oauth) = &deploy_config.oauth {
2086                let oauth2_config: mockforge_core::config::OAuth2Config = prod_oauth.clone().into();
2087                deceptive_deploy_auth_config = Some(mockforge_core::config::AuthConfig {
2088                    oauth2: Some(oauth2_config),
2089                    ..Default::default()
2090                });
2091                info!("Applied production-like OAuth configuration for deceptive deploy");
2092            }
2093        }
2094    }
2095
2096    // Initialize rate limiter and state
2097    let rate_limiter =
2098        std::sync::Arc::new(crate::middleware::GlobalRateLimiter::new(rate_limit_config.clone()));
2099
2100    let mut state = HttpServerState::new().with_rate_limiter(rate_limiter.clone());
2101
2102    // Add production headers to state if configured
2103    if let Some(headers) = production_headers.clone() {
2104        state = state.with_production_headers(headers);
2105    }
2106
2107    // Add rate limiting middleware
2108    app = app.layer(from_fn_with_state(state.clone(), crate::middleware::rate_limit_middleware));
2109
2110    // Add production headers middleware if configured
2111    if state.production_headers.is_some() {
2112        app = app.layer(from_fn_with_state(
2113            state.clone(),
2114            crate::middleware::production_headers_middleware,
2115        ));
2116    }
2117
2118    // Add authentication middleware if OAuth is configured via deceptive deploy
2119    if let Some(auth_config) = deceptive_deploy_auth_config {
2120        use crate::auth::{auth_middleware, create_oauth2_client, AuthState};
2121        use std::collections::HashMap;
2122        use std::sync::Arc;
2123        use tokio::sync::RwLock;
2124
2125        // Create OAuth2 client if configured
2126        let oauth2_client = if let Some(oauth2_config) = &auth_config.oauth2 {
2127            match create_oauth2_client(oauth2_config) {
2128                Ok(client) => Some(client),
2129                Err(e) => {
2130                    warn!("Failed to create OAuth2 client from deceptive deploy config: {}", e);
2131                    None
2132                }
2133            }
2134        } else {
2135            None
2136        };
2137
2138        // Create auth state
2139        let auth_state = AuthState {
2140            config: auth_config,
2141            spec: None, // OpenAPI spec not available in this context
2142            oauth2_client,
2143            introspection_cache: Arc::new(RwLock::new(HashMap::new())),
2144        };
2145
2146        // Apply auth middleware
2147        app = app.layer(axum::middleware::from_fn_with_state(auth_state, auth_middleware));
2148        info!("Applied OAuth authentication middleware from deceptive deploy configuration");
2149    }
2150
2151    // Add contract diff middleware for automatic request capture
2152    // This captures requests for contract diff analysis
2153    app = app.layer(axum::middleware::from_fn(contract_diff_middleware::capture_for_contract_diff));
2154
2155    // Add CORS middleware (use final_cors_config which may be overridden by deceptive deploy)
2156    app = apply_cors_middleware(app, final_cors_config);
2157
2158    app
2159}
2160
2161// Note: start_with_traffic_shaping function removed due to compilation issues
2162// Use build_router_with_traffic_shaping_and_multi_tenant directly instead
2163
2164#[test]
2165fn test_route_info_clone() {
2166    let route = RouteInfo {
2167        method: "POST".to_string(),
2168        path: "/users".to_string(),
2169        operation_id: Some("createUser".to_string()),
2170        summary: None,
2171        description: None,
2172        parameters: vec![],
2173    };
2174
2175    let cloned = route.clone();
2176    assert_eq!(route.method, cloned.method);
2177    assert_eq!(route.path, cloned.path);
2178    assert_eq!(route.operation_id, cloned.operation_id);
2179}
2180
2181#[test]
2182fn test_http_server_state_new() {
2183    let state = HttpServerState::new();
2184    assert_eq!(state.routes.len(), 0);
2185}
2186
2187#[test]
2188fn test_http_server_state_with_routes() {
2189    let routes = vec![
2190        RouteInfo {
2191            method: "GET".to_string(),
2192            path: "/users".to_string(),
2193            operation_id: Some("getUsers".to_string()),
2194            summary: None,
2195            description: None,
2196            parameters: vec![],
2197        },
2198        RouteInfo {
2199            method: "POST".to_string(),
2200            path: "/users".to_string(),
2201            operation_id: Some("createUser".to_string()),
2202            summary: None,
2203            description: None,
2204            parameters: vec![],
2205        },
2206    ];
2207
2208    let state = HttpServerState::with_routes(routes.clone());
2209    assert_eq!(state.routes.len(), 2);
2210    assert_eq!(state.routes[0].method, "GET");
2211    assert_eq!(state.routes[1].method, "POST");
2212}
2213
2214#[test]
2215fn test_http_server_state_clone() {
2216    let routes = vec![RouteInfo {
2217        method: "GET".to_string(),
2218        path: "/test".to_string(),
2219        operation_id: None,
2220        summary: None,
2221        description: None,
2222        parameters: vec![],
2223    }];
2224
2225    let state = HttpServerState::with_routes(routes);
2226    let cloned = state.clone();
2227
2228    assert_eq!(state.routes.len(), cloned.routes.len());
2229    assert_eq!(state.routes[0].method, cloned.routes[0].method);
2230}
2231
2232#[tokio::test]
2233async fn test_build_router_without_openapi() {
2234    let _router = build_router(None, None, None).await;
2235    // Should succeed without OpenAPI spec
2236}
2237
2238#[tokio::test]
2239async fn test_build_router_with_nonexistent_spec() {
2240    let _router = build_router(Some("/nonexistent/spec.yaml".to_string()), None, None).await;
2241    // Should succeed but log a warning
2242}
2243
2244#[tokio::test]
2245async fn test_build_router_with_auth_and_latency() {
2246    let _router = build_router_with_auth_and_latency(None, None, None, None).await;
2247    // Should succeed without parameters
2248}
2249
2250#[tokio::test]
2251async fn test_build_router_with_latency() {
2252    let _router = build_router_with_latency(None, None, None).await;
2253    // Should succeed without parameters
2254}
2255
2256#[tokio::test]
2257async fn test_build_router_with_auth() {
2258    let _router = build_router_with_auth(None, None, None).await;
2259    // Should succeed without parameters
2260}
2261
2262#[tokio::test]
2263async fn test_build_router_with_chains() {
2264    let _router = build_router_with_chains(None, None, None).await;
2265    // Should succeed without parameters
2266}
2267
2268#[test]
2269fn test_route_info_with_all_fields() {
2270    let route = RouteInfo {
2271        method: "PUT".to_string(),
2272        path: "/users/{id}".to_string(),
2273        operation_id: Some("updateUser".to_string()),
2274        summary: Some("Update user".to_string()),
2275        description: Some("Updates an existing user".to_string()),
2276        parameters: vec!["id".to_string(), "body".to_string()],
2277    };
2278
2279    assert!(route.operation_id.is_some());
2280    assert!(route.summary.is_some());
2281    assert!(route.description.is_some());
2282    assert_eq!(route.parameters.len(), 2);
2283}
2284
2285#[test]
2286fn test_route_info_with_minimal_fields() {
2287    let route = RouteInfo {
2288        method: "DELETE".to_string(),
2289        path: "/users/{id}".to_string(),
2290        operation_id: None,
2291        summary: None,
2292        description: None,
2293        parameters: vec![],
2294    };
2295
2296    assert!(route.operation_id.is_none());
2297    assert!(route.summary.is_none());
2298    assert!(route.description.is_none());
2299    assert_eq!(route.parameters.len(), 0);
2300}
2301
2302#[test]
2303fn test_http_server_state_empty_routes() {
2304    let state = HttpServerState::with_routes(vec![]);
2305    assert_eq!(state.routes.len(), 0);
2306}
2307
2308#[test]
2309fn test_http_server_state_multiple_routes() {
2310    let routes = vec![
2311        RouteInfo {
2312            method: "GET".to_string(),
2313            path: "/users".to_string(),
2314            operation_id: Some("listUsers".to_string()),
2315            summary: Some("List all users".to_string()),
2316            description: None,
2317            parameters: vec![],
2318        },
2319        RouteInfo {
2320            method: "GET".to_string(),
2321            path: "/users/{id}".to_string(),
2322            operation_id: Some("getUser".to_string()),
2323            summary: Some("Get a user".to_string()),
2324            description: None,
2325            parameters: vec!["id".to_string()],
2326        },
2327        RouteInfo {
2328            method: "POST".to_string(),
2329            path: "/users".to_string(),
2330            operation_id: Some("createUser".to_string()),
2331            summary: Some("Create a user".to_string()),
2332            description: None,
2333            parameters: vec!["body".to_string()],
2334        },
2335    ];
2336
2337    let state = HttpServerState::with_routes(routes);
2338    assert_eq!(state.routes.len(), 3);
2339
2340    // Verify different HTTP methods
2341    let methods: Vec<&str> = state.routes.iter().map(|r| r.method.as_str()).collect();
2342    assert!(methods.contains(&"GET"));
2343    assert!(methods.contains(&"POST"));
2344}
2345
2346#[test]
2347fn test_http_server_state_with_rate_limiter() {
2348    use std::sync::Arc;
2349
2350    let config = crate::middleware::RateLimitConfig::default();
2351    let rate_limiter = Arc::new(crate::middleware::GlobalRateLimiter::new(config));
2352
2353    let state = HttpServerState::new().with_rate_limiter(rate_limiter);
2354
2355    assert!(state.rate_limiter.is_some());
2356    assert_eq!(state.routes.len(), 0);
2357}
2358
2359#[tokio::test]
2360async fn test_build_router_includes_rate_limiter() {
2361    let _router = build_router(None, None, None).await;
2362    // Router should be created successfully with rate limiter initialized
2363}