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