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