1use super::validation::{ValidationMode, ValidationOptions};
7use crate::ai_response::RequestContext;
8use crate::openapi::response::AiGenerator;
9use crate::openapi::route::OpenApiRoute;
10use crate::openapi::spec::OpenApiSpec;
11use axum::extract::Json;
12use axum::http::HeaderMap;
13use openapiv3::{PathItem, ReferenceOr};
14use serde_json::Value;
15use std::collections::{HashMap, HashSet};
16use std::sync::Arc;
17use url::Url;
18
19#[derive(Debug, Clone)]
21pub struct OpenApiRouteRegistry {
22 spec: Arc<OpenApiSpec>,
24 routes: Vec<OpenApiRoute>,
26 options: ValidationOptions,
28}
29
30#[cfg(test)]
31mod tests {
32 use super::*;
33
34 fn registry_from_yaml(yaml: &str) -> OpenApiRouteRegistry {
35 let spec = OpenApiSpec::from_string(yaml, Some("yaml")).expect("parse spec");
36 OpenApiRouteRegistry::new_with_env(spec)
37 }
38
39 #[test]
40 fn generates_routes_from_components_path_items() {
41 let yaml = r#"
42openapi: 3.1.0
43info:
44 title: Test API
45 version: "1.0.0"
46paths:
47 /users:
48 $ref: '#/components/pathItems/UserCollection'
49components:
50 pathItems:
51 UserCollection:
52 get:
53 operationId: listUsers
54 responses:
55 '200':
56 description: ok
57 content:
58 application/json:
59 schema:
60 type: array
61 items:
62 type: string
63 "#;
64
65 let registry = registry_from_yaml(yaml);
66 let routes = registry.routes();
67 assert_eq!(routes.len(), 1);
68 assert_eq!(routes[0].method, "GET");
69 assert_eq!(routes[0].path, "/users");
70 }
71
72 #[test]
73 fn generates_routes_from_paths_references() {
74 let yaml = r#"
75openapi: 3.0.3
76info:
77 title: PathRef API
78 version: "1.0.0"
79paths:
80 /users:
81 get:
82 operationId: getUsers
83 responses:
84 '200':
85 description: ok
86 /all-users:
87 $ref: '#/paths/~1users'
88 "#;
89
90 let registry = registry_from_yaml(yaml);
91 let routes = registry.routes();
92 assert_eq!(routes.len(), 2);
93
94 let mut paths: Vec<(&str, &str)> = routes
95 .iter()
96 .map(|route| (route.method.as_str(), route.path.as_str()))
97 .collect();
98 paths.sort();
99
100 assert_eq!(paths, vec![("GET", "/all-users"), ("GET", "/users")]);
101 }
102
103 #[test]
104 fn generates_routes_with_server_base_path() {
105 let yaml = r#"
106openapi: 3.0.3
107info:
108 title: Base Path API
109 version: "1.0.0"
110servers:
111 - url: https://api.example.com/api/v1
112paths:
113 /users:
114 get:
115 operationId: getUsers
116 responses:
117 '200':
118 description: ok
119 "#;
120
121 let registry = registry_from_yaml(yaml);
122 let paths: Vec<String> = registry.routes().iter().map(|route| route.path.clone()).collect();
123 assert!(paths.contains(&"/api/v1/users".to_string()));
124 assert!(!paths.contains(&"/users".to_string()));
125 }
126
127 #[test]
128 fn generates_routes_with_relative_server_base_path() {
129 let yaml = r#"
130openapi: 3.0.3
131info:
132 title: Relative Base Path API
133 version: "1.0.0"
134servers:
135 - url: /api/v2
136paths:
137 /orders:
138 post:
139 operationId: createOrder
140 responses:
141 '201':
142 description: created
143 "#;
144
145 let registry = registry_from_yaml(yaml);
146 let paths: Vec<String> = registry.routes().iter().map(|route| route.path.clone()).collect();
147 assert!(paths.contains(&"/api/v2/orders".to_string()));
148 assert!(!paths.contains(&"/orders".to_string()));
149 }
150}
151
152impl OpenApiRouteRegistry {
153 pub fn new(spec: OpenApiSpec) -> Self {
155 Self::new_with_env(spec)
156 }
157
158 pub fn new_with_env(spec: OpenApiSpec) -> Self {
167 tracing::debug!("Creating OpenAPI route registry");
168 let spec = Arc::new(spec);
169 let routes = Self::generate_routes(&spec);
170 let options = ValidationOptions {
171 request_mode: match std::env::var("MOCKFORGE_REQUEST_VALIDATION")
172 .unwrap_or_else(|_| "enforce".into())
173 .to_ascii_lowercase()
174 .as_str()
175 {
176 "off" | "disable" | "disabled" => ValidationMode::Disabled,
177 "warn" | "warning" => ValidationMode::Warn,
178 _ => ValidationMode::Enforce,
179 },
180 aggregate_errors: std::env::var("MOCKFORGE_AGGREGATE_ERRORS")
181 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
182 .unwrap_or(true),
183 validate_responses: std::env::var("MOCKFORGE_RESPONSE_VALIDATION")
184 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
185 .unwrap_or(false),
186 overrides: HashMap::new(),
187 admin_skip_prefixes: Vec::new(),
188 response_template_expand: std::env::var("MOCKFORGE_RESPONSE_TEMPLATE_EXPAND")
189 .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
190 .unwrap_or(false),
191 validation_status: std::env::var("MOCKFORGE_VALIDATION_STATUS")
192 .ok()
193 .and_then(|s| s.parse::<u16>().ok()),
194 };
195 Self {
196 spec,
197 routes,
198 options,
199 }
200 }
201
202 pub fn new_with_options(spec: OpenApiSpec, options: ValidationOptions) -> Self {
208 tracing::debug!("Creating OpenAPI route registry with custom options");
209 let spec = Arc::new(spec);
210 let routes = Self::generate_routes(&spec);
211 Self {
212 spec,
213 routes,
214 options,
215 }
216 }
217
218 fn generate_routes(spec: &Arc<OpenApiSpec>) -> Vec<OpenApiRoute> {
220 tracing::debug!(
221 "Generating routes from OpenAPI spec with {} paths",
222 spec.spec.paths.paths.len()
223 );
224 let base_paths = Self::collect_base_paths(spec);
225
226 #[cfg(feature = "rayon")]
228 {
229 use rayon::prelude::*;
230 let path_items: Vec<_> = spec.spec.paths.paths.iter().collect();
231
232 if path_items.len() > 100 {
234 tracing::debug!("Using parallel route generation for {} paths", path_items.len());
235 let routes: Vec<Vec<OpenApiRoute>> = path_items
236 .par_iter()
237 .map(|(path, path_item)| {
238 let mut routes = Vec::new();
239 let mut visited = HashSet::new();
240 if let Some(item) = Self::resolve_path_item(path_item, spec, &mut visited) {
241 Self::collect_routes_for_path(&mut routes, path, &item, spec, &base_paths);
242 } else {
243 tracing::warn!(
244 "Skipping path {} because the referenced PathItem could not be resolved",
245 path
246 );
247 }
248 routes
249 })
250 .collect();
251
252 let mut all_routes = Vec::new();
253 for route_batch in routes {
254 all_routes.extend(route_batch);
255 }
256 tracing::debug!(
257 "Generated {} total routes from OpenAPI spec (parallel)",
258 all_routes.len()
259 );
260 return all_routes;
261 }
262 }
263
264 let mut routes = Vec::new();
266 for (path, path_item) in &spec.spec.paths.paths {
267 tracing::debug!("Processing path: {}", path);
268 let mut visited = HashSet::new();
269 if let Some(item) = Self::resolve_path_item(path_item, spec, &mut visited) {
270 Self::collect_routes_for_path(&mut routes, path, &item, spec, &base_paths);
271 } else {
272 tracing::warn!(
273 "Skipping path {} because the referenced PathItem could not be resolved",
274 path
275 );
276 }
277 }
278
279 tracing::debug!("Generated {} total routes from OpenAPI spec", routes.len());
280 routes
281 }
282
283 fn collect_routes_for_path(
284 routes: &mut Vec<OpenApiRoute>,
285 path: &str,
286 item: &PathItem,
287 spec: &Arc<OpenApiSpec>,
288 base_paths: &[String],
289 ) {
290 if let Some(op) = &item.get {
291 tracing::debug!(" Adding GET route for path: {}", path);
292 Self::push_routes_for_method(routes, "GET", path, op, spec, base_paths);
293 }
294 if let Some(op) = &item.post {
295 Self::push_routes_for_method(routes, "POST", path, op, spec, base_paths);
296 }
297 if let Some(op) = &item.put {
298 Self::push_routes_for_method(routes, "PUT", path, op, spec, base_paths);
299 }
300 if let Some(op) = &item.delete {
301 Self::push_routes_for_method(routes, "DELETE", path, op, spec, base_paths);
302 }
303 if let Some(op) = &item.patch {
304 Self::push_routes_for_method(routes, "PATCH", path, op, spec, base_paths);
305 }
306 if let Some(op) = &item.head {
307 Self::push_routes_for_method(routes, "HEAD", path, op, spec, base_paths);
308 }
309 if let Some(op) = &item.options {
310 Self::push_routes_for_method(routes, "OPTIONS", path, op, spec, base_paths);
311 }
312 if let Some(op) = &item.trace {
313 Self::push_routes_for_method(routes, "TRACE", path, op, spec, base_paths);
314 }
315 }
316
317 fn push_routes_for_method(
318 routes: &mut Vec<OpenApiRoute>,
319 method: &str,
320 path: &str,
321 operation: &openapiv3::Operation,
322 spec: &Arc<OpenApiSpec>,
323 base_paths: &[String],
324 ) {
325 for base in base_paths {
326 let full_path = Self::join_base_path(base, path);
327 routes.push(OpenApiRoute::from_operation(method, full_path, operation, spec.clone()));
328 }
329 }
330
331 fn collect_base_paths(spec: &Arc<OpenApiSpec>) -> Vec<String> {
332 let mut base_paths = Vec::new();
333
334 for server in spec.servers() {
335 if let Some(base_path) = Self::extract_base_path(server.url.as_str()) {
336 if !base_paths.contains(&base_path) {
337 base_paths.push(base_path);
338 }
339 }
340 }
341
342 if base_paths.is_empty() {
343 base_paths.push(String::new());
344 }
345
346 base_paths
347 }
348
349 fn extract_base_path(raw_url: &str) -> Option<String> {
350 let trimmed = raw_url.trim();
351 if trimmed.is_empty() {
352 return None;
353 }
354
355 if trimmed.starts_with('/') {
356 return Some(Self::normalize_base_path(trimmed));
357 }
358
359 if let Ok(parsed) = Url::parse(trimmed) {
360 return Some(Self::normalize_base_path(parsed.path()));
361 }
362
363 None
364 }
365
366 fn normalize_base_path(path: &str) -> String {
367 let trimmed = path.trim();
368 if trimmed.is_empty() || trimmed == "/" {
369 String::new()
370 } else {
371 let mut normalized = trimmed.trim_end_matches('/').to_string();
372 if !normalized.starts_with('/') {
373 normalized.insert(0, '/');
374 }
375 normalized
376 }
377 }
378
379 fn join_base_path(base: &str, path: &str) -> String {
380 let trimmed_path = path.trim_start_matches('/');
381
382 if base.is_empty() {
383 if trimmed_path.is_empty() {
384 "/".to_string()
385 } else {
386 format!("/{}", trimmed_path)
387 }
388 } else if trimmed_path.is_empty() {
389 base.to_string()
390 } else {
391 format!("{}/{}", base, trimmed_path)
392 }
393 }
394
395 fn resolve_path_item(
396 value: &ReferenceOr<PathItem>,
397 spec: &Arc<OpenApiSpec>,
398 visited: &mut HashSet<String>,
399 ) -> Option<PathItem> {
400 match value {
401 ReferenceOr::Item(item) => Some(item.clone()),
402 ReferenceOr::Reference { reference } => {
403 Self::resolve_path_item_reference(reference, spec, visited)
404 }
405 }
406 }
407
408 fn resolve_path_item_reference(
409 reference: &str,
410 spec: &Arc<OpenApiSpec>,
411 visited: &mut HashSet<String>,
412 ) -> Option<PathItem> {
413 if !visited.insert(reference.to_string()) {
414 tracing::warn!("Detected recursive path item reference: {}", reference);
415 return None;
416 }
417
418 if let Some(name) = reference.strip_prefix("#/components/pathItems/") {
419 return Self::resolve_component_path_item(name, spec, visited);
420 }
421
422 if let Some(pointer) = reference.strip_prefix("#/paths/") {
423 let decoded_path = Self::decode_json_pointer(pointer);
424 if let Some(next) = spec.spec.paths.paths.get(&decoded_path) {
425 return Self::resolve_path_item(next, spec, visited);
426 }
427 tracing::warn!(
428 "Path reference {} resolved to missing path '{}'",
429 reference,
430 decoded_path
431 );
432 return None;
433 }
434
435 tracing::warn!("Unsupported path item reference: {}", reference);
436 None
437 }
438
439 fn resolve_component_path_item(
440 name: &str,
441 spec: &Arc<OpenApiSpec>,
442 visited: &mut HashSet<String>,
443 ) -> Option<PathItem> {
444 let raw = spec.raw_document.as_ref()?;
445 let components = raw.get("components")?.as_object()?;
446 let path_items = components.get("pathItems")?.as_object()?;
447 let item_value = path_items.get(name)?;
448
449 if let Some(reference) = item_value
450 .as_object()
451 .and_then(|obj| obj.get("$ref"))
452 .and_then(|value| value.as_str())
453 {
454 tracing::debug!(
455 "Resolving components.pathItems entry '{}' via reference {}",
456 name,
457 reference
458 );
459 return Self::resolve_path_item_reference(reference, spec, visited);
460 }
461
462 match serde_json::from_value(item_value.clone()) {
463 Ok(item) => Some(item),
464 Err(err) => {
465 tracing::warn!(
466 "Failed to deserialize components.pathItems entry '{}' as a PathItem: {}",
467 name,
468 err
469 );
470 None
471 }
472 }
473 }
474
475 fn decode_json_pointer(pointer: &str) -> String {
476 let segments: Vec<String> = pointer
477 .split('/')
478 .map(|segment| segment.replace("~1", "/").replace("~0", "~"))
479 .collect();
480 segments.join("/")
481 }
482
483 pub fn routes(&self) -> &[OpenApiRoute] {
485 &self.routes
486 }
487
488 pub fn spec(&self) -> &OpenApiSpec {
490 &self.spec
491 }
492
493 pub fn options(&self) -> &ValidationOptions {
495 &self.options
496 }
497
498 pub fn options_mut(&mut self) -> &mut ValidationOptions {
500 &mut self.options
501 }
502
503 pub fn build_router(&self) -> axum::Router {
505 use axum::routing::{delete, get, patch, post, put};
506
507 let mut router = axum::Router::new();
508 tracing::debug!("Building router from {} routes", self.routes.len());
509
510 for route in &self.routes {
511 tracing::debug!("Adding route: {} {}", route.method, route.path);
512 tracing::debug!(
513 "Route operation responses: {:?}",
514 route.operation.responses.responses.keys().collect::<Vec<_>>()
515 );
516
517 let route_clone = route.clone();
518 let handler = move || {
519 let route = route_clone.clone();
520 async move {
521 tracing::debug!("Handling request for route: {} {}", route.method, route.path);
522 let (status, response, trace) =
523 route.mock_response_with_status_and_scenario_and_trace(None);
524 tracing::debug!("Generated response with status: {}", status);
525
526 use axum::response::IntoResponse;
528 let mut axum_response = (
529 axum::http::StatusCode::from_u16(status)
530 .unwrap_or(axum::http::StatusCode::OK),
531 axum::response::Json(response),
532 )
533 .into_response();
534
535 axum_response.extensions_mut().insert(trace);
537
538 axum_response
539 }
540 };
541
542 match route.method.as_str() {
543 "GET" => {
544 tracing::debug!("Registering GET route: {}", route.path);
545 router = router.route(&route.path, get(handler));
546 }
547 "POST" => {
548 tracing::debug!("Registering POST route: {}", route.path);
549 router = router.route(&route.path, post(handler));
550 }
551 "PUT" => {
552 tracing::debug!("Registering PUT route: {}", route.path);
553 router = router.route(&route.path, put(handler));
554 }
555 "DELETE" => {
556 tracing::debug!("Registering DELETE route: {}", route.path);
557 router = router.route(&route.path, delete(handler));
558 }
559 "PATCH" => {
560 tracing::debug!("Registering PATCH route: {}", route.path);
561 router = router.route(&route.path, patch(handler));
562 }
563 _ => tracing::warn!("Unsupported HTTP method: {}", route.method),
564 }
565 }
566
567 router
568 }
569
570 pub fn build_router_with_injectors(
579 &self,
580 latency_injector: crate::latency::LatencyInjector,
581 failure_injector: Option<crate::failure_injection::FailureInjector>,
582 ) -> axum::Router {
583 use axum::routing::{delete, get, patch, post, put};
584
585 let mut router = axum::Router::new();
586 tracing::debug!("Building router with injectors from {} routes", self.routes.len());
587
588 for route in &self.routes {
589 tracing::debug!("Adding route with injectors: {} {}", route.method, route.path);
590
591 let route_clone = route.clone();
592 let latency_injector_clone = latency_injector.clone();
593 let failure_injector_clone = failure_injector.clone();
594
595 let handler = move || {
596 let route = route_clone.clone();
597 let latency_injector = latency_injector_clone.clone();
598 let failure_injector = failure_injector_clone.clone();
599
600 async move {
601 tracing::debug!(
602 "Handling request with injectors for route: {} {}",
603 route.method,
604 route.path
605 );
606
607 let tags = route.operation.tags.clone();
609
610 if let Err(e) = latency_injector.inject_latency(&tags).await {
612 tracing::warn!("Failed to inject latency: {}", e);
613 }
614
615 if let Some(ref injector) = failure_injector {
617 if injector.should_inject_failure(&tags) {
618 return (
620 axum::http::StatusCode::INTERNAL_SERVER_ERROR,
621 axum::response::Json(serde_json::json!({
622 "error": "Injected failure",
623 "code": 500
624 })),
625 );
626 }
627 }
628
629 let (status, response) = route.mock_response_with_status();
631 (
632 axum::http::StatusCode::from_u16(status)
633 .unwrap_or(axum::http::StatusCode::OK),
634 axum::response::Json(response),
635 )
636 }
637 };
638
639 match route.method.as_str() {
640 "GET" => router = router.route(&route.path, get(handler)),
641 "POST" => router = router.route(&route.path, post(handler)),
642 "PUT" => router = router.route(&route.path, put(handler)),
643 "DELETE" => router = router.route(&route.path, delete(handler)),
644 "PATCH" => router = router.route(&route.path, patch(handler)),
645 _ => tracing::warn!("Unsupported HTTP method: {}", route.method),
646 }
647 }
648
649 router
650 }
651
652 pub fn extract_path_parameters(&self, path: &str, method: &str) -> HashMap<String, String> {
661 for route in &self.routes {
662 if route.method != method {
663 continue;
664 }
665
666 if let Some(params) = self.match_path_to_route(path, &route.path) {
667 return params;
668 }
669 }
670 HashMap::new()
671 }
672
673 fn match_path_to_route(
675 &self,
676 request_path: &str,
677 route_pattern: &str,
678 ) -> Option<HashMap<String, String>> {
679 let mut params = HashMap::new();
680
681 let request_segments: Vec<&str> = request_path.trim_start_matches('/').split('/').collect();
683 let pattern_segments: Vec<&str> =
684 route_pattern.trim_start_matches('/').split('/').collect();
685
686 if request_segments.len() != pattern_segments.len() {
687 return None;
688 }
689
690 for (req_seg, pat_seg) in request_segments.iter().zip(pattern_segments.iter()) {
691 if pat_seg.starts_with('{') && pat_seg.ends_with('}') {
692 let param_name = &pat_seg[1..pat_seg.len() - 1];
694 params.insert(param_name.to_string(), req_seg.to_string());
695 } else if req_seg != pat_seg {
696 return None;
698 }
699 }
700
701 Some(params)
702 }
703
704 pub fn build_router_with_ai(
712 &self,
713 ai_generator: Option<std::sync::Arc<dyn AiGenerator + Send + Sync>>,
714 ) -> axum::Router {
715 use axum::routing::{delete, get, patch, post, put};
716
717 let mut router = axum::Router::new();
718 tracing::debug!("Building router with AI support from {} routes", self.routes.len());
719
720 for route in &self.routes {
721 tracing::debug!("Adding AI-enabled route: {} {}", route.method, route.path);
722
723 let route_clone = route.clone();
724 let ai_generator_clone = ai_generator.clone();
725
726 let handler = move |headers: HeaderMap, body: Option<Json<Value>>| {
728 let route = route_clone.clone();
729 let ai_generator = ai_generator_clone.clone();
730
731 async move {
732 tracing::debug!(
733 "Handling AI request for route: {} {}",
734 route.method,
735 route.path
736 );
737
738 let mut context = RequestContext::new(route.method.clone(), route.path.clone());
740
741 context.headers = headers
743 .iter()
744 .map(|(k, v)| {
745 (k.to_string(), Value::String(v.to_str().unwrap_or("").to_string()))
746 })
747 .collect();
748
749 context.body = body.map(|Json(b)| b);
751
752 let (status, response) = if let (Some(generator), Some(_ai_config)) =
754 (ai_generator, &route.ai_config)
755 {
756 route
757 .mock_response_with_status_async(&context, Some(generator.as_ref()))
758 .await
759 } else {
760 route.mock_response_with_status()
762 };
763
764 (
765 axum::http::StatusCode::from_u16(status)
766 .unwrap_or(axum::http::StatusCode::OK),
767 axum::response::Json(response),
768 )
769 }
770 };
771
772 match route.method.as_str() {
773 "GET" => {
774 router = router.route(&route.path, get(handler));
775 }
776 "POST" => {
777 router = router.route(&route.path, post(handler));
778 }
779 "PUT" => {
780 router = router.route(&route.path, put(handler));
781 }
782 "DELETE" => {
783 router = router.route(&route.path, delete(handler));
784 }
785 "PATCH" => {
786 router = router.route(&route.path, patch(handler));
787 }
788 _ => tracing::warn!("Unsupported HTTP method for AI: {}", route.method),
789 }
790 }
791
792 router
793 }
794
795 pub fn build_router_with_mockai(
806 &self,
807 mockai: Option<std::sync::Arc<tokio::sync::RwLock<crate::intelligent_behavior::MockAI>>>,
808 ) -> axum::Router {
809 use crate::intelligent_behavior::Request as MockAIRequest;
810
811 use axum::routing::{delete, get, patch, post, put};
812
813 let mut router = axum::Router::new();
814 tracing::debug!("Building router with MockAI support from {} routes", self.routes.len());
815
816 for route in &self.routes {
817 tracing::debug!("Adding MockAI-enabled route: {} {}", route.method, route.path);
818
819 let route_clone = route.clone();
820 let mockai_clone = mockai.clone();
821
822 let handler = move |query: axum::extract::Query<HashMap<String, String>>,
826 headers: HeaderMap,
827 body: Option<Json<Value>>| {
828 let route = route_clone.clone();
829 let mockai = mockai_clone.clone();
830
831 async move {
832 tracing::debug!(
833 "Handling MockAI request for route: {} {}",
834 route.method,
835 route.path
836 );
837
838 let mockai_query = query.0;
840
841 if let Some(mockai_arc) = mockai {
843 let mockai_guard = mockai_arc.read().await;
844
845 let mut mockai_headers = HashMap::new();
847 for (k, v) in headers.iter() {
848 mockai_headers
849 .insert(k.to_string(), v.to_str().unwrap_or("").to_string());
850 }
851
852 let mockai_request = MockAIRequest {
853 method: route.method.clone(),
854 path: route.path.clone(),
855 body: body.as_ref().map(|Json(b)| b.clone()),
856 query_params: mockai_query,
857 headers: mockai_headers,
858 };
859
860 match mockai_guard.process_request(&mockai_request).await {
862 Ok(mockai_response) => {
863 tracing::debug!(
864 "MockAI generated response with status: {}",
865 mockai_response.status_code
866 );
867 return (
868 axum::http::StatusCode::from_u16(mockai_response.status_code)
869 .unwrap_or(axum::http::StatusCode::OK),
870 axum::response::Json(mockai_response.body),
871 );
872 }
873 Err(e) => {
874 tracing::warn!(
875 "MockAI processing failed for {} {}: {}, falling back to standard response",
876 route.method,
877 route.path,
878 e
879 );
880 }
882 }
883 }
884
885 let (status, response) = route.mock_response_with_status();
887 (
888 axum::http::StatusCode::from_u16(status)
889 .unwrap_or(axum::http::StatusCode::OK),
890 axum::response::Json(response),
891 )
892 }
893 };
894
895 match route.method.as_str() {
896 "GET" => {
897 router = router.route(&route.path, get(handler));
898 }
899 "POST" => {
900 router = router.route(&route.path, post(handler));
901 }
902 "PUT" => {
903 router = router.route(&route.path, put(handler));
904 }
905 "DELETE" => {
906 router = router.route(&route.path, delete(handler));
907 }
908 "PATCH" => {
909 router = router.route(&route.path, patch(handler));
910 }
911 _ => tracing::warn!("Unsupported HTTP method for MockAI: {}", route.method),
912 }
913 }
914
915 router
916 }
917}