Skip to main content

mockforge_http/
lib.rs

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