1use crate::handler_trait::{Handler, HandlerResult, RequestData};
4use axum::body::Body;
5use futures::FutureExt;
6use serde_json::Value;
7use spikard_core::errors::StructuredError;
8use spikard_core::{ParameterValidator, ProblemDetails, SchemaValidator};
9use std::future::Future;
10use std::panic::AssertUnwindSafe;
11use std::pin::Pin;
12use std::sync::Arc;
13
14pub struct ValidatingHandler {
16 inner: Arc<dyn Handler>,
17 request_validator: Option<Arc<SchemaValidator>>,
18 parameter_validator: Option<ParameterValidator>,
19}
20
21impl ValidatingHandler {
22 pub fn new(inner: Arc<dyn Handler>, route: &crate::Route) -> Self {
24 Self {
25 inner,
26 request_validator: route.request_validator.clone(),
27 parameter_validator: route.parameter_validator.clone(),
28 }
29 }
30}
31
32impl Handler for ValidatingHandler {
33 fn prefers_raw_json_body(&self) -> bool {
34 self.inner.prefers_raw_json_body()
35 }
36
37 fn prefers_parameter_extraction(&self) -> bool {
38 self.inner.prefers_parameter_extraction()
39 }
40
41 fn call(
42 &self,
43 req: axum::http::Request<Body>,
44 mut request_data: RequestData,
45 ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
46 let inner = &self.inner;
50 let request_validator = &self.request_validator;
51 let parameter_validator = &self.parameter_validator;
52
53 Box::pin(async move {
54 let is_json_body = request_data.body.is_null()
55 && request_data.raw_body.is_some()
56 && request_data
57 .headers
58 .get("content-type")
59 .is_some_and(|ct| crate::middleware::validation::is_json_like_str(ct));
60
61 if is_json_body
62 && request_validator.is_none()
63 && !inner.prefers_raw_json_body()
64 && let Some(raw_bytes) = request_data.raw_body.as_ref()
65 {
66 request_data.body = Arc::new(
67 serde_json::from_slice::<Value>(raw_bytes)
68 .map_err(|e| (axum::http::StatusCode::BAD_REQUEST, format!("Invalid JSON: {}", e)))?,
69 );
70 }
71
72 if let Some(validator) = request_validator {
73 if request_data.body.is_null()
74 && let Some(raw_bytes) = request_data.raw_body.as_ref()
75 {
76 request_data.body = Arc::new(
77 serde_json::from_slice::<Value>(raw_bytes)
78 .map_err(|e| (axum::http::StatusCode::BAD_REQUEST, format!("Invalid JSON: {}", e)))?,
79 );
80 }
81
82 if let Err(errors) = validator.validate(&request_data.body) {
83 let problem = ProblemDetails::from_validation_error(&errors);
84 let body = problem.to_json().unwrap_or_else(|_| "{}".to_string());
85 return Err((problem.status_code(), body));
86 }
87 }
88
89 if let Some(validator) = parameter_validator
90 && !inner.prefers_parameter_extraction()
91 {
92 match validator.validate_and_extract(
93 &request_data.query_params,
94 &request_data.raw_query_params,
95 &request_data.path_params,
96 &request_data.headers,
97 &request_data.cookies,
98 ) {
99 Ok(validated) => {
100 request_data.validated_params = Some(Arc::new(validated));
101 }
102 Err(errors) => {
103 let problem = ProblemDetails::from_validation_error(&errors);
104 let body = problem.to_json().unwrap_or_else(|_| "{}".to_string());
105 return Err((problem.status_code(), body));
106 }
107 }
108 }
109
110 match AssertUnwindSafe(async { inner.call(req, request_data).await })
111 .catch_unwind()
112 .await
113 {
114 Ok(result) => result,
115 Err(_) => {
116 let panic_payload = StructuredError::simple("panic", "Unexpected panic in handler");
117 let body = serde_json::to_string(&panic_payload)
118 .unwrap_or_else(|_| r#"{"error":"panic","code":"panic","details":{}}"#.to_string());
119 Err((axum::http::StatusCode::INTERNAL_SERVER_ERROR, body))
120 }
121 }
122 })
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use axum::http::{Request, Response, StatusCode};
130 use serde_json::json;
131 use std::collections::HashMap;
132 use std::sync::Arc;
133
134 fn create_request_data(body: Value) -> RequestData {
136 RequestData {
137 path_params: Arc::new(HashMap::new()),
138 query_params: Arc::new(json!({})),
139 validated_params: None,
140 raw_query_params: Arc::new(HashMap::new()),
141 body: Arc::new(body),
142 raw_body: None,
143 headers: Arc::new(HashMap::new()),
144 cookies: Arc::new(HashMap::new()),
145 method: "POST".to_string(),
146 path: "/test".to_string(),
147 #[cfg(feature = "di")]
148 dependencies: None,
149 }
150 }
151
152 fn create_request_data_with_raw_body(raw_body: Vec<u8>) -> RequestData {
154 RequestData {
155 path_params: Arc::new(HashMap::new()),
156 query_params: Arc::new(json!({})),
157 validated_params: None,
158 raw_query_params: Arc::new(HashMap::new()),
159 body: Arc::new(Value::Null),
160 raw_body: Some(bytes::Bytes::from(raw_body)),
161 headers: Arc::new(HashMap::new()),
162 cookies: Arc::new(HashMap::new()),
163 method: "POST".to_string(),
164 path: "/test".to_string(),
165 #[cfg(feature = "di")]
166 dependencies: None,
167 }
168 }
169
170 struct SuccessEchoHandler;
172
173 impl Handler for SuccessEchoHandler {
174 fn call(
175 &self,
176 _request: Request<Body>,
177 request_data: RequestData,
178 ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
179 Box::pin(async move {
180 let response = Response::builder()
181 .status(StatusCode::OK)
182 .header("content-type", "application/json")
183 .body(Body::from(request_data.body.to_string()))
184 .unwrap();
185 Ok(response)
186 })
187 }
188 }
189
190 struct PanicHandlerImpl;
192
193 impl Handler for PanicHandlerImpl {
194 fn call(
195 &self,
196 _request: Request<Body>,
197 _request_data: RequestData,
198 ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>> {
199 Box::pin(async move {
200 panic!("Intentional panic for testing");
201 })
202 }
203 }
204
205 #[tokio::test]
207 async fn test_no_validation_passes_through() {
208 let route = spikard_core::Route {
209 method: spikard_core::http::Method::Post,
210 path: "/test".to_string(),
211 handler_name: "test_handler".to_string(),
212 request_validator: None,
213 response_validator: None,
214 parameter_validator: None,
215 file_params: None,
216 is_async: true,
217 cors: None,
218 expects_json_body: false,
219 #[cfg(feature = "di")]
220 handler_dependencies: vec![],
221 jsonrpc_method: None,
222 };
223
224 let inner = Arc::new(SuccessEchoHandler);
225 let validator_handler = ValidatingHandler::new(inner, &route);
226
227 let request = Request::builder()
228 .method("POST")
229 .uri("/test")
230 .body(Body::empty())
231 .unwrap();
232
233 let request_data = create_request_data(json!({"name": "Alice"}));
234
235 let result = validator_handler.call(request, request_data).await;
236
237 assert!(result.is_ok(), "Handler should succeed without validators");
238 let response = result.unwrap();
239 assert_eq!(response.status(), StatusCode::OK);
240 }
241
242 #[tokio::test]
244 async fn test_json_body_parsed_without_request_validator() {
245 let route = spikard_core::Route {
246 method: spikard_core::http::Method::Post,
247 path: "/test".to_string(),
248 handler_name: "test_handler".to_string(),
249 request_validator: None,
250 response_validator: None,
251 parameter_validator: None,
252 file_params: None,
253 is_async: true,
254 cors: None,
255 expects_json_body: false,
256 #[cfg(feature = "di")]
257 handler_dependencies: vec![],
258 jsonrpc_method: None,
259 };
260
261 let inner = Arc::new(SuccessEchoHandler);
262 let validator_handler = ValidatingHandler::new(inner, &route);
263
264 let request = Request::builder()
265 .method("POST")
266 .uri("/test")
267 .header("content-type", "application/json")
268 .body(Body::empty())
269 .unwrap();
270
271 let mut headers = HashMap::new();
272 headers.insert("content-type".to_string(), "application/json".to_string());
273 let request_data = RequestData {
274 path_params: Arc::new(HashMap::new()),
275 query_params: Arc::new(json!({})),
276 validated_params: None,
277 raw_query_params: Arc::new(HashMap::new()),
278 body: Arc::new(Value::Null),
279 raw_body: Some(bytes::Bytes::from(br#"{"name":"Alice"}"#.to_vec())),
280 headers: Arc::new(headers),
281 cookies: Arc::new(HashMap::new()),
282 method: "POST".to_string(),
283 path: "/test".to_string(),
284 #[cfg(feature = "di")]
285 dependencies: None,
286 };
287
288 let response = validator_handler
289 .call(request, request_data)
290 .await
291 .expect("handler should succeed");
292 let body = axum::body::to_bytes(response.into_body(), usize::MAX)
293 .await
294 .expect("read body");
295 let echoed: Value = serde_json::from_slice(&body).expect("json");
296 assert_eq!(echoed["name"], "Alice");
297 }
298
299 #[tokio::test]
301 async fn test_request_body_validation_valid() {
302 let schema = json!({
303 "type": "object",
304 "properties": {
305 "name": {"type": "string"},
306 "age": {"type": "integer"}
307 },
308 "required": ["name"]
309 });
310
311 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
312
313 let route = spikard_core::Route {
314 method: spikard_core::http::Method::Post,
315 path: "/test".to_string(),
316 handler_name: "test_handler".to_string(),
317 request_validator: Some(validator),
318 response_validator: None,
319 parameter_validator: None,
320 file_params: None,
321 is_async: true,
322 cors: None,
323 expects_json_body: true,
324 #[cfg(feature = "di")]
325 handler_dependencies: vec![],
326 jsonrpc_method: None,
327 };
328
329 let inner = Arc::new(SuccessEchoHandler);
330 let validator_handler = ValidatingHandler::new(inner, &route);
331
332 let request = Request::builder()
333 .method("POST")
334 .uri("/test")
335 .body(Body::empty())
336 .unwrap();
337
338 let request_data = create_request_data(json!({"name": "Alice", "age": 30}));
339
340 let result = validator_handler.call(request, request_data).await;
341
342 assert!(result.is_ok(), "Valid request should pass validation");
343 let response = result.unwrap();
344 assert_eq!(response.status(), StatusCode::OK);
345 }
346
347 #[tokio::test]
349 async fn test_request_body_validation_invalid() {
350 let schema = json!({
351 "type": "object",
352 "properties": {
353 "name": {"type": "string"}
354 },
355 "required": ["name"]
356 });
357
358 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
359
360 let route = spikard_core::Route {
361 method: spikard_core::http::Method::Post,
362 path: "/test".to_string(),
363 handler_name: "test_handler".to_string(),
364 request_validator: Some(validator),
365 response_validator: None,
366 parameter_validator: None,
367 file_params: None,
368 is_async: true,
369 cors: None,
370 expects_json_body: true,
371 #[cfg(feature = "di")]
372 handler_dependencies: vec![],
373 jsonrpc_method: None,
374 };
375
376 let inner = Arc::new(SuccessEchoHandler);
377 let validator_handler = ValidatingHandler::new(inner, &route);
378
379 let request = Request::builder()
380 .method("POST")
381 .uri("/test")
382 .body(Body::empty())
383 .unwrap();
384
385 let request_data = create_request_data(json!({"age": 30}));
386
387 let result = validator_handler.call(request, request_data).await;
388
389 assert!(result.is_err(), "Invalid request should fail validation");
390 let (status, body) = result.unwrap_err();
391 assert_eq!(
392 status,
393 StatusCode::UNPROCESSABLE_ENTITY,
394 "Should return 422 for validation error"
395 );
396
397 let problem: serde_json::Value = serde_json::from_str(&body).expect("Should parse as JSON");
398 assert_eq!(problem["type"], "https://spikard.dev/errors/validation-error");
399 assert_eq!(problem["title"], "Request Validation Failed");
400 assert_eq!(problem["status"], 422);
401 assert!(problem["errors"].is_array(), "Should contain errors array extension");
402 assert!(
403 problem["errors"][0]["loc"][0] == "body",
404 "Error location should start with 'body'"
405 );
406 }
407
408 #[tokio::test]
410 async fn test_json_parsing_error() {
411 let schema = json!({
412 "type": "object",
413 "properties": {
414 "name": {"type": "string"}
415 }
416 });
417
418 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
419
420 let route = spikard_core::Route {
421 method: spikard_core::http::Method::Post,
422 path: "/test".to_string(),
423 handler_name: "test_handler".to_string(),
424 request_validator: Some(validator),
425 response_validator: None,
426 parameter_validator: None,
427 file_params: None,
428 is_async: true,
429 cors: None,
430 expects_json_body: true,
431 #[cfg(feature = "di")]
432 handler_dependencies: vec![],
433 jsonrpc_method: None,
434 };
435
436 let inner = Arc::new(SuccessEchoHandler);
437 let validator_handler = ValidatingHandler::new(inner, &route);
438
439 let request = Request::builder()
440 .method("POST")
441 .uri("/test")
442 .body(Body::empty())
443 .unwrap();
444
445 let request_data = create_request_data_with_raw_body(b"{invalid json}".to_vec());
446
447 let result = validator_handler.call(request, request_data).await;
448
449 assert!(result.is_err(), "Invalid JSON should fail");
450 let (status, body) = result.unwrap_err();
451 assert_eq!(status, StatusCode::BAD_REQUEST);
452 assert!(
453 body.contains("Invalid JSON"),
454 "Error message should mention invalid JSON"
455 );
456 }
457
458 #[tokio::test]
460 async fn test_panic_handling() {
461 let route = spikard_core::Route {
462 method: spikard_core::http::Method::Post,
463 path: "/test".to_string(),
464 handler_name: "test_handler".to_string(),
465 request_validator: None,
466 response_validator: None,
467 parameter_validator: None,
468 file_params: None,
469 is_async: true,
470 cors: None,
471 expects_json_body: false,
472 #[cfg(feature = "di")]
473 handler_dependencies: vec![],
474 jsonrpc_method: None,
475 };
476
477 let inner = Arc::new(PanicHandlerImpl);
478 let validator_handler = ValidatingHandler::new(inner, &route);
479
480 let request = Request::builder()
481 .method("POST")
482 .uri("/test")
483 .body(Body::empty())
484 .unwrap();
485
486 let request_data = create_request_data(json!({}));
487
488 let result = validator_handler.call(request, request_data).await;
489
490 assert!(result.is_err(), "Panicking handler should return error");
491 let (status, body) = result.unwrap_err();
492 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR, "Panic should return 500");
493
494 let error: serde_json::Value = serde_json::from_str(&body).expect("Should parse as JSON");
495 assert_eq!(error["code"], "panic");
496 assert_eq!(error["error"], "Unexpected panic in handler");
497 }
498
499 #[tokio::test]
501 async fn test_raw_body_parsing() {
502 let schema = json!({
503 "type": "object",
504 "properties": {
505 "name": {"type": "string"}
506 },
507 "required": ["name"]
508 });
509
510 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
511
512 let route = spikard_core::Route {
513 method: spikard_core::http::Method::Post,
514 path: "/test".to_string(),
515 handler_name: "test_handler".to_string(),
516 request_validator: Some(validator),
517 response_validator: None,
518 parameter_validator: None,
519 file_params: None,
520 is_async: true,
521 cors: None,
522 expects_json_body: true,
523 #[cfg(feature = "di")]
524 handler_dependencies: vec![],
525 jsonrpc_method: None,
526 };
527
528 let inner = Arc::new(SuccessEchoHandler);
529 let validator_handler = ValidatingHandler::new(inner, &route);
530
531 let request = Request::builder()
532 .method("POST")
533 .uri("/test")
534 .body(Body::empty())
535 .unwrap();
536
537 let raw_body_json = br#"{"name":"Bob"}"#;
538 let request_data = create_request_data_with_raw_body(raw_body_json.to_vec());
539
540 let result = validator_handler.call(request, request_data).await;
541
542 assert!(result.is_ok(), "Raw body should be parsed successfully");
543 let response = result.unwrap();
544 assert_eq!(response.status(), StatusCode::OK);
545 }
546
547 #[tokio::test]
549 async fn test_multiple_validation_errors() {
550 let schema = json!({
551 "type": "object",
552 "properties": {
553 "name": {"type": "string"},
554 "email": {"type": "string", "format": "email"},
555 "age": {"type": "integer", "minimum": 0}
556 },
557 "required": ["name", "email", "age"]
558 });
559
560 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
561
562 let route = spikard_core::Route {
563 method: spikard_core::http::Method::Post,
564 path: "/test".to_string(),
565 handler_name: "test_handler".to_string(),
566 request_validator: Some(validator),
567 response_validator: None,
568 parameter_validator: None,
569 file_params: None,
570 is_async: true,
571 cors: None,
572 expects_json_body: true,
573 #[cfg(feature = "di")]
574 handler_dependencies: vec![],
575 jsonrpc_method: None,
576 };
577
578 let inner = Arc::new(SuccessEchoHandler);
579 let validator_handler = ValidatingHandler::new(inner, &route);
580
581 let request = Request::builder()
582 .method("POST")
583 .uri("/test")
584 .body(Body::empty())
585 .unwrap();
586
587 let request_data = create_request_data(json!({"age": -5}));
588
589 let result = validator_handler.call(request, request_data).await;
590
591 assert!(result.is_err());
592 let (status, body) = result.unwrap_err();
593 assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
594
595 let problem: serde_json::Value = serde_json::from_str(&body).expect("Should parse as JSON");
596 let errors = problem["errors"].as_array().expect("Should have errors array");
597 assert!(
598 errors.len() >= 2,
599 "Should have multiple validation errors: got {}",
600 errors.len()
601 );
602 }
603
604 #[tokio::test]
606 async fn test_type_mismatch_validation() {
607 let schema = json!({
608 "type": "object",
609 "properties": {
610 "age": {"type": "integer"}
611 },
612 "required": ["age"]
613 });
614
615 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
616
617 let route = spikard_core::Route {
618 method: spikard_core::http::Method::Post,
619 path: "/test".to_string(),
620 handler_name: "test_handler".to_string(),
621 request_validator: Some(validator),
622 response_validator: None,
623 parameter_validator: None,
624 file_params: None,
625 is_async: true,
626 cors: None,
627 expects_json_body: true,
628 #[cfg(feature = "di")]
629 handler_dependencies: vec![],
630 jsonrpc_method: None,
631 };
632
633 let inner = Arc::new(SuccessEchoHandler);
634 let validator_handler = ValidatingHandler::new(inner, &route);
635
636 let request = Request::builder()
637 .method("POST")
638 .uri("/test")
639 .body(Body::empty())
640 .unwrap();
641
642 let request_data = create_request_data(json!({"age": "thirty"}));
643
644 let result = validator_handler.call(request, request_data).await;
645
646 assert!(result.is_err());
647 let (status, body) = result.unwrap_err();
648 assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
649
650 let problem: serde_json::Value = serde_json::from_str(&body).expect("Should parse as JSON");
651 let errors = problem["errors"].as_array().expect("Should have errors array");
652 assert!(!errors.is_empty());
653 assert_eq!(errors[0]["loc"][1], "age");
654 }
655
656 #[tokio::test]
658 async fn test_empty_body_validation_optional() {
659 let schema = json!({
660 "type": "object",
661 "properties": {
662 "name": {"type": "string"}
663 }
664 });
665
666 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
667
668 let route = spikard_core::Route {
669 method: spikard_core::http::Method::Post,
670 path: "/test".to_string(),
671 handler_name: "test_handler".to_string(),
672 request_validator: Some(validator),
673 response_validator: None,
674 parameter_validator: None,
675 file_params: None,
676 is_async: true,
677 cors: None,
678 expects_json_body: true,
679 #[cfg(feature = "di")]
680 handler_dependencies: vec![],
681 jsonrpc_method: None,
682 };
683
684 let inner = Arc::new(SuccessEchoHandler);
685 let validator_handler = ValidatingHandler::new(inner, &route);
686
687 let request = Request::builder()
688 .method("POST")
689 .uri("/test")
690 .body(Body::empty())
691 .unwrap();
692
693 let request_data = create_request_data(json!({}));
694
695 let result = validator_handler.call(request, request_data).await;
696
697 assert!(result.is_ok(), "Empty body should be valid when no fields are required");
698 }
699
700 #[tokio::test]
702 async fn test_parameter_validation_empty() {
703 let param_validator = spikard_core::ParameterValidator::new(json!({})).expect("Valid empty schema");
704
705 let route = spikard_core::Route {
706 method: spikard_core::http::Method::Get,
707 path: "/search".to_string(),
708 handler_name: "search_handler".to_string(),
709 request_validator: None,
710 response_validator: None,
711 parameter_validator: Some(param_validator),
712 file_params: None,
713 is_async: true,
714 cors: None,
715 expects_json_body: false,
716 #[cfg(feature = "di")]
717 handler_dependencies: vec![],
718 jsonrpc_method: None,
719 };
720
721 let inner = Arc::new(SuccessEchoHandler);
722 let validator_handler = ValidatingHandler::new(inner, &route);
723
724 let request = Request::builder()
725 .method("GET")
726 .uri("/search")
727 .body(Body::empty())
728 .unwrap();
729
730 let request_data = create_request_data(json!({}));
731
732 let result = validator_handler.call(request, request_data).await;
733
734 assert!(result.is_ok());
735 }
736
737 #[tokio::test]
739 async fn test_null_body_with_no_raw_body() {
740 let schema = json!({
741 "type": "object",
742 "properties": {
743 "name": {"type": "string"}
744 }
745 });
746
747 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
748
749 let route = spikard_core::Route {
750 method: spikard_core::http::Method::Post,
751 path: "/test".to_string(),
752 handler_name: "test_handler".to_string(),
753 request_validator: Some(validator),
754 response_validator: None,
755 parameter_validator: None,
756 file_params: None,
757 is_async: true,
758 cors: None,
759 expects_json_body: true,
760 #[cfg(feature = "di")]
761 handler_dependencies: vec![],
762 jsonrpc_method: None,
763 };
764
765 let inner = Arc::new(SuccessEchoHandler);
766 let validator_handler = ValidatingHandler::new(inner, &route);
767
768 let request = Request::builder()
769 .method("POST")
770 .uri("/test")
771 .body(Body::empty())
772 .unwrap();
773
774 let request_data = RequestData {
775 path_params: Arc::new(HashMap::new()),
776 query_params: Arc::new(json!({})),
777 validated_params: None,
778 raw_query_params: Arc::new(HashMap::new()),
779 body: Arc::new(Value::Null),
780 raw_body: None,
781 headers: Arc::new(HashMap::new()),
782 cookies: Arc::new(HashMap::new()),
783 method: "POST".to_string(),
784 path: "/test".to_string(),
785 #[cfg(feature = "di")]
786 dependencies: None,
787 };
788
789 let result = validator_handler.call(request, request_data).await;
790
791 assert!(result.is_err(), "Null body with no raw_body should fail");
792 }
793
794 #[tokio::test]
796 async fn test_panic_error_json_structure() {
797 let route = spikard_core::Route {
798 method: spikard_core::http::Method::Post,
799 path: "/test".to_string(),
800 handler_name: "test_handler".to_string(),
801 request_validator: None,
802 response_validator: None,
803 parameter_validator: None,
804 file_params: None,
805 is_async: true,
806 cors: None,
807 expects_json_body: false,
808 #[cfg(feature = "di")]
809 handler_dependencies: vec![],
810 jsonrpc_method: None,
811 };
812
813 let inner = Arc::new(PanicHandlerImpl);
814 let validator_handler = ValidatingHandler::new(inner, &route);
815
816 let request = Request::builder()
817 .method("POST")
818 .uri("/test")
819 .body(Body::empty())
820 .unwrap();
821
822 let request_data = create_request_data(json!({}));
823
824 let result = validator_handler.call(request, request_data).await;
825
826 assert!(result.is_err());
827 let (status, body) = result.unwrap_err();
828 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
829
830 let error: serde_json::Value = serde_json::from_str(&body).expect("Should parse as JSON");
831 assert!(error.get("error").is_some(), "Should have 'error' field");
832 assert!(error.get("code").is_some(), "Should have 'code' field");
833 assert_eq!(error["code"], "panic", "Code should be 'panic'");
834 }
835
836 #[tokio::test]
838 async fn test_handler_receives_correct_data() {
839 let route = spikard_core::Route {
840 method: spikard_core::http::Method::Post,
841 path: "/test".to_string(),
842 handler_name: "test_handler".to_string(),
843 request_validator: None,
844 response_validator: None,
845 parameter_validator: None,
846 file_params: None,
847 is_async: true,
848 cors: None,
849 expects_json_body: false,
850 #[cfg(feature = "di")]
851 handler_dependencies: vec![],
852 jsonrpc_method: None,
853 };
854
855 let inner = Arc::new(SuccessEchoHandler);
856 let validator_handler = ValidatingHandler::new(inner, &route);
857
858 let request = Request::builder()
859 .method("POST")
860 .uri("/test")
861 .body(Body::empty())
862 .unwrap();
863
864 let original_body = json!({"test": "data"});
865 let request_data = create_request_data(original_body.clone());
866
867 let result = validator_handler.call(request, request_data).await;
868
869 assert!(result.is_ok());
870 let response = result.unwrap();
871 assert_eq!(response.status(), StatusCode::OK);
872 }
873
874 #[tokio::test]
876 async fn test_raw_body_parsing_when_body_null() {
877 let schema = json!({
878 "type": "object",
879 "properties": {
880 "id": {"type": "integer"}
881 },
882 "required": ["id"]
883 });
884
885 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
886
887 let route = spikard_core::Route {
888 method: spikard_core::http::Method::Post,
889 path: "/test".to_string(),
890 handler_name: "test_handler".to_string(),
891 request_validator: Some(validator),
892 response_validator: None,
893 parameter_validator: None,
894 file_params: None,
895 is_async: true,
896 cors: None,
897 expects_json_body: true,
898 #[cfg(feature = "di")]
899 handler_dependencies: vec![],
900 jsonrpc_method: None,
901 };
902
903 let inner = Arc::new(SuccessEchoHandler);
904 let validator_handler = ValidatingHandler::new(inner, &route);
905
906 let request = Request::builder()
907 .method("POST")
908 .uri("/test")
909 .body(Body::empty())
910 .unwrap();
911
912 let request_data = RequestData {
913 path_params: Arc::new(HashMap::new()),
914 query_params: Arc::new(json!({})),
915 validated_params: None,
916 raw_query_params: Arc::new(HashMap::new()),
917 body: Arc::new(Value::Null),
918 raw_body: Some(bytes::Bytes::from(br#"{"id":42}"#.to_vec())),
919 headers: Arc::new(HashMap::new()),
920 cookies: Arc::new(HashMap::new()),
921 method: "POST".to_string(),
922 path: "/test".to_string(),
923 #[cfg(feature = "di")]
924 dependencies: None,
925 };
926
927 let result = validator_handler.call(request, request_data).await;
928
929 assert!(result.is_ok(), "Should parse raw_body and validate successfully");
930 let response = result.unwrap();
931 assert_eq!(response.status(), StatusCode::OK);
932 }
933
934 #[tokio::test]
936 async fn test_validation_error_status_code() {
937 let schema = json!({
938 "type": "object",
939 "properties": {
940 "count": {"type": "integer", "minimum": 1}
941 },
942 "required": ["count"]
943 });
944
945 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
946
947 let route = spikard_core::Route {
948 method: spikard_core::http::Method::Post,
949 path: "/test".to_string(),
950 handler_name: "test_handler".to_string(),
951 request_validator: Some(validator),
952 response_validator: None,
953 parameter_validator: None,
954 file_params: None,
955 is_async: true,
956 cors: None,
957 expects_json_body: true,
958 #[cfg(feature = "di")]
959 handler_dependencies: vec![],
960 jsonrpc_method: None,
961 };
962
963 let inner = Arc::new(SuccessEchoHandler);
964 let validator_handler = ValidatingHandler::new(inner, &route);
965
966 let request = Request::builder()
967 .method("POST")
968 .uri("/test")
969 .body(Body::empty())
970 .unwrap();
971
972 let request_data = create_request_data(json!({"count": 0}));
973
974 let result = validator_handler.call(request, request_data).await;
975
976 assert!(result.is_err());
977 let (status, _body) = result.unwrap_err();
978 assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
979 }
980
981 #[tokio::test]
983 async fn test_invalid_json_parsing_status() {
984 let schema = json!({"type": "object"});
985 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
986
987 let route = spikard_core::Route {
988 method: spikard_core::http::Method::Post,
989 path: "/test".to_string(),
990 handler_name: "test_handler".to_string(),
991 request_validator: Some(validator),
992 response_validator: None,
993 parameter_validator: None,
994 file_params: None,
995 is_async: true,
996 cors: None,
997 expects_json_body: true,
998 #[cfg(feature = "di")]
999 handler_dependencies: vec![],
1000 jsonrpc_method: None,
1001 };
1002
1003 let inner = Arc::new(SuccessEchoHandler);
1004 let validator_handler = ValidatingHandler::new(inner, &route);
1005
1006 let request = Request::builder()
1007 .method("POST")
1008 .uri("/test")
1009 .body(Body::empty())
1010 .unwrap();
1011
1012 let request_data = create_request_data_with_raw_body(b"[invalid]".to_vec());
1013
1014 let result = validator_handler.call(request, request_data).await;
1015
1016 assert!(result.is_err());
1017 let (status, _body) = result.unwrap_err();
1018 assert_eq!(status, StatusCode::BAD_REQUEST);
1019 }
1020
1021 #[tokio::test]
1023 async fn test_inner_handler_arc_cloning() {
1024 let route = spikard_core::Route {
1025 method: spikard_core::http::Method::Post,
1026 path: "/test".to_string(),
1027 handler_name: "test_handler".to_string(),
1028 request_validator: None,
1029 response_validator: None,
1030 parameter_validator: None,
1031 file_params: None,
1032 is_async: true,
1033 cors: None,
1034 expects_json_body: false,
1035 #[cfg(feature = "di")]
1036 handler_dependencies: vec![],
1037 jsonrpc_method: None,
1038 };
1039
1040 let inner = Arc::new(SuccessEchoHandler);
1041 let original_arc_ptr = Arc::as_ptr(&inner);
1042
1043 let validator_handler = ValidatingHandler::new(inner.clone(), &route);
1044
1045 let request = Request::builder()
1046 .method("POST")
1047 .uri("/test")
1048 .body(Body::empty())
1049 .unwrap();
1050
1051 let request_data = create_request_data(json!({"data": "test"}));
1052
1053 let result = validator_handler.call(request, request_data).await;
1054
1055 assert!(result.is_ok());
1056 assert_eq!(Arc::as_ptr(&inner), original_arc_ptr);
1057 }
1058
1059 #[tokio::test]
1061 async fn test_panic_error_serialization_fallback() {
1062 let route = spikard_core::Route {
1063 method: spikard_core::http::Method::Post,
1064 path: "/test".to_string(),
1065 handler_name: "test_handler".to_string(),
1066 request_validator: None,
1067 response_validator: None,
1068 parameter_validator: None,
1069 file_params: None,
1070 is_async: true,
1071 cors: None,
1072 expects_json_body: false,
1073 #[cfg(feature = "di")]
1074 handler_dependencies: vec![],
1075 jsonrpc_method: None,
1076 };
1077
1078 let inner = Arc::new(PanicHandlerImpl);
1079 let validator_handler = ValidatingHandler::new(inner, &route);
1080
1081 let request = Request::builder()
1082 .method("POST")
1083 .uri("/test")
1084 .body(Body::empty())
1085 .unwrap();
1086
1087 let request_data = create_request_data(json!({}));
1088
1089 let result = validator_handler.call(request, request_data).await;
1090
1091 assert!(result.is_err());
1092 let (_status, body) = result.unwrap_err();
1093
1094 assert!(
1095 body.contains("panic") || body.contains("Unexpected"),
1096 "Body should contain panic-related information"
1097 );
1098 }
1099
1100 #[tokio::test]
1102 async fn test_validation_error_body_is_json() {
1103 let schema = json!({
1104 "type": "object",
1105 "properties": {
1106 "email": {"type": "string", "format": "email"}
1107 },
1108 "required": ["email"]
1109 });
1110
1111 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
1112
1113 let route = spikard_core::Route {
1114 method: spikard_core::http::Method::Post,
1115 path: "/test".to_string(),
1116 handler_name: "test_handler".to_string(),
1117 request_validator: Some(validator),
1118 response_validator: None,
1119 parameter_validator: None,
1120 file_params: None,
1121 is_async: true,
1122 cors: None,
1123 expects_json_body: true,
1124 #[cfg(feature = "di")]
1125 handler_dependencies: vec![],
1126 jsonrpc_method: None,
1127 };
1128
1129 let inner = Arc::new(SuccessEchoHandler);
1130 let validator_handler = ValidatingHandler::new(inner, &route);
1131
1132 let request = Request::builder()
1133 .method("POST")
1134 .uri("/test")
1135 .body(Body::empty())
1136 .unwrap();
1137
1138 let request_data = create_request_data(json!({}));
1139
1140 let result = validator_handler.call(request, request_data).await;
1141
1142 assert!(result.is_err());
1143 let (_status, body) = result.unwrap_err();
1144
1145 let parsed: serde_json::Value = serde_json::from_str(&body).expect("Validation error body must be valid JSON");
1146 assert!(parsed.is_object(), "Validation error body should be a JSON object");
1147 }
1148
1149 #[tokio::test]
1151 async fn test_no_validators_executes_handler_directly() {
1152 let route = spikard_core::Route {
1153 method: spikard_core::http::Method::Post,
1154 path: "/test".to_string(),
1155 handler_name: "test_handler".to_string(),
1156 request_validator: None,
1157 response_validator: None,
1158 parameter_validator: None,
1159 file_params: None,
1160 is_async: true,
1161 cors: None,
1162 expects_json_body: false,
1163 #[cfg(feature = "di")]
1164 handler_dependencies: vec![],
1165 jsonrpc_method: None,
1166 };
1167
1168 let inner = Arc::new(SuccessEchoHandler);
1169 let validator_handler = ValidatingHandler::new(inner, &route);
1170
1171 let request = Request::builder()
1172 .method("POST")
1173 .uri("/test")
1174 .body(Body::empty())
1175 .unwrap();
1176
1177 let request_data = create_request_data(json!({"any": "data", "is": "ok"}));
1178
1179 let result = validator_handler.call(request, request_data).await;
1180
1181 assert!(result.is_ok(), "Without validators, any data should pass through");
1182 let response = result.unwrap();
1183 assert_eq!(response.status(), StatusCode::OK);
1184 }
1185
1186 #[tokio::test]
1188 async fn test_handler_with_path_headers_cookies() {
1189 let route = spikard_core::Route {
1190 method: spikard_core::http::Method::Get,
1191 path: "/api/{id}".to_string(),
1192 handler_name: "handler".to_string(),
1193 request_validator: None,
1194 response_validator: None,
1195 parameter_validator: None,
1196 file_params: None,
1197 is_async: true,
1198 cors: None,
1199 expects_json_body: false,
1200 #[cfg(feature = "di")]
1201 handler_dependencies: vec![],
1202 jsonrpc_method: None,
1203 };
1204
1205 let inner = Arc::new(SuccessEchoHandler);
1206 let validator_handler = ValidatingHandler::new(inner, &route);
1207
1208 let request = Request::builder()
1209 .method("GET")
1210 .uri("/api/123?search=test")
1211 .body(Body::empty())
1212 .unwrap();
1213
1214 let mut request_data = create_request_data(json!({}));
1215 request_data.path_params = Arc::new({
1216 let mut m = HashMap::new();
1217 m.insert("id".to_string(), "123".to_string());
1218 m
1219 });
1220 request_data.headers = Arc::new({
1221 let mut m = HashMap::new();
1222 m.insert("x-custom".to_string(), "header-value".to_string());
1223 m
1224 });
1225 request_data.cookies = Arc::new({
1226 let mut m = HashMap::new();
1227 m.insert("session".to_string(), "abc123".to_string());
1228 m
1229 });
1230
1231 let result = validator_handler.call(request, request_data).await;
1232
1233 assert!(result.is_ok());
1234 }
1235
1236 #[tokio::test]
1238 async fn test_panic_produces_500_status() {
1239 let route = spikard_core::Route {
1240 method: spikard_core::http::Method::Post,
1241 path: "/test".to_string(),
1242 handler_name: "test_handler".to_string(),
1243 request_validator: None,
1244 response_validator: None,
1245 parameter_validator: None,
1246 file_params: None,
1247 is_async: true,
1248 cors: None,
1249 expects_json_body: false,
1250 #[cfg(feature = "di")]
1251 handler_dependencies: vec![],
1252 jsonrpc_method: None,
1253 };
1254
1255 let inner = Arc::new(PanicHandlerImpl);
1256 let validator_handler = ValidatingHandler::new(inner, &route);
1257
1258 let request = Request::builder()
1259 .method("POST")
1260 .uri("/test")
1261 .body(Body::empty())
1262 .unwrap();
1263
1264 let request_data = create_request_data(json!({}));
1265
1266 let result = validator_handler.call(request, request_data).await;
1267
1268 assert!(result.is_err());
1269 let (status, _body) = result.unwrap_err();
1270 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
1271 }
1272
1273 #[tokio::test]
1275 async fn test_valid_json_invalid_schema() {
1276 let schema = json!({
1277 "type": "object",
1278 "properties": {
1279 "price": {"type": "number", "minimum": 0, "maximum": 1000}
1280 },
1281 "required": ["price"]
1282 });
1283
1284 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
1285
1286 let route = spikard_core::Route {
1287 method: spikard_core::http::Method::Post,
1288 path: "/test".to_string(),
1289 handler_name: "test_handler".to_string(),
1290 request_validator: Some(validator),
1291 response_validator: None,
1292 parameter_validator: None,
1293 file_params: None,
1294 is_async: true,
1295 cors: None,
1296 expects_json_body: true,
1297 #[cfg(feature = "di")]
1298 handler_dependencies: vec![],
1299 jsonrpc_method: None,
1300 };
1301
1302 let inner = Arc::new(SuccessEchoHandler);
1303 let validator_handler = ValidatingHandler::new(inner, &route);
1304
1305 let request = Request::builder()
1306 .method("POST")
1307 .uri("/test")
1308 .body(Body::empty())
1309 .unwrap();
1310
1311 let request_data = create_request_data(json!({"price": 2000.0}));
1312
1313 let result = validator_handler.call(request, request_data).await;
1314
1315 assert!(result.is_err(), "Should fail schema validation");
1316 let (status, _body) = result.unwrap_err();
1317 assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
1318 }
1319
1320 #[tokio::test]
1322 async fn test_empty_raw_body_bytes() {
1323 let schema = json!({
1324 "type": "object"
1325 });
1326
1327 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
1328
1329 let route = spikard_core::Route {
1330 method: spikard_core::http::Method::Post,
1331 path: "/test".to_string(),
1332 handler_name: "test_handler".to_string(),
1333 request_validator: Some(validator),
1334 response_validator: None,
1335 parameter_validator: None,
1336 file_params: None,
1337 is_async: true,
1338 cors: None,
1339 expects_json_body: true,
1340 #[cfg(feature = "di")]
1341 handler_dependencies: vec![],
1342 jsonrpc_method: None,
1343 };
1344
1345 let inner = Arc::new(SuccessEchoHandler);
1346 let validator_handler = ValidatingHandler::new(inner, &route);
1347
1348 let request = Request::builder()
1349 .method("POST")
1350 .uri("/test")
1351 .body(Body::empty())
1352 .unwrap();
1353
1354 let request_data = create_request_data_with_raw_body(vec![]);
1355
1356 let result = validator_handler.call(request, request_data).await;
1357
1358 assert!(result.is_err(), "Empty raw body should fail JSON parsing");
1359 let (status, _body) = result.unwrap_err();
1360 assert_eq!(status, StatusCode::BAD_REQUEST);
1361 }
1362
1363 #[tokio::test]
1365 async fn test_json_parsing_error_message() {
1366 let schema = json!({"type": "object"});
1367 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
1368
1369 let route = spikard_core::Route {
1370 method: spikard_core::http::Method::Post,
1371 path: "/test".to_string(),
1372 handler_name: "test_handler".to_string(),
1373 request_validator: Some(validator),
1374 response_validator: None,
1375 parameter_validator: None,
1376 file_params: None,
1377 is_async: true,
1378 cors: None,
1379 expects_json_body: true,
1380 #[cfg(feature = "di")]
1381 handler_dependencies: vec![],
1382 jsonrpc_method: None,
1383 };
1384
1385 let inner = Arc::new(SuccessEchoHandler);
1386 let validator_handler = ValidatingHandler::new(inner, &route);
1387
1388 let request = Request::builder()
1389 .method("POST")
1390 .uri("/test")
1391 .body(Body::empty())
1392 .unwrap();
1393
1394 let request_data = create_request_data_with_raw_body(b"not valid json}}".to_vec());
1395
1396 let result = validator_handler.call(request, request_data).await;
1397
1398 assert!(result.is_err());
1399 let (_status, body) = result.unwrap_err();
1400 assert!(
1401 body.contains("Invalid JSON"),
1402 "Error message should mention invalid JSON"
1403 );
1404 }
1405
1406 #[tokio::test]
1408 async fn test_nested_object_validation() {
1409 let schema = json!({
1410 "type": "object",
1411 "properties": {
1412 "user": {
1413 "type": "object",
1414 "properties": {
1415 "name": {"type": "string"},
1416 "age": {"type": "integer"}
1417 },
1418 "required": ["name"]
1419 }
1420 },
1421 "required": ["user"]
1422 });
1423
1424 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
1425
1426 let route = spikard_core::Route {
1427 method: spikard_core::http::Method::Post,
1428 path: "/test".to_string(),
1429 handler_name: "test_handler".to_string(),
1430 request_validator: Some(validator),
1431 response_validator: None,
1432 parameter_validator: None,
1433 file_params: None,
1434 is_async: true,
1435 cors: None,
1436 expects_json_body: true,
1437 #[cfg(feature = "di")]
1438 handler_dependencies: vec![],
1439 jsonrpc_method: None,
1440 };
1441
1442 let inner = Arc::new(SuccessEchoHandler);
1443 let validator_handler = ValidatingHandler::new(inner, &route);
1444
1445 let request = Request::builder()
1446 .method("POST")
1447 .uri("/test")
1448 .body(Body::empty())
1449 .unwrap();
1450
1451 let request_data = create_request_data(json!({"user": {"age": 30}}));
1452
1453 let result = validator_handler.call(request, request_data).await;
1454
1455 assert!(result.is_err());
1456 let (status, body) = result.unwrap_err();
1457 assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
1458
1459 let problem: serde_json::Value = serde_json::from_str(&body).expect("Should parse as JSON");
1460 assert!(problem["errors"].is_array(), "Should contain errors array");
1461 }
1462
1463 #[tokio::test]
1465 async fn test_array_validation() {
1466 let schema = json!({
1467 "type": "object",
1468 "properties": {
1469 "items": {
1470 "type": "array",
1471 "items": {"type": "string"}
1472 }
1473 },
1474 "required": ["items"]
1475 });
1476
1477 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
1478
1479 let route = spikard_core::Route {
1480 method: spikard_core::http::Method::Post,
1481 path: "/test".to_string(),
1482 handler_name: "test_handler".to_string(),
1483 request_validator: Some(validator),
1484 response_validator: None,
1485 parameter_validator: None,
1486 file_params: None,
1487 is_async: true,
1488 cors: None,
1489 expects_json_body: true,
1490 #[cfg(feature = "di")]
1491 handler_dependencies: vec![],
1492 jsonrpc_method: None,
1493 };
1494
1495 let inner = Arc::new(SuccessEchoHandler);
1496 let validator_handler = ValidatingHandler::new(inner, &route);
1497
1498 let request = Request::builder()
1499 .method("POST")
1500 .uri("/test")
1501 .body(Body::empty())
1502 .unwrap();
1503
1504 let request_data = create_request_data(json!({"items": ["a", "b", "c"]}));
1505
1506 let result = validator_handler.call(request, request_data).await;
1507
1508 assert!(result.is_ok(), "Valid array should pass validation");
1509 }
1510
1511 #[tokio::test]
1513 async fn test_array_wrong_item_type() {
1514 let schema = json!({
1515 "type": "object",
1516 "properties": {
1517 "tags": {
1518 "type": "array",
1519 "items": {"type": "string"}
1520 }
1521 },
1522 "required": ["tags"]
1523 });
1524
1525 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
1526
1527 let route = spikard_core::Route {
1528 method: spikard_core::http::Method::Post,
1529 path: "/test".to_string(),
1530 handler_name: "test_handler".to_string(),
1531 request_validator: Some(validator),
1532 response_validator: None,
1533 parameter_validator: None,
1534 file_params: None,
1535 is_async: true,
1536 cors: None,
1537 expects_json_body: true,
1538 #[cfg(feature = "di")]
1539 handler_dependencies: vec![],
1540 jsonrpc_method: None,
1541 };
1542
1543 let inner = Arc::new(SuccessEchoHandler);
1544 let validator_handler = ValidatingHandler::new(inner, &route);
1545
1546 let request = Request::builder()
1547 .method("POST")
1548 .uri("/test")
1549 .body(Body::empty())
1550 .unwrap();
1551
1552 let request_data = create_request_data(json!({"tags": ["tag1", 42, "tag3"]}));
1553
1554 let result = validator_handler.call(request, request_data).await;
1555
1556 assert!(result.is_err(), "Array with wrong item type should fail");
1557 let (status, _body) = result.unwrap_err();
1558 assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
1559 }
1560
1561 #[tokio::test]
1563 async fn test_concurrent_panic_handling() {
1564 let route = spikard_core::Route {
1565 method: spikard_core::http::Method::Post,
1566 path: "/test".to_string(),
1567 handler_name: "test_handler".to_string(),
1568 request_validator: None,
1569 response_validator: None,
1570 parameter_validator: None,
1571 file_params: None,
1572 is_async: true,
1573 cors: None,
1574 expects_json_body: false,
1575 #[cfg(feature = "di")]
1576 handler_dependencies: vec![],
1577 jsonrpc_method: None,
1578 };
1579
1580 let inner = Arc::new(PanicHandlerImpl);
1581 let validator_handler = Arc::new(ValidatingHandler::new(inner, &route));
1582
1583 let mut join_handles = vec![];
1584
1585 for i in 0..5 {
1586 let shared_handler = validator_handler.clone();
1587 let handle = tokio::spawn(async move {
1588 let request = Request::builder()
1589 .method("POST")
1590 .uri("/test")
1591 .body(Body::empty())
1592 .unwrap();
1593
1594 let request_data = create_request_data(json!({"id": i}));
1595
1596 let result = shared_handler.call(request, request_data).await;
1597 assert!(result.is_err(), "Each concurrent panic should be caught");
1598
1599 let (status, _body) = result.unwrap_err();
1600 assert_eq!(status, StatusCode::INTERNAL_SERVER_ERROR);
1601 });
1602
1603 join_handles.push(handle);
1604 }
1605
1606 for handle in join_handles {
1607 handle.await.expect("Concurrent test should complete");
1608 }
1609 }
1610
1611 #[tokio::test]
1613 async fn test_problem_details_status_code_mapping() {
1614 let schema = json!({
1615 "type": "object",
1616 "properties": {
1617 "required_field": {"type": "string"}
1618 },
1619 "required": ["required_field"]
1620 });
1621
1622 let validator = Arc::new(SchemaValidator::new(schema).unwrap());
1623
1624 let route = spikard_core::Route {
1625 method: spikard_core::http::Method::Post,
1626 path: "/test".to_string(),
1627 handler_name: "test_handler".to_string(),
1628 request_validator: Some(validator),
1629 response_validator: None,
1630 parameter_validator: None,
1631 file_params: None,
1632 is_async: true,
1633 cors: None,
1634 expects_json_body: true,
1635 #[cfg(feature = "di")]
1636 handler_dependencies: vec![],
1637 jsonrpc_method: None,
1638 };
1639
1640 let inner = Arc::new(SuccessEchoHandler);
1641 let validator_handler = ValidatingHandler::new(inner, &route);
1642
1643 let request = Request::builder()
1644 .method("POST")
1645 .uri("/test")
1646 .body(Body::empty())
1647 .unwrap();
1648
1649 let request_data = create_request_data(json!({}));
1650
1651 let result = validator_handler.call(request, request_data).await;
1652
1653 assert!(result.is_err());
1654 let (status, body) = result.unwrap_err();
1655
1656 assert_eq!(status, StatusCode::UNPROCESSABLE_ENTITY);
1657
1658 let problem: serde_json::Value = serde_json::from_str(&body).expect("Should parse as JSON");
1659 assert_eq!(problem["status"], 422);
1660 }
1661}