Skip to main content

mockforge_http/
lib.rs

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