Skip to main content

spikard_http/
handler_trait.rs

1//! Handler trait for language-agnostic request handling
2//!
3//! This module defines the core trait that all language bindings must implement.
4//! It's completely language-agnostic - no Python, Node, or WASM knowledge.
5
6use axum::{
7    body::Body,
8    http::{Request, Response, StatusCode},
9};
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12use std::collections::HashMap;
13use std::future::Future;
14use std::pin::Pin;
15
16/// Request data extracted from HTTP request
17/// This is the language-agnostic representation passed to handlers
18///
19/// Uses Arc for HashMaps to enable cheap cloning without duplicating data.
20/// When RequestData is cloned, only the Arc pointers are cloned, not the underlying data.
21///
22/// Performance optimization: raw_body stores the unparsed request body bytes.
23/// Language bindings should use raw_body when possible to avoid double-parsing.
24/// The body field is lazily parsed only when needed for validation.
25#[derive(Debug, Clone)]
26pub struct RequestData {
27    pub path_params: std::sync::Arc<HashMap<String, String>>,
28    pub query_params: std::sync::Arc<Value>,
29    /// Validated parameters produced by ParameterValidator (query/path/header/cookie combined).
30    pub validated_params: Option<std::sync::Arc<Value>>,
31    pub raw_query_params: std::sync::Arc<HashMap<String, Vec<String>>>,
32    pub body: std::sync::Arc<Value>,
33    pub raw_body: Option<bytes::Bytes>,
34    pub headers: std::sync::Arc<HashMap<String, String>>,
35    pub cookies: std::sync::Arc<HashMap<String, String>>,
36    pub method: String,
37    pub path: String,
38    /// Resolved dependencies for this request (populated by DependencyInjectingHandler)
39    #[cfg(feature = "di")]
40    pub dependencies: Option<std::sync::Arc<spikard_core::di::ResolvedDependencies>>,
41}
42
43impl Serialize for RequestData {
44    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
45    where
46        S: serde::Serializer,
47    {
48        use serde::ser::SerializeStruct;
49        #[cfg(feature = "di")]
50        let field_count = 11;
51        #[cfg(not(feature = "di"))]
52        let field_count = 10;
53
54        let mut state = serializer.serialize_struct("RequestData", field_count)?;
55        state.serialize_field("path_params", &*self.path_params)?;
56        state.serialize_field("query_params", &*self.query_params)?;
57        state.serialize_field("validated_params", &self.validated_params.as_deref())?;
58        state.serialize_field("raw_query_params", &*self.raw_query_params)?;
59        state.serialize_field("body", &*self.body)?;
60        state.serialize_field("raw_body", &self.raw_body.as_ref().map(|b| b.as_ref()))?;
61        state.serialize_field("headers", &*self.headers)?;
62        state.serialize_field("cookies", &*self.cookies)?;
63        state.serialize_field("method", &self.method)?;
64        state.serialize_field("path", &self.path)?;
65
66        #[cfg(feature = "di")]
67        {
68            state.serialize_field("has_dependencies", &self.dependencies.is_some())?;
69        }
70
71        state.end()
72    }
73}
74
75impl<'de> Deserialize<'de> for RequestData {
76    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77    where
78        D: serde::Deserializer<'de>,
79    {
80        #[derive(Deserialize)]
81        #[serde(field_identifier, rename_all = "snake_case")]
82        enum Field {
83            PathParams,
84            QueryParams,
85            RawQueryParams,
86            ValidatedParams,
87            Body,
88            RawBody,
89            Headers,
90            Cookies,
91            Method,
92            Path,
93            #[cfg(feature = "di")]
94            HasDependencies,
95        }
96
97        struct RequestDataVisitor;
98
99        impl<'de> serde::de::Visitor<'de> for RequestDataVisitor {
100            type Value = RequestData;
101
102            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
103                formatter.write_str("struct RequestData")
104            }
105
106            fn visit_map<V>(self, mut map: V) -> Result<RequestData, V::Error>
107            where
108                V: serde::de::MapAccess<'de>,
109            {
110                let mut path_params = None;
111                let mut query_params = None;
112                let mut raw_query_params = None;
113                let mut validated_params = None;
114                let mut body = None;
115                let mut raw_body = None;
116                let mut headers = None;
117                let mut cookies = None;
118                let mut method = None;
119                let mut path = None;
120
121                while let Some(key) = map.next_key()? {
122                    match key {
123                        Field::PathParams => {
124                            path_params = Some(std::sync::Arc::new(map.next_value()?));
125                        }
126                        Field::QueryParams => {
127                            query_params = Some(std::sync::Arc::new(map.next_value()?));
128                        }
129                        Field::RawQueryParams => {
130                            raw_query_params = Some(std::sync::Arc::new(map.next_value()?));
131                        }
132                        Field::ValidatedParams => {
133                            validated_params = Some(std::sync::Arc::new(map.next_value()?));
134                        }
135                        Field::Body => {
136                            body = Some(std::sync::Arc::new(map.next_value()?));
137                        }
138                        Field::RawBody => {
139                            let bytes_vec: Option<Vec<u8>> = map.next_value()?;
140                            raw_body = bytes_vec.map(bytes::Bytes::from);
141                        }
142                        Field::Headers => {
143                            headers = Some(std::sync::Arc::new(map.next_value()?));
144                        }
145                        Field::Cookies => {
146                            cookies = Some(std::sync::Arc::new(map.next_value()?));
147                        }
148                        Field::Method => {
149                            method = Some(map.next_value()?);
150                        }
151                        Field::Path => {
152                            path = Some(map.next_value()?);
153                        }
154                        #[cfg(feature = "di")]
155                        Field::HasDependencies => {
156                            let _: bool = map.next_value()?;
157                        }
158                    }
159                }
160
161                Ok(RequestData {
162                    path_params: path_params.ok_or_else(|| serde::de::Error::missing_field("path_params"))?,
163                    query_params: query_params.ok_or_else(|| serde::de::Error::missing_field("query_params"))?,
164                    raw_query_params: raw_query_params
165                        .ok_or_else(|| serde::de::Error::missing_field("raw_query_params"))?,
166                    validated_params,
167                    body: body.ok_or_else(|| serde::de::Error::missing_field("body"))?,
168                    raw_body,
169                    headers: headers.ok_or_else(|| serde::de::Error::missing_field("headers"))?,
170                    cookies: cookies.ok_or_else(|| serde::de::Error::missing_field("cookies"))?,
171                    method: method.ok_or_else(|| serde::de::Error::missing_field("method"))?,
172                    path: path.ok_or_else(|| serde::de::Error::missing_field("path"))?,
173                    #[cfg(feature = "di")]
174                    dependencies: None,
175                })
176            }
177        }
178
179        #[cfg(feature = "di")]
180        const FIELDS: &[&str] = &[
181            "path_params",
182            "query_params",
183            "validated_params",
184            "raw_query_params",
185            "body",
186            "raw_body",
187            "headers",
188            "cookies",
189            "method",
190            "path",
191            "has_dependencies",
192        ];
193
194        #[cfg(not(feature = "di"))]
195        const FIELDS: &[&str] = &[
196            "path_params",
197            "query_params",
198            "validated_params",
199            "raw_query_params",
200            "body",
201            "raw_body",
202            "headers",
203            "cookies",
204            "method",
205            "path",
206        ];
207
208        deserializer.deserialize_struct("RequestData", FIELDS, RequestDataVisitor)
209    }
210}
211
212/// Result type for handlers
213pub type HandlerResult = Result<Response<Body>, (StatusCode, String)>;
214
215/// Handler trait that all language bindings must implement
216///
217/// This trait is completely language-agnostic. Each binding (Python, Node, WASM)
218/// implements this trait to bridge their runtime to our HTTP server.
219pub trait Handler: Send + Sync {
220    /// Handle an HTTP request
221    ///
222    /// Takes the extracted request data and returns a future that resolves to either:
223    /// - Ok(Response): A successful HTTP response
224    /// - Err((StatusCode, String)): An error with status code and message
225    fn call(
226        &self,
227        request: Request<Body>,
228        request_data: RequestData,
229    ) -> Pin<Box<dyn Future<Output = HandlerResult> + Send + '_>>;
230
231    /// Whether this handler prefers consuming `RequestData::raw_body` over the parsed
232    /// `RequestData::body` for JSON requests.
233    ///
234    /// When `true`, the server may skip eager JSON parsing when there is no request-body
235    /// schema validator attached to the route.
236    fn prefers_raw_json_body(&self) -> bool {
237        false
238    }
239
240    /// Whether this handler wants to perform its own parameter validation/extraction (path/query/header/cookie).
241    ///
242    /// When `true`, the server will skip `ParameterValidator::validate_and_extract` in `ValidatingHandler`.
243    /// This is useful for language bindings which need to transform validated parameters into
244    /// language-specific values (e.g., Python kwargs) without duplicating work. When `false`,
245    /// the server stores validated output in `RequestData::validated_params`.
246    fn prefers_parameter_extraction(&self) -> bool {
247        false
248    }
249
250    /// Whether this handler needs the parsed headers map in `RequestData`.
251    ///
252    /// When `false`, the server may skip building `RequestData::headers` for requests without a body.
253    /// (Requests with bodies still typically need `Content-Type` decisions.)
254    fn wants_headers(&self) -> bool {
255        true
256    }
257
258    /// Whether this handler needs the parsed cookies map in `RequestData`.
259    ///
260    /// When `false`, the server may skip parsing cookies for requests without a body.
261    fn wants_cookies(&self) -> bool {
262        true
263    }
264
265    /// Whether this handler needs `RequestData` stored in request extensions.
266    ///
267    /// When `false`, the server avoids inserting `RequestData` into extensions to
268    /// skip cloning in hot paths.
269    fn wants_request_extensions(&self) -> bool {
270        false
271    }
272}
273
274/// Validated parameters from request (path, query, headers, cookies)
275#[derive(Debug, Clone)]
276pub struct ValidatedParams {
277    pub params: HashMap<String, Value>,
278}
279
280#[cfg(test)]
281mod tests {
282    use super::*;
283    use std::collections::HashMap;
284
285    fn minimal_request_data() -> RequestData {
286        RequestData {
287            path_params: std::sync::Arc::new(HashMap::new()),
288            query_params: std::sync::Arc::new(Value::Object(serde_json::Map::new())),
289            validated_params: None,
290            raw_query_params: std::sync::Arc::new(HashMap::new()),
291            body: std::sync::Arc::new(Value::Null),
292            raw_body: None,
293            headers: std::sync::Arc::new(HashMap::new()),
294            cookies: std::sync::Arc::new(HashMap::new()),
295            method: "GET".to_string(),
296            path: "/".to_string(),
297            #[cfg(feature = "di")]
298            dependencies: None,
299        }
300    }
301
302    #[test]
303    fn test_request_data_serialization_minimal() {
304        let data = minimal_request_data();
305
306        let json = serde_json::to_value(&data).expect("serialization failed");
307
308        assert!(json["path_params"].is_object());
309        assert!(json["query_params"].is_object());
310        assert!(json["raw_query_params"].is_object());
311        assert!(json["body"].is_null());
312        assert!(json["headers"].is_object());
313        assert!(json["cookies"].is_object());
314        assert_eq!(json["method"], "GET");
315        assert_eq!(json["path"], "/");
316    }
317
318    #[test]
319    fn test_request_data_serialization_with_path_params() {
320        let mut path_params = HashMap::new();
321        path_params.insert("id".to_string(), "123".to_string());
322        path_params.insert("username".to_string(), "alice".to_string());
323
324        let data = RequestData {
325            path_params: std::sync::Arc::new(path_params),
326            ..minimal_request_data()
327        };
328
329        let json = serde_json::to_value(&data).expect("serialization failed");
330
331        assert_eq!(json["path_params"]["id"], "123");
332        assert_eq!(json["path_params"]["username"], "alice");
333    }
334
335    #[test]
336    fn test_request_data_serialization_with_query_params() {
337        let query_params = serde_json::json!({
338            "filter": "active",
339            "limit": 10,
340            "sort": "name"
341        });
342
343        let data = RequestData {
344            query_params: std::sync::Arc::new(query_params),
345            ..minimal_request_data()
346        };
347
348        let json = serde_json::to_value(&data).expect("serialization failed");
349
350        assert_eq!(json["query_params"]["filter"], "active");
351        assert_eq!(json["query_params"]["limit"], 10);
352        assert_eq!(json["query_params"]["sort"], "name");
353    }
354
355    #[test]
356    fn test_request_data_serialization_with_raw_query_params() {
357        let mut raw_query_params = HashMap::new();
358        raw_query_params.insert("tags".to_string(), vec!["rust".to_string(), "web".to_string()]);
359        raw_query_params.insert("category".to_string(), vec!["backend".to_string()]);
360
361        let data = RequestData {
362            raw_query_params: std::sync::Arc::new(raw_query_params),
363            ..minimal_request_data()
364        };
365
366        let json = serde_json::to_value(&data).expect("serialization failed");
367
368        assert!(json["raw_query_params"]["tags"].is_array());
369        assert_eq!(json["raw_query_params"]["tags"][0], "rust");
370        assert_eq!(json["raw_query_params"]["tags"][1], "web");
371    }
372
373    #[test]
374    fn test_request_data_serialization_with_headers() {
375        let mut headers = HashMap::new();
376        headers.insert("content-type".to_string(), "application/json".to_string());
377        headers.insert("authorization".to_string(), "Bearer token123".to_string());
378        headers.insert("user-agent".to_string(), "test-client/1.0".to_string());
379
380        let data = RequestData {
381            headers: std::sync::Arc::new(headers),
382            ..minimal_request_data()
383        };
384
385        let json = serde_json::to_value(&data).expect("serialization failed");
386
387        assert_eq!(json["headers"]["content-type"], "application/json");
388        assert_eq!(json["headers"]["authorization"], "Bearer token123");
389        assert_eq!(json["headers"]["user-agent"], "test-client/1.0");
390    }
391
392    #[test]
393    fn test_request_data_serialization_with_cookies() {
394        let mut cookies = HashMap::new();
395        cookies.insert("session_id".to_string(), "abc123def456".to_string());
396        cookies.insert("preferences".to_string(), "dark_mode=true".to_string());
397
398        let data = RequestData {
399            cookies: std::sync::Arc::new(cookies),
400            ..minimal_request_data()
401        };
402
403        let json = serde_json::to_value(&data).expect("serialization failed");
404
405        assert_eq!(json["cookies"]["session_id"], "abc123def456");
406        assert_eq!(json["cookies"]["preferences"], "dark_mode=true");
407    }
408
409    #[test]
410    fn test_request_data_serialization_with_json_body() {
411        let body = serde_json::json!({
412            "name": "test",
413            "age": 30,
414            "active": true,
415            "tags": ["a", "b"]
416        });
417
418        let data = RequestData {
419            body: std::sync::Arc::new(body),
420            ..minimal_request_data()
421        };
422
423        let json = serde_json::to_value(&data).expect("serialization failed");
424
425        assert_eq!(json["body"]["name"], "test");
426        assert_eq!(json["body"]["age"], 30);
427        assert_eq!(json["body"]["active"], true);
428        assert!(json["body"]["tags"].is_array());
429    }
430
431    #[test]
432    fn test_request_data_serialization_with_raw_body() {
433        let raw_body = bytes::Bytes::from("raw body content");
434        let data = RequestData {
435            raw_body: Some(raw_body),
436            ..minimal_request_data()
437        };
438
439        let json = serde_json::to_value(&data).expect("serialization failed");
440
441        assert!(json["raw_body"].is_array());
442        let serialized_bytes: Vec<u8> =
443            serde_json::from_value(json["raw_body"].clone()).expect("failed to deserialize bytes");
444        assert_eq!(serialized_bytes, b"raw body content".to_vec());
445    }
446
447    #[test]
448    fn test_request_data_serialization_with_empty_strings() {
449        let mut headers = HashMap::new();
450        headers.insert("x-empty".to_string(), "".to_string());
451
452        let data = RequestData {
453            method: "".to_string(),
454            path: "".to_string(),
455            headers: std::sync::Arc::new(headers),
456            ..minimal_request_data()
457        };
458
459        let json = serde_json::to_value(&data).expect("serialization failed");
460
461        assert_eq!(json["method"], "");
462        assert_eq!(json["path"], "");
463        assert_eq!(json["headers"]["x-empty"], "");
464    }
465
466    #[test]
467    fn test_request_data_serialization_with_nested_json_body() {
468        let body = serde_json::json!({
469            "user": {
470                "profile": {
471                    "name": "Alice",
472                    "contact": {
473                        "email": "alice@example.com",
474                        "phone": null
475                    }
476                }
477            },
478            "settings": {
479                "notifications": [true, false, true]
480            }
481        });
482
483        let data = RequestData {
484            body: std::sync::Arc::new(body),
485            ..minimal_request_data()
486        };
487
488        let json = serde_json::to_value(&data).expect("serialization failed");
489
490        assert_eq!(json["body"]["user"]["profile"]["name"], "Alice");
491        assert_eq!(json["body"]["user"]["profile"]["contact"]["email"], "alice@example.com");
492        assert!(json["body"]["user"]["profile"]["contact"]["phone"].is_null());
493        assert_eq!(json["body"]["settings"]["notifications"][0], true);
494    }
495
496    #[test]
497    fn test_request_data_serialization_all_fields_complete() {
498        let mut path_params = HashMap::new();
499        path_params.insert("id".to_string(), "42".to_string());
500
501        let mut raw_query_params = HashMap::new();
502        raw_query_params.insert("filter".to_string(), vec!["active".to_string()]);
503
504        let mut headers = HashMap::new();
505        headers.insert("content-type".to_string(), "application/json".to_string());
506
507        let mut cookies = HashMap::new();
508        cookies.insert("session".to_string(), "xyz789".to_string());
509
510        let body = serde_json::json!({"action": "create"});
511        let raw_body = bytes::Bytes::from("body bytes");
512
513        let data = RequestData {
514            path_params: std::sync::Arc::new(path_params),
515            query_params: std::sync::Arc::new(serde_json::json!({"page": 1})),
516            validated_params: None,
517            raw_query_params: std::sync::Arc::new(raw_query_params),
518            body: std::sync::Arc::new(body),
519            raw_body: Some(raw_body),
520            headers: std::sync::Arc::new(headers),
521            cookies: std::sync::Arc::new(cookies),
522            method: "POST".to_string(),
523            path: "/api/users".to_string(),
524            #[cfg(feature = "di")]
525            dependencies: None,
526        };
527
528        let json = serde_json::to_value(&data).expect("serialization failed");
529
530        assert_eq!(json["path_params"]["id"], "42");
531        assert_eq!(json["query_params"]["page"], 1);
532        assert_eq!(json["raw_query_params"]["filter"][0], "active");
533        assert_eq!(json["body"]["action"], "create");
534        assert!(json["raw_body"].is_array());
535        assert_eq!(json["headers"]["content-type"], "application/json");
536        assert_eq!(json["cookies"]["session"], "xyz789");
537        assert_eq!(json["method"], "POST");
538        assert_eq!(json["path"], "/api/users");
539    }
540
541    #[test]
542    fn test_request_data_clone_shares_arc_data() {
543        let mut path_params = HashMap::new();
544        path_params.insert("id".to_string(), "original".to_string());
545
546        let data1 = RequestData {
547            path_params: std::sync::Arc::new(path_params),
548            ..minimal_request_data()
549        };
550
551        let data2 = data1.clone();
552
553        assert!(std::sync::Arc::ptr_eq(&data1.path_params, &data2.path_params));
554    }
555
556    #[test]
557    fn test_request_data_deserialization_complete() {
558        let json = serde_json::json!({
559            "path_params": {"id": "123"},
560            "query_params": {"filter": "active"},
561            "raw_query_params": {"tags": ["rust", "web"]},
562            "body": {"name": "test"},
563            "raw_body": null,
564            "headers": {"content-type": "application/json"},
565            "cookies": {"session": "abc"},
566            "method": "POST",
567            "path": "/api/test"
568        });
569
570        let data: RequestData = serde_json::from_value(json).expect("deserialization failed");
571
572        assert_eq!(data.path_params.get("id").unwrap(), "123");
573        assert_eq!(data.query_params["filter"], "active");
574        assert_eq!(data.raw_query_params.get("tags").unwrap()[0], "rust");
575        assert_eq!(data.body["name"], "test");
576        assert!(data.raw_body.is_none());
577        assert_eq!(data.headers.get("content-type").unwrap(), "application/json");
578        assert_eq!(data.cookies.get("session").unwrap(), "abc");
579        assert_eq!(data.method, "POST");
580        assert_eq!(data.path, "/api/test");
581    }
582
583    #[test]
584    fn test_request_data_deserialization_with_raw_body_bytes() {
585        let json = serde_json::json!({
586            "path_params": {},
587            "query_params": {},
588            "raw_query_params": {},
589            "body": null,
590            "raw_body": [72, 101, 108, 108, 111],
591            "headers": {},
592            "cookies": {},
593            "method": "GET",
594            "path": "/"
595        });
596
597        let data: RequestData = serde_json::from_value(json).expect("deserialization failed");
598
599        assert!(data.raw_body.is_some());
600        assert_eq!(data.raw_body.unwrap().as_ref(), b"Hello");
601    }
602
603    #[test]
604    fn test_request_data_deserialization_missing_required_field_path_params() {
605        let json = serde_json::json!({
606            "query_params": {},
607            "raw_query_params": {},
608            "body": null,
609            "headers": {},
610            "cookies": {},
611            "method": "GET",
612            "path": "/"
613        });
614
615        let result: Result<RequestData, _> = serde_json::from_value(json);
616        assert!(result.is_err());
617        assert!(result.unwrap_err().to_string().contains("path_params"));
618    }
619
620    #[test]
621    fn test_request_data_deserialization_missing_required_field_method() {
622        let json = serde_json::json!({
623            "path_params": {},
624            "query_params": {},
625            "raw_query_params": {},
626            "body": null,
627            "headers": {},
628            "cookies": {},
629            "path": "/"
630        });
631
632        let result: Result<RequestData, _> = serde_json::from_value(json);
633        assert!(result.is_err());
634        assert!(result.unwrap_err().to_string().contains("method"));
635    }
636
637    #[test]
638    fn test_request_data_serialization_roundtrip() {
639        let original = RequestData {
640            path_params: std::sync::Arc::new({
641                let mut map = HashMap::new();
642                map.insert("id".to_string(), "999".to_string());
643                map
644            }),
645            query_params: std::sync::Arc::new(serde_json::json!({"limit": 50, "offset": 10})),
646            validated_params: None,
647            raw_query_params: std::sync::Arc::new({
648                let mut map = HashMap::new();
649                map.insert("sort".to_string(), vec!["name".to_string(), "date".to_string()]);
650                map
651            }),
652            body: std::sync::Arc::new(serde_json::json!({"title": "New Post", "content": "Hello World"})),
653            raw_body: None,
654            headers: std::sync::Arc::new({
655                let mut map = HashMap::new();
656                map.insert("accept".to_string(), "application/json".to_string());
657                map
658            }),
659            cookies: std::sync::Arc::new({
660                let mut map = HashMap::new();
661                map.insert("user_id".to_string(), "user42".to_string());
662                map
663            }),
664            method: "PUT".to_string(),
665            path: "/blog/posts/999".to_string(),
666            #[cfg(feature = "di")]
667            dependencies: None,
668        };
669
670        let json = serde_json::to_value(&original).expect("serialization failed");
671        let restored: RequestData = serde_json::from_value(json).expect("deserialization failed");
672
673        assert_eq!(*original.path_params, *restored.path_params);
674        assert_eq!(original.query_params, restored.query_params);
675        assert_eq!(*original.raw_query_params, *restored.raw_query_params);
676        assert_eq!(original.body, restored.body);
677        assert_eq!(original.raw_body, restored.raw_body);
678        assert_eq!(*original.headers, *restored.headers);
679        assert_eq!(*original.cookies, *restored.cookies);
680        assert_eq!(original.method, restored.method);
681        assert_eq!(original.path, restored.path);
682    }
683
684    #[test]
685    fn test_request_data_serialization_large_body() {
686        let mut large_object = serde_json::Map::new();
687        for i in 0..100 {
688            large_object.insert(format!("key_{}", i), serde_json::Value::String(format!("value_{}", i)));
689        }
690
691        let data = RequestData {
692            body: std::sync::Arc::new(Value::Object(large_object)),
693            ..minimal_request_data()
694        };
695
696        let json = serde_json::to_value(&data).expect("serialization failed");
697
698        assert!(json["body"].is_object());
699        assert_eq!(json["body"].get("key_0").unwrap(), "value_0");
700        assert_eq!(json["body"].get("key_99").unwrap(), "value_99");
701    }
702
703    #[test]
704    fn test_request_data_empty_collections() {
705        let data = RequestData {
706            path_params: std::sync::Arc::new(HashMap::new()),
707            query_params: std::sync::Arc::new(Value::Object(serde_json::Map::new())),
708            validated_params: None,
709            raw_query_params: std::sync::Arc::new(HashMap::new()),
710            body: std::sync::Arc::new(Value::Object(serde_json::Map::new())),
711            raw_body: None,
712            headers: std::sync::Arc::new(HashMap::new()),
713            cookies: std::sync::Arc::new(HashMap::new()),
714            method: "GET".to_string(),
715            path: "/".to_string(),
716            #[cfg(feature = "di")]
717            dependencies: None,
718        };
719
720        let json = serde_json::to_value(&data).expect("serialization failed");
721
722        assert_eq!(json["path_params"].as_object().unwrap().len(), 0);
723        assert_eq!(json["query_params"].as_object().unwrap().len(), 0);
724        assert_eq!(json["raw_query_params"].as_object().unwrap().len(), 0);
725        assert_eq!(json["body"].as_object().unwrap().len(), 0);
726        assert!(json["raw_body"].is_null());
727        assert_eq!(json["headers"].as_object().unwrap().len(), 0);
728        assert_eq!(json["cookies"].as_object().unwrap().len(), 0);
729    }
730
731    #[test]
732    fn test_request_data_special_characters_in_strings() {
733        let mut headers = HashMap::new();
734        headers.insert("x-custom".to_string(), "value with \"quotes\"".to_string());
735        headers.insert("x-unicode".to_string(), "Café ☕ 🚀".to_string());
736
737        let data = RequestData {
738            method: "POST".to_string(),
739            path: "/api/v1/users\\test".to_string(),
740            headers: std::sync::Arc::new(headers),
741            body: std::sync::Arc::new(serde_json::json!({"note": "Contains\nnewline"})),
742            ..minimal_request_data()
743        };
744
745        let json = serde_json::to_value(&data).expect("serialization failed");
746
747        assert_eq!(json["headers"]["x-custom"], "value with \"quotes\"");
748        assert_eq!(json["headers"]["x-unicode"], "Café ☕ 🚀");
749        assert_eq!(json["path"], "/api/v1/users\\test");
750        assert_eq!(json["body"]["note"], "Contains\nnewline");
751    }
752
753    #[test]
754    #[cfg(feature = "di")]
755    fn test_request_data_serialization_with_di_feature_no_dependencies() {
756        let data = minimal_request_data();
757
758        let json = serde_json::to_value(&data).expect("serialization failed");
759
760        assert_eq!(json["has_dependencies"], false);
761    }
762
763    #[test]
764    fn test_request_data_method_variants() {
765        let methods = vec!["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"];
766
767        for method in methods {
768            let data = RequestData {
769                method: method.to_string(),
770                ..minimal_request_data()
771            };
772
773            let json = serde_json::to_value(&data).expect("serialization failed");
774
775            assert_eq!(json["method"], method);
776        }
777    }
778
779    #[test]
780    fn test_request_data_serialization_null_body() {
781        let data = RequestData {
782            body: std::sync::Arc::new(Value::Null),
783            ..minimal_request_data()
784        };
785
786        let json = serde_json::to_value(&data).expect("serialization failed");
787
788        assert!(json["body"].is_null());
789    }
790
791    #[test]
792    fn test_request_data_serialization_array_body() {
793        let data = RequestData {
794            body: std::sync::Arc::new(serde_json::json!([1, 2, 3, "four", {"five": 5}])),
795            ..minimal_request_data()
796        };
797
798        let json = serde_json::to_value(&data).expect("serialization failed");
799
800        assert!(json["body"].is_array());
801        assert_eq!(json["body"][0], 1);
802        assert_eq!(json["body"][1], 2);
803        assert_eq!(json["body"][3], "four");
804        assert_eq!(json["body"][4]["five"], 5);
805    }
806
807    #[test]
808    fn test_request_data_serialization_numeric_edge_cases() {
809        let data = RequestData {
810            body: std::sync::Arc::new(serde_json::json!({
811                "zero": 0,
812                "negative": -42,
813                "large": 9223372036854775807i64,
814                "float": 3.14159
815            })),
816            ..minimal_request_data()
817        };
818
819        let json = serde_json::to_value(&data).expect("serialization failed");
820
821        assert_eq!(json["body"]["zero"], 0);
822        assert_eq!(json["body"]["negative"], -42);
823        assert_eq!(json["body"]["large"], 9223372036854775807i64);
824        assert_eq!(json["body"]["float"], 3.14159);
825    }
826
827    #[test]
828    fn test_validated_params_basic_creation() {
829        let mut params = HashMap::new();
830        params.insert("id".to_string(), Value::String("123".to_string()));
831        params.insert("active".to_string(), Value::Bool(true));
832
833        let validated = ValidatedParams { params };
834
835        assert_eq!(validated.params.get("id").unwrap(), &Value::String("123".to_string()));
836        assert_eq!(validated.params.get("active").unwrap(), &Value::Bool(true));
837    }
838}