spikard_http/server/
handler.rs

1//! ValidatingHandler wrapper that executes request/parameter validation before handler
2
3use 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
14/// Wrapper that runs request/parameter validation before calling the user handler.
15pub struct ValidatingHandler {
16    inner: Arc<dyn Handler>,
17    request_validator: Option<Arc<SchemaValidator>>,
18    parameter_validator: Option<ParameterValidator>,
19}
20
21impl ValidatingHandler {
22    /// Create a new validating handler wrapping the inner handler with schema validators
23    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    /// Create a minimal RequestData for testing
110    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    /// Create RequestData with raw body bytes
127    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    /// Success handler that echoes request body
144    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    /// Panic handler for testing panic recovery
164    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    /// Test 1: Handler with no validators passes through to inner handler
179    #[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    /// Test 1b: JSON body is parsed even without request schema validation.
216    #[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    /// Test 2: Request body validation - Valid input passes
272    #[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    /// Test 3: Request body validation - Invalid input returns 422
320    #[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    /// Test 4: JSON parsing error returns 400
381    #[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    /// Test 5: Panic handling - Inner handler panic is caught and returns 500
431    #[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    /// Test 6: Raw body parsing - Body is parsed on-demand from raw_body
472    #[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    /// Test 7: Multiple validation error details in response
520    #[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    /// Test 8: Type mismatch in request body
577    #[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    /// Test 9: Successfully validates empty body when not required
629    #[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    /// Test 10: Parameter validation with empty validators passes through
673    #[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    /// Test 11: Request body is null when raw_body is None
710    #[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    /// Test 12: Panic error serialization has correct JSON structure
766    #[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    /// Test 13: Handler receives request and request_data unchanged
808    #[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    /// Test 14: Raw body parsing when body is null and raw_body exists
846    #[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    /// Test 15: Validation error returns correct status code (422)
905    #[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    /// Test 16: Invalid JSON parsing returns 400 status
952    #[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    /// Test 17: Handler clones inner handler Arc correctly
992    #[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    /// Test 18: Panic during panic error serialization falls back to hardcoded JSON
1030    #[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    /// Test 19: Validation error body is valid JSON
1071    #[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    /// Test 20: No validators means handler executes without validation
1120    #[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    /// Test 21: Handler correctly uses path params, headers, and cookies from request data
1157    #[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    /// Test 22: Panic in handler produces correct status 500
1207    #[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    /// Test 23: Valid JSON but invalid schema should fail validation
1244    #[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    /// Test 24: Empty raw body bytes with validator
1291    #[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    /// Test 25: JSON parsing error message contains useful info
1334    #[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    /// Test 26: Nested object validation in request body
1377    #[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    /// Test 27: Array validation in request body
1434    #[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    /// Test 28: Array with wrong item type validation error
1482    #[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    /// Test 29: Unwind safety with concurrent handler execution
1532    #[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    /// Test 30: Problem details status code from validation error
1582    #[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}