1use crate::mock_generator::{MockDataGenerator, MockDataResult, MockGeneratorConfig, MockResponse};
7use crate::{Error, Result};
8use axum::{
9 extract::Query,
10 http::{HeaderMap, StatusCode},
11 response::Json,
12 routing::get,
13 Router,
14};
15use serde_json::{json, Value};
16use std::collections::HashMap;
17use std::net::SocketAddr;
18use std::sync::Arc;
19use tokio::net::TcpListener;
20use tracing::info;
21
22#[derive(Debug, Clone)]
24pub struct MockServerConfig {
25 pub port: u16,
27 pub host: String,
29 pub openapi_spec: Value,
31 pub generator_config: MockGeneratorConfig,
33 pub enable_cors: bool,
35 pub response_delays: HashMap<String, u64>,
37 pub log_requests: bool,
39}
40
41impl Default for MockServerConfig {
42 fn default() -> Self {
43 Self {
44 port: 3000,
45 host: "127.0.0.1".to_string(),
46 openapi_spec: json!({}),
47 generator_config: MockGeneratorConfig::default(),
48 enable_cors: true,
49 response_delays: HashMap::new(),
50 log_requests: true,
51 }
52 }
53}
54
55impl MockServerConfig {
56 pub fn new(openapi_spec: Value) -> Self {
58 Self {
59 openapi_spec,
60 ..Default::default()
61 }
62 }
63
64 pub fn port(mut self, port: u16) -> Self {
66 self.port = port;
67 self
68 }
69
70 pub fn host(mut self, host: String) -> Self {
72 self.host = host;
73 self
74 }
75
76 pub fn generator_config(mut self, config: MockGeneratorConfig) -> Self {
78 self.generator_config = config;
79 self
80 }
81
82 pub fn enable_cors(mut self, enabled: bool) -> Self {
84 self.enable_cors = enabled;
85 self
86 }
87
88 pub fn response_delay(mut self, endpoint: String, delay_ms: u64) -> Self {
90 self.response_delays.insert(endpoint, delay_ms);
91 self
92 }
93
94 pub fn log_requests(mut self, enabled: bool) -> Self {
96 self.log_requests = enabled;
97 self
98 }
99}
100
101#[derive(Debug)]
103pub struct MockServer {
104 config: MockServerConfig,
106 mock_data: Arc<MockDataResult>,
108 handlers: HashMap<String, MockResponse>,
110}
111
112impl MockServer {
113 pub fn new(config: MockServerConfig) -> Result<Self> {
115 info!("Creating mock server with OpenAPI specification");
116
117 let mut generator = MockDataGenerator::with_config(config.generator_config.clone());
119 let mock_data = generator.generate_from_openapi_spec(&config.openapi_spec)?;
120
121 let mut handlers = HashMap::new();
123 for (endpoint, response) in &mock_data.responses {
124 handlers.insert(endpoint.clone(), response.clone());
125 }
126
127 Ok(Self {
128 config,
129 mock_data: Arc::new(mock_data),
130 handlers,
131 })
132 }
133
134 pub async fn start(self) -> Result<()> {
136 let config = self.config.clone();
137 let app = self.create_router();
138 let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
139
140 info!("Starting mock server on {}", addr);
141
142 let listener = TcpListener::bind(addr)
143 .await
144 .map_err(|e| Error::generic(format!("Failed to bind to {}: {}", addr, e)))?;
145
146 axum::serve(listener, app)
147 .await
148 .map_err(|e| Error::generic(format!("Server error: {}", e)))?;
149
150 Ok(())
151 }
152
153 fn create_router(self) -> Router {
155 let mock_data = Arc::clone(&self.mock_data);
156 let config = Arc::new(self.config);
157 let handlers = Arc::new(self.handlers);
158
159 let mut router = Router::new()
160 .route("/", get(Self::root_handler))
162 .route("/health", get(Self::health_handler))
163 .route("/openapi.json", get(Self::openapi_handler))
164 .route("/mock-data", get(Self::mock_data_handler))
165 .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 #[allow(dead_code)]
274 async fn generic_handler(
275 axum::extract::State(state): axum::extract::State<MockServerState>,
276 method: axum::http::Method,
277 path: axum::extract::Path<String>,
278 query: Query<HashMap<String, String>>,
279 _headers: HeaderMap,
280 ) -> std::result::Result<Json<Value>, StatusCode> {
281 let endpoint_key = format!("{} /{}", method.as_str().to_uppercase(), path.as_str());
282
283 if state.config.log_requests {
285 info!("Handling request: {} with query: {:?}", endpoint_key, query);
286 }
287
288 if let Some(delay) = state.config.response_delays.get(&endpoint_key) {
290 tokio::time::sleep(tokio::time::Duration::from_millis(*delay)).await;
291 }
292
293 if let Some(response) = state.handlers.get(&endpoint_key) {
295 Ok(Json(response.body.clone()))
296 } else {
297 let similar_endpoint = state
299 .handlers
300 .keys()
301 .find(|key| Self::endpoints_match(key, &endpoint_key))
302 .cloned();
303
304 if let Some(endpoint) = similar_endpoint {
305 if let Some(response) = state.handlers.get(&endpoint) {
306 Ok(Json(response.body.clone()))
307 } else {
308 Err(StatusCode::NOT_FOUND)
309 }
310 } else {
311 let generic_response = json!({
313 "message": "Mock response",
314 "endpoint": endpoint_key,
315 "timestamp": chrono::Utc::now().to_rfc3339(),
316 "data": {}
317 });
318 Ok(Json(generic_response))
319 }
320 }
321 }
322
323 pub fn endpoints_match(pattern: &str, request: &str) -> bool {
327 let pattern_parts: Vec<&str> = pattern.split(' ').collect();
330 let request_parts: Vec<&str> = request.split(' ').collect();
331
332 if pattern_parts.len() != request_parts.len() {
333 return false;
334 }
335
336 for (pattern_part, request_part) in pattern_parts.iter().zip(request_parts.iter()) {
337 if pattern_part != request_part && !pattern_part.contains(":") {
338 return false;
339 }
340 }
341
342 true
343 }
344}
345
346#[derive(Debug, Clone)]
348struct MockServerState {
349 mock_data: Arc<MockDataResult>,
350 config: Arc<MockServerConfig>,
352 handlers: Arc<HashMap<String, MockResponse>>,
354}
355
356#[derive(Debug)]
358pub struct MockServerBuilder {
359 config: MockServerConfig,
360}
361
362impl MockServerBuilder {
363 pub fn new(openapi_spec: Value) -> Self {
365 Self {
366 config: MockServerConfig::new(openapi_spec),
367 }
368 }
369
370 pub fn port(mut self, port: u16) -> Self {
372 self.config = self.config.port(port);
373 self
374 }
375
376 pub fn host(mut self, host: String) -> Self {
378 self.config = self.config.host(host);
379 self
380 }
381
382 pub fn generator_config(mut self, config: MockGeneratorConfig) -> Self {
384 self.config = self.config.generator_config(config);
385 self
386 }
387
388 pub fn enable_cors(mut self, enabled: bool) -> Self {
390 self.config = self.config.enable_cors(enabled);
391 self
392 }
393
394 pub fn response_delay(mut self, endpoint: String, delay_ms: u64) -> Self {
396 self.config = self.config.response_delay(endpoint, delay_ms);
397 self
398 }
399
400 pub fn log_requests(mut self, enabled: bool) -> Self {
402 self.config = self.config.log_requests(enabled);
403 self
404 }
405
406 pub fn build(self) -> Result<MockServer> {
408 MockServer::new(self.config)
409 }
410}
411
412pub async fn start_mock_server(openapi_spec: Value, port: u16) -> Result<()> {
414 let server = MockServerBuilder::new(openapi_spec).port(port).build()?;
415
416 server.start().await
417}
418
419pub async fn start_mock_server_with_config(config: MockServerConfig) -> Result<()> {
421 let server = MockServer::new(config)?;
422 server.start().await
423}
424
425#[cfg(test)]
426mod tests {
427 use super::*;
428 use serde_json::json;
429
430 #[test]
431 fn test_mock_server_config_default() {
432 let config = MockServerConfig::default();
433
434 assert_eq!(config.port, 3000);
435 assert_eq!(config.host, "127.0.0.1");
436 assert!(config.enable_cors);
437 assert!(config.log_requests);
438 assert!(config.response_delays.is_empty());
439 }
440
441 #[test]
442 fn test_mock_server_config_new() {
443 let spec = json!({
444 "openapi": "3.0.0",
445 "info": {
446 "title": "Test API",
447 "version": "1.0.0"
448 }
449 });
450
451 let config = MockServerConfig::new(spec);
452
453 assert_eq!(config.port, 3000);
454 assert_eq!(config.host, "127.0.0.1");
455 assert!(config.enable_cors);
456 }
457
458 #[test]
459 fn test_mock_server_config_builder_methods() {
460 let spec = json!({
461 "openapi": "3.0.0",
462 "info": {
463 "title": "Test API",
464 "version": "1.0.0"
465 }
466 });
467
468 let config = MockServerConfig::new(spec)
469 .port(8080)
470 .host("0.0.0.0".to_string())
471 .enable_cors(false)
472 .response_delay("/api/users".to_string(), 100)
473 .log_requests(false);
474
475 assert_eq!(config.port, 8080);
476 assert_eq!(config.host, "0.0.0.0");
477 assert!(!config.enable_cors);
478 assert!(!config.log_requests);
479 assert!(config.response_delays.contains_key("/api/users"));
480 assert_eq!(config.response_delays.get("/api/users"), Some(&100));
481 }
482
483 #[test]
484 fn test_mock_server_builder() {
485 let spec = json!({
486 "openapi": "3.0.0",
487 "info": {
488 "title": "Test API",
489 "version": "1.0.0"
490 }
491 });
492
493 let builder = MockServerBuilder::new(spec)
494 .port(8080)
495 .host("0.0.0.0".to_string())
496 .enable_cors(false);
497
498 assert_eq!(builder.config.port, 8080);
499 assert_eq!(builder.config.host, "0.0.0.0");
500 assert!(!builder.config.enable_cors);
501 }
502
503 #[test]
504 fn test_endpoints_match_exact() {
505 assert!(MockServer::endpoints_match("GET /api/users", "GET /api/users"));
506 assert!(!MockServer::endpoints_match("GET /api/users", "POST /api/users"));
507 assert!(!MockServer::endpoints_match("GET /api/users", "GET /api/products"));
508 }
509
510 #[test]
511 fn test_endpoints_match_with_params() {
512 assert!(MockServer::endpoints_match("GET /api/users/:id", "GET /api/users/123"));
514 assert!(MockServer::endpoints_match("GET /api/users/:id", "GET /api/users/abc"));
515 }
516
517 #[tokio::test]
518 async fn test_mock_server_creation() {
519 let spec = json!({
520 "openapi": "3.0.0",
521 "info": {
522 "title": "Test API",
523 "version": "1.0.0"
524 },
525 "paths": {
526 "/api/users": {
527 "get": {
528 "responses": {
529 "200": {
530 "description": "List of users",
531 "content": {
532 "application/json": {
533 "schema": {
534 "type": "object",
535 "properties": {
536 "users": {
537 "type": "array",
538 "items": {
539 "type": "object",
540 "properties": {
541 "id": {"type": "string"},
542 "name": {"type": "string"},
543 "email": {"type": "string"}
544 }
545 }
546 }
547 }
548 }
549 }
550 }
551 }
552 }
553 }
554 }
555 }
556 });
557
558 let config = MockServerConfig::new(spec);
559 let server = MockServer::new(config);
560
561 assert!(server.is_ok());
562 }
563}