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