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