mockforge_core/openapi_routes/
builder.rs1use crate::openapi::OpenApiSpec;
7use crate::openapi_routes::OpenApiRouteRegistry;
8use axum::{
9 body::Body,
10 extract::State,
11 http::{Request, StatusCode},
12 middleware::Next,
13 response::Response,
14 routing::{delete, get, head, options, patch, post, put},
15 Router,
16};
17use serde_json::{Map, Value};
18use tracing;
19use url;
20
21pub fn build_router_from_spec(spec: OpenApiSpec) -> Router {
23 let registry = OpenApiRouteRegistry::new(spec);
24 registry.build_router()
25}
26
27pub fn build_router_from_spec_with_options(
29 spec: OpenApiSpec,
30 options: crate::openapi_routes::ValidationOptions,
31) -> Router {
32 let registry = OpenApiRouteRegistry::new_with_options(spec, options);
33 registry.build_router()
34}
35
36pub struct RouterBuilder {
38 registry: OpenApiRouteRegistry,
39 middleware: Option<Box<dyn Fn(Router) -> Router + Send + 'static>>,
40 custom_routes: Option<Box<dyn Fn(Router) -> Router + Send + 'static>>,
41}
42
43impl RouterBuilder {
44 pub fn new(spec: OpenApiSpec) -> Self {
46 let registry = OpenApiRouteRegistry::new(spec);
47 Self {
48 registry,
49 middleware: None,
50 custom_routes: None,
51 }
52 }
53
54 pub fn with_options(
56 spec: OpenApiSpec,
57 options: crate::openapi_routes::ValidationOptions,
58 ) -> Self {
59 let registry = OpenApiRouteRegistry::new_with_options(spec, options);
60 Self {
61 registry,
62 middleware: None,
63 custom_routes: None,
64 }
65 }
66
67 pub fn with_middleware<F>(mut self, middleware: F) -> Self
69 where
70 F: Fn(Router) -> Router + Send + 'static,
71 {
72 self.middleware = Some(Box::new(middleware));
73 self
74 }
75
76 pub fn with_custom_routes<F>(mut self, route_builder: F) -> Self
78 where
79 F: Fn(Router) -> Router + Send + 'static,
80 {
81 self.custom_routes = Some(Box::new(route_builder));
82 self
83 }
84
85 pub fn build(self) -> Router {
87 let mut router = self.registry.build_router();
88 if let Some(middleware) = self.middleware {
89 router = middleware(router);
90 }
91 if let Some(custom_routes) = self.custom_routes {
92 router = custom_routes(router);
93 }
94 router
95 }
96}
97
98pub fn create_route_handler(
100 route: &crate::openapi::route::OpenApiRoute,
101 registry: &OpenApiRouteRegistry,
102) -> Router {
103 let axum_path = route.axum_path();
104 let route_clone = route.clone();
105 let _validator = registry.clone_for_validation();
106
107 let handler = move || async move {
109 let (status, response) = route_clone.mock_response_with_status();
110 (
111 axum::http::StatusCode::from_u16(status).unwrap_or(axum::http::StatusCode::OK),
112 axum::response::Json(response),
113 )
114 };
115
116 match route.method.as_str() {
117 "GET" => Router::new().route(&axum_path, get(handler)),
118 "POST" => Router::new().route(&axum_path, post(handler)),
119 "PUT" => Router::new().route(&axum_path, put(handler)),
120 "DELETE" => Router::new().route(&axum_path, delete(handler)),
121 "PATCH" => Router::new().route(&axum_path, patch(handler)),
122 "HEAD" => Router::new().route(&axum_path, head(handler)),
123 "OPTIONS" => Router::new().route(&axum_path, options(handler)),
124 _ => Router::new().route(&axum_path, get(handler)),
125 }
126}
127
128pub fn merge_routers(routers: Vec<Router>) -> Router {
130 let mut merged = Router::new();
131
132 for router in routers {
133 merged = merged.merge(router);
134 }
135
136 merged
137}
138
139pub async fn error_handler(request: Request<Body>, next: Next) -> Response {
141 let method = request.method().clone();
143 let uri = request.uri().clone();
144
145 let response = next.run(request).await;
146
147 if response.status().is_server_error() {
149 tracing::error!(
150 "Server error response: {} for request: {} {}",
151 response.status(),
152 method,
153 uri
154 );
155 } else if response.status().is_client_error() {
157 tracing::warn!(
158 "Client error response: {} for request: {} {}",
159 response.status(),
160 method,
161 uri
162 );
163 }
164
165 response
173}
174
175pub fn create_router_with_error_handling(router: Router) -> Router {
177 router.layer(axum::middleware::from_fn(error_handler))
178}
179
180pub fn create_router_with_logging(router: Router) -> Router {
182 router.layer(axum::middleware::from_fn(request_logger))
183}
184
185pub async fn validate_request(
187 State(validator): State<OpenApiRouteRegistry>,
188 mut request: Request<Body>,
189 next: Next,
190) -> Result<Response, StatusCode> {
191 let method = request.method().clone();
193 let uri = request.uri().clone();
194 let path = uri.path().to_string();
195
196 let query_string = uri.query().unwrap_or("");
198 let mut query_map = Map::new();
199 for (k, v) in url::form_urlencoded::parse(query_string.as_bytes()) {
200 query_map.insert(k.to_string(), Value::String(v.to_string()));
201 }
202
203 let headers = request.headers();
205 let mut header_map = Map::new();
206 for (name, value) in headers {
207 if let Ok(value_str) = value.to_str() {
208 header_map.insert(name.as_str().to_string(), Value::String(value_str.to_string()));
209 }
210 }
211
212 let mut cookie_map = Map::new();
214 if let Some(cookie_header) = headers.get("cookie") {
215 if let Ok(cookie_str) = cookie_header.to_str() {
216 for part in cookie_str.split(';') {
217 let part = part.trim();
218 if let Some((k, v)) = part.split_once('=') {
219 cookie_map.insert(k.to_string(), Value::String(v.to_string()));
220 }
221 }
222 }
223 }
224
225 let path_params = validator.extract_path_parameters(&path, method.as_str());
227 let mut path_map = Map::new();
228 for (key, value) in path_params {
229 path_map.insert(key, Value::String(value));
230 }
231
232 let body = std::mem::take(request.body_mut());
234 let body_bytes = axum::body::to_bytes(body, usize::MAX)
235 .await
236 .map_err(|_| StatusCode::BAD_REQUEST)?;
237 let body_json = if !body_bytes.is_empty() {
238 serde_json::from_slice(&body_bytes).ok()
239 } else {
240 None
241 };
242
243 if validator
245 .validate_request_with_all(
246 &path,
247 method.as_str(),
248 &path_map,
249 &query_map,
250 &header_map,
251 &cookie_map,
252 body_json.as_ref(),
253 )
254 .is_err()
255 {
256 let status_code = validator.options.validation_status.unwrap_or_else(|| {
258 std::env::var("MOCKFORGE_VALIDATION_STATUS")
259 .ok()
260 .and_then(|s| s.parse::<u16>().ok())
261 .unwrap_or(400)
262 });
263 return Err(StatusCode::from_u16(status_code).unwrap_or(StatusCode::BAD_REQUEST));
264 }
265
266 Ok(next.run(request).await)
268}
269
270pub async fn request_logger(request: Request<Body>, next: Next) -> Result<Response, StatusCode> {
272 let method = request.method().clone();
273 let uri = request.uri().clone();
274 let version = request.version();
275
276 tracing::info!("Request: {} {} {:?}", method, uri, version);
278
279 if tracing::level_enabled!(tracing::Level::DEBUG) {
281 for (name, value) in request.headers() {
282 if let Ok(value_str) = value.to_str() {
283 tracing::debug!("Header: {}: {}", name, value_str);
284 }
285 }
286 }
287
288 let start = std::time::Instant::now();
289
290 let response = next.run(request).await;
292
293 let duration = start.elapsed();
294
295 tracing::info!("Response: {} {} - {} in {:?}", method, uri, response.status(), duration);
297
298 Ok(response)
299}
300
301pub fn create_router_with_validation(router: Router, validator: OpenApiRouteRegistry) -> Router {
303 router.layer(axum::middleware::from_fn_with_state(validator, validate_request))
304}