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