Skip to main content

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