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