1use crate::mock_generator::{MockDataGenerator, MockDataResult, MockGeneratorConfig, MockResponse};
7use crate::{Error, Result};
8use axum::{
9 http::{HeaderMap, StatusCode},
10 response::Json,
11 routing::get,
12 Router,
13};
14use serde_json::{json, Value};
15use std::collections::HashMap;
16use std::net::SocketAddr;
17use std::sync::Arc;
18use tokio::net::TcpListener;
19use tracing::info;
20
21#[derive(Debug, Clone)]
23pub struct MockServerConfig {
24 pub port: u16,
26 pub host: String,
28 pub openapi_spec: Value,
30 pub generator_config: MockGeneratorConfig,
32 pub enable_cors: bool,
34 pub response_delays: HashMap<String, u64>,
36 pub log_requests: bool,
38}
39
40impl Default for MockServerConfig {
41 fn default() -> Self {
42 Self {
43 port: 3000,
44 host: "127.0.0.1".to_string(),
45 openapi_spec: json!({}),
46 generator_config: MockGeneratorConfig::default(),
47 enable_cors: true,
48 response_delays: HashMap::new(),
49 log_requests: true,
50 }
51 }
52}
53
54impl MockServerConfig {
55 pub fn new(openapi_spec: Value) -> Self {
57 Self {
58 openapi_spec,
59 ..Default::default()
60 }
61 }
62
63 pub fn port(mut self, port: u16) -> Self {
65 self.port = port;
66 self
67 }
68
69 pub fn host(mut self, host: String) -> Self {
71 self.host = host;
72 self
73 }
74
75 pub fn generator_config(mut self, config: MockGeneratorConfig) -> Self {
77 self.generator_config = config;
78 self
79 }
80
81 pub fn enable_cors(mut self, enabled: bool) -> Self {
83 self.enable_cors = enabled;
84 self
85 }
86
87 pub fn response_delay(mut self, endpoint: String, delay_ms: u64) -> Self {
89 self.response_delays.insert(endpoint, delay_ms);
90 self
91 }
92
93 pub fn log_requests(mut self, enabled: bool) -> Self {
95 self.log_requests = enabled;
96 self
97 }
98}
99
100#[derive(Debug)]
102pub struct MockServer {
103 config: MockServerConfig,
105 mock_data: Arc<MockDataResult>,
107 handlers: HashMap<String, MockResponse>,
109}
110
111impl MockServer {
112 pub fn new(config: MockServerConfig) -> Result<Self> {
114 info!("Creating mock server with OpenAPI specification");
115
116 let mut generator = MockDataGenerator::with_config(config.generator_config.clone());
118 let mock_data = generator.generate_from_openapi_spec(&config.openapi_spec)?;
119
120 let mut handlers = HashMap::new();
122 for (endpoint, response) in &mock_data.responses {
123 handlers.insert(endpoint.clone(), response.clone());
124 }
125
126 Ok(Self {
127 config,
128 mock_data: Arc::new(mock_data),
129 handlers,
130 })
131 }
132
133 pub async fn start(self) -> Result<()> {
135 let config = self.config.clone();
136 let app = self.create_router();
137 let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
138
139 info!("Starting mock server on {}", addr);
140
141 let listener = TcpListener::bind(addr)
142 .await
143 .map_err(|e| Error::generic(format!("Failed to bind to {}: {}", addr, e)))?;
144
145 axum::serve(listener, app)
146 .await
147 .map_err(|e| Error::generic(format!("Server error: {}", e)))?;
148
149 Ok(())
150 }
151
152 fn create_router(self) -> Router {
154 let mock_data = Arc::clone(&self.mock_data);
155 let config = Arc::new(self.config);
156 let handlers = Arc::new(self.handlers);
157
158 let mut router = Router::new()
159 .route("/", get(Self::root_handler))
161 .route("/health", get(Self::health_handler))
162 .route("/openapi.json", get(Self::openapi_handler))
163 .route("/mock-data", get(Self::mock_data_handler))
164 .fallback(Self::generic_handler)
166 .with_state(MockServerState {
167 mock_data,
168 config: config.clone(),
169 handlers: handlers.clone(),
170 });
171
172 if config.enable_cors {
174 use tower_http::cors::CorsLayer;
175 router = router.layer(CorsLayer::permissive());
176 info!("CORS middleware enabled for mock server");
177 }
178
179 if config.log_requests {
181 router = router.layer(axum::middleware::from_fn(Self::request_logging_middleware));
182 }
183
184 router
185 }
186
187 async fn request_logging_middleware(
189 request: axum::http::Request<axum::body::Body>,
190 next: axum::middleware::Next,
191 ) -> axum::response::Response {
192 let method = request.method().clone();
193 let uri = request.uri().clone();
194 let start = std::time::Instant::now();
195
196 info!("Incoming request: {} {}", method, uri);
197
198 let response = next.run(request).await;
199
200 let duration = start.elapsed();
201 info!(
202 "Request completed: {} {} - Status: {} - Duration: {:?}",
203 method,
204 uri,
205 response.status(),
206 duration
207 );
208
209 response
210 }
211
212 async fn root_handler() -> Json<Value> {
214 Json(json!({
215 "name": "MockForge Mock Server",
216 "version": "1.0.0",
217 "description": "Mock server powered by MockForge",
218 "endpoints": {
219 "/health": "Health check endpoint",
220 "/openapi.json": "OpenAPI specification",
221 "/mock-data": "Generated mock data"
222 }
223 }))
224 }
225
226 async fn health_handler() -> Json<Value> {
228 Json(json!({
229 "status": "healthy",
230 "timestamp": chrono::Utc::now().to_rfc3339(),
231 "service": "mockforge-mock-server"
232 }))
233 }
234
235 async fn openapi_handler(
237 axum::extract::State(state): axum::extract::State<MockServerState>,
238 ) -> Json<Value> {
239 if let Some(delay) = state.config.response_delays.get("GET /openapi.json") {
241 tokio::time::sleep(tokio::time::Duration::from_millis(*delay)).await;
242 }
243
244 Json(serde_json::to_value(&state.mock_data.spec_info).unwrap_or(json!({})))
245 }
246
247 async fn mock_data_handler(
249 axum::extract::State(state): axum::extract::State<MockServerState>,
250 ) -> Json<Value> {
251 if let Some(delay) = state.config.response_delays.get("GET /mock-data") {
253 tokio::time::sleep(tokio::time::Duration::from_millis(*delay)).await;
254 }
255
256 let endpoint_key = "GET /mock-data";
258 if let Some(response) = state.handlers.get(endpoint_key) {
259 Json(response.body.clone())
260 } else {
261 Json(json!({
262 "schemas": state.mock_data.schemas,
263 "responses": state.mock_data.responses,
264 "warnings": state.mock_data.warnings
265 }))
266 }
267 }
268
269 async fn generic_handler(
274 axum::extract::State(state): axum::extract::State<MockServerState>,
275 method: axum::http::Method,
276 uri: axum::http::Uri,
277 _headers: HeaderMap,
278 ) -> std::result::Result<Json<Value>, StatusCode> {
279 let path = uri.path();
280 let endpoint_key = format!("{} {}", method.as_str().to_uppercase(), path);
281
282 if state.config.log_requests {
284 info!("Handling request: {}", endpoint_key);
285 }
286
287 if let Some(delay) = state.config.response_delays.get(&endpoint_key) {
289 tokio::time::sleep(tokio::time::Duration::from_millis(*delay)).await;
290 }
291
292 if let Some(response) = state.handlers.get(&endpoint_key) {
294 Ok(Json(response.body.clone()))
295 } else {
296 let similar_endpoint = state
298 .handlers
299 .keys()
300 .find(|key| Self::endpoints_match(key, &endpoint_key))
301 .cloned();
302
303 if let Some(endpoint) = similar_endpoint {
304 if let Some(response) = state.handlers.get(&endpoint) {
305 Ok(Json(response.body.clone()))
306 } else {
307 Err(StatusCode::NOT_FOUND)
308 }
309 } else {
310 let generic_response = json!({
312 "message": "Mock response",
313 "endpoint": endpoint_key,
314 "timestamp": chrono::Utc::now().to_rfc3339(),
315 "data": {}
316 });
317 Ok(Json(generic_response))
318 }
319 }
320 }
321
322 pub fn endpoints_match(pattern: &str, request: &str) -> bool {
326 let pattern_parts: Vec<&str> = pattern.split(' ').collect();
329 let request_parts: Vec<&str> = request.split(' ').collect();
330
331 if pattern_parts.len() != request_parts.len() {
332 return false;
333 }
334
335 for (pattern_part, request_part) in pattern_parts.iter().zip(request_parts.iter()) {
336 if pattern_part != request_part && !pattern_part.contains(":") {
337 return false;
338 }
339 }
340
341 true
342 }
343}
344
345#[derive(Debug, Clone)]
347struct MockServerState {
348 mock_data: Arc<MockDataResult>,
349 config: Arc<MockServerConfig>,
351 handlers: Arc<HashMap<String, MockResponse>>,
353}
354
355#[derive(Debug)]
357pub struct MockServerBuilder {
358 config: MockServerConfig,
359}
360
361impl MockServerBuilder {
362 pub fn new(openapi_spec: Value) -> Self {
364 Self {
365 config: MockServerConfig::new(openapi_spec),
366 }
367 }
368
369 pub fn port(mut self, port: u16) -> Self {
371 self.config = self.config.port(port);
372 self
373 }
374
375 pub fn host(mut self, host: String) -> Self {
377 self.config = self.config.host(host);
378 self
379 }
380
381 pub fn generator_config(mut self, config: MockGeneratorConfig) -> Self {
383 self.config = self.config.generator_config(config);
384 self
385 }
386
387 pub fn enable_cors(mut self, enabled: bool) -> Self {
389 self.config = self.config.enable_cors(enabled);
390 self
391 }
392
393 pub fn response_delay(mut self, endpoint: String, delay_ms: u64) -> Self {
395 self.config = self.config.response_delay(endpoint, delay_ms);
396 self
397 }
398
399 pub fn log_requests(mut self, enabled: bool) -> Self {
401 self.config = self.config.log_requests(enabled);
402 self
403 }
404
405 pub fn build(self) -> Result<MockServer> {
407 MockServer::new(self.config)
408 }
409}
410
411pub async fn start_mock_server(openapi_spec: Value, port: u16) -> Result<()> {
413 let server = MockServerBuilder::new(openapi_spec).port(port).build()?;
414
415 server.start().await
416}
417
418pub async fn start_mock_server_with_config(config: MockServerConfig) -> Result<()> {
420 let server = MockServer::new(config)?;
421 server.start().await
422}
423
424#[cfg(test)]
425mod tests {
426 use super::*;
427 use serde_json::json;
428
429 #[test]
430 fn test_mock_server_config_default() {
431 let config = MockServerConfig::default();
432
433 assert_eq!(config.port, 3000);
434 assert_eq!(config.host, "127.0.0.1");
435 assert!(config.enable_cors);
436 assert!(config.log_requests);
437 assert!(config.response_delays.is_empty());
438 }
439
440 #[test]
441 fn test_mock_server_config_new() {
442 let spec = json!({
443 "openapi": "3.0.0",
444 "info": {
445 "title": "Test API",
446 "version": "1.0.0"
447 }
448 });
449
450 let config = MockServerConfig::new(spec);
451
452 assert_eq!(config.port, 3000);
453 assert_eq!(config.host, "127.0.0.1");
454 assert!(config.enable_cors);
455 }
456
457 #[test]
458 fn test_mock_server_config_builder_methods() {
459 let spec = json!({
460 "openapi": "3.0.0",
461 "info": {
462 "title": "Test API",
463 "version": "1.0.0"
464 }
465 });
466
467 let config = MockServerConfig::new(spec)
468 .port(8080)
469 .host("0.0.0.0".to_string())
470 .enable_cors(false)
471 .response_delay("/api/users".to_string(), 100)
472 .log_requests(false);
473
474 assert_eq!(config.port, 8080);
475 assert_eq!(config.host, "0.0.0.0");
476 assert!(!config.enable_cors);
477 assert!(!config.log_requests);
478 assert!(config.response_delays.contains_key("/api/users"));
479 assert_eq!(config.response_delays.get("/api/users"), Some(&100));
480 }
481
482 #[test]
483 fn test_mock_server_builder() {
484 let spec = json!({
485 "openapi": "3.0.0",
486 "info": {
487 "title": "Test API",
488 "version": "1.0.0"
489 }
490 });
491
492 let builder = MockServerBuilder::new(spec)
493 .port(8080)
494 .host("0.0.0.0".to_string())
495 .enable_cors(false);
496
497 assert_eq!(builder.config.port, 8080);
498 assert_eq!(builder.config.host, "0.0.0.0");
499 assert!(!builder.config.enable_cors);
500 }
501
502 #[test]
503 fn test_endpoints_match_exact() {
504 assert!(MockServer::endpoints_match("GET /api/users", "GET /api/users"));
505 assert!(!MockServer::endpoints_match("GET /api/users", "POST /api/users"));
506 assert!(!MockServer::endpoints_match("GET /api/users", "GET /api/products"));
507 }
508
509 #[test]
510 fn test_endpoints_match_with_params() {
511 assert!(MockServer::endpoints_match("GET /api/users/:id", "GET /api/users/123"));
513 assert!(MockServer::endpoints_match("GET /api/users/:id", "GET /api/users/abc"));
514 }
515
516 #[tokio::test]
517 async fn test_mock_server_creation() {
518 let spec = json!({
519 "openapi": "3.0.0",
520 "info": {
521 "title": "Test API",
522 "version": "1.0.0"
523 },
524 "paths": {
525 "/api/users": {
526 "get": {
527 "responses": {
528 "200": {
529 "description": "List of users",
530 "content": {
531 "application/json": {
532 "schema": {
533 "type": "object",
534 "properties": {
535 "users": {
536 "type": "array",
537 "items": {
538 "type": "object",
539 "properties": {
540 "id": {"type": "string"},
541 "name": {"type": "string"},
542 "email": {"type": "string"}
543 }
544 }
545 }
546 }
547 }
548 }
549 }
550 }
551 }
552 }
553 }
554 }
555 });
556
557 let config = MockServerConfig::new(spec);
558 let server = MockServer::new(config);
559
560 assert!(server.is_ok());
561 }
562}