spikard_core/
request_data.rs

1//! Request data structures for HTTP handlers
2//!
3//! This module provides the `RequestData` type which represents extracted
4//! HTTP request data in a language-agnostic format.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11#[cfg(feature = "di")]
12use crate::di::ResolvedDependencies;
13#[cfg(feature = "di")]
14use bytes::Bytes;
15
16/// Request data extracted from HTTP request
17///
18/// This is the language-agnostic representation passed to handlers.
19///
20/// Uses Arc for HashMaps to enable cheap cloning without duplicating data.
21/// When RequestData is cloned, only the Arc pointers are cloned, not the underlying data.
22///
23/// Performance optimization: raw_body stores the unparsed request body bytes.
24/// Language bindings should use raw_body when possible to avoid double-parsing.
25/// The body field is lazily parsed only when needed for validation.
26#[derive(Debug, Clone)]
27pub struct RequestData {
28    /// Path parameters extracted from the URL path
29    pub path_params: Arc<HashMap<String, String>>,
30    /// Query parameters parsed as JSON
31    pub query_params: Value,
32    /// Validated parameters produced by ParameterValidator (query/path/header/cookie combined).
33    pub validated_params: Option<Value>,
34    /// Raw query parameters as key-value pairs
35    pub raw_query_params: Arc<HashMap<String, Vec<String>>>,
36    /// Parsed request body as JSON
37    pub body: Value,
38    /// Raw request body bytes (optional, for zero-copy access)
39    #[cfg(feature = "di")]
40    pub raw_body: Option<Bytes>,
41    #[cfg(not(feature = "di"))]
42    pub raw_body: Option<Vec<u8>>,
43    /// Request headers
44    pub headers: Arc<HashMap<String, String>>,
45    /// Request cookies
46    pub cookies: Arc<HashMap<String, String>>,
47    /// HTTP method (GET, POST, etc.)
48    pub method: String,
49    /// Request path
50    pub path: String,
51    /// Resolved dependencies for this request (populated by DI handlers)
52    #[cfg(feature = "di")]
53    pub dependencies: Option<Arc<ResolvedDependencies>>,
54}
55
56impl Serialize for RequestData {
57    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
58    where
59        S: serde::Serializer,
60    {
61        use serde::ser::SerializeStruct;
62        let mut state = serializer.serialize_struct("RequestData", 10)?;
63        state.serialize_field("path_params", &*self.path_params)?;
64        state.serialize_field("query_params", &self.query_params)?;
65        state.serialize_field("validated_params", &self.validated_params)?;
66        state.serialize_field("raw_query_params", &*self.raw_query_params)?;
67        state.serialize_field("body", &self.body)?;
68        #[cfg(feature = "di")]
69        state.serialize_field("raw_body", &self.raw_body.as_ref().map(|b| b.as_ref()))?;
70        #[cfg(not(feature = "di"))]
71        state.serialize_field("raw_body", &self.raw_body)?;
72        state.serialize_field("headers", &*self.headers)?;
73        state.serialize_field("cookies", &*self.cookies)?;
74        state.serialize_field("method", &self.method)?;
75        state.serialize_field("path", &self.path)?;
76        state.end()
77    }
78}
79
80impl<'de> Deserialize<'de> for RequestData {
81    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
82    where
83        D: serde::Deserializer<'de>,
84    {
85        #[derive(Deserialize)]
86        #[serde(field_identifier, rename_all = "snake_case")]
87        enum Field {
88            PathParams,
89            QueryParams,
90            ValidatedParams,
91            RawQueryParams,
92            Body,
93            RawBody,
94            Headers,
95            Cookies,
96            Method,
97            Path,
98        }
99
100        struct RequestDataVisitor;
101
102        impl<'de> serde::de::Visitor<'de> for RequestDataVisitor {
103            type Value = RequestData;
104
105            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
106                formatter.write_str("struct RequestData")
107            }
108
109            fn visit_map<V>(self, mut map: V) -> Result<RequestData, V::Error>
110            where
111                V: serde::de::MapAccess<'de>,
112            {
113                let mut path_params = None;
114                let mut query_params = None;
115                let mut validated_params = None;
116                let mut raw_query_params = None;
117                let mut body = None;
118                let mut raw_body = None;
119                let mut headers = None;
120                let mut cookies = None;
121                let mut method = None;
122                let mut path = None;
123
124                while let Some(key) = map.next_key()? {
125                    match key {
126                        Field::PathParams => {
127                            path_params = Some(Arc::new(map.next_value()?));
128                        }
129                        Field::QueryParams => {
130                            query_params = Some(map.next_value()?);
131                        }
132                        Field::ValidatedParams => {
133                            validated_params = Some(map.next_value()?);
134                        }
135                        Field::RawQueryParams => {
136                            raw_query_params = Some(Arc::new(map.next_value()?));
137                        }
138                        Field::Body => {
139                            body = Some(map.next_value()?);
140                        }
141                        Field::RawBody => {
142                            let bytes_vec: Option<Vec<u8>> = map.next_value()?;
143                            #[cfg(feature = "di")]
144                            {
145                                raw_body = bytes_vec.map(Bytes::from);
146                            }
147                            #[cfg(not(feature = "di"))]
148                            {
149                                raw_body = bytes_vec;
150                            }
151                        }
152                        Field::Headers => {
153                            headers = Some(Arc::new(map.next_value()?));
154                        }
155                        Field::Cookies => {
156                            cookies = Some(Arc::new(map.next_value()?));
157                        }
158                        Field::Method => {
159                            method = Some(map.next_value()?);
160                        }
161                        Field::Path => {
162                            path = Some(map.next_value()?);
163                        }
164                    }
165                }
166
167                Ok(RequestData {
168                    path_params: path_params.ok_or_else(|| serde::de::Error::missing_field("path_params"))?,
169                    query_params: query_params.ok_or_else(|| serde::de::Error::missing_field("query_params"))?,
170                    validated_params,
171                    raw_query_params: raw_query_params
172                        .ok_or_else(|| serde::de::Error::missing_field("raw_query_params"))?,
173                    body: body.ok_or_else(|| serde::de::Error::missing_field("body"))?,
174                    raw_body,
175                    headers: headers.ok_or_else(|| serde::de::Error::missing_field("headers"))?,
176                    cookies: cookies.ok_or_else(|| serde::de::Error::missing_field("cookies"))?,
177                    method: method.ok_or_else(|| serde::de::Error::missing_field("method"))?,
178                    path: path.ok_or_else(|| serde::de::Error::missing_field("path"))?,
179                    #[cfg(feature = "di")]
180                    dependencies: None,
181                })
182            }
183        }
184
185        const FIELDS: &[&str] = &[
186            "path_params",
187            "query_params",
188            "validated_params",
189            "raw_query_params",
190            "body",
191            "raw_body",
192            "headers",
193            "cookies",
194            "method",
195            "path",
196        ];
197        deserializer.deserialize_struct("RequestData", FIELDS, RequestDataVisitor)
198    }
199}
200
201#[cfg(test)]
202mod tests {
203    use super::*;
204    use serde_json::{Value, json};
205
206    #[derive(Default)]
207    struct RequestDataCollections {
208        raw_query_params: HashMap<String, Vec<String>>,
209        headers: HashMap<String, String>,
210        cookies: HashMap<String, String>,
211        path_params: HashMap<String, String>,
212    }
213
214    fn create_request_data(
215        path: &str,
216        method: &str,
217        body: Value,
218        query_params: Value,
219        collections: RequestDataCollections,
220    ) -> RequestData {
221        RequestData {
222            path_params: Arc::new(collections.path_params),
223            query_params,
224            validated_params: None,
225            raw_query_params: Arc::new(collections.raw_query_params),
226            body,
227            raw_body: None,
228            headers: Arc::new(collections.headers),
229            cookies: Arc::new(collections.cookies),
230            method: method.to_string(),
231            path: path.to_string(),
232            #[cfg(feature = "di")]
233            dependencies: None,
234        }
235    }
236
237    #[test]
238    fn test_request_data_minimal() {
239        let data = create_request_data(
240            "/api/users",
241            "GET",
242            json!(null),
243            json!({}),
244            RequestDataCollections::default(),
245        );
246
247        assert_eq!(data.path, "/api/users");
248        assert_eq!(data.method, "GET");
249        assert_eq!(data.body, json!(null));
250    }
251
252    #[test]
253    fn test_request_data_with_json_body() {
254        let body = json!({
255            "name": "test_user",
256            "email": "test@example.com",
257            "age": 30
258        });
259
260        let data = create_request_data(
261            "/api/users",
262            "POST",
263            body.clone(),
264            json!({}),
265            RequestDataCollections::default(),
266        );
267
268        assert_eq!(data.body, body);
269        assert_eq!(data.body["name"], "test_user");
270        assert_eq!(data.body["email"], "test@example.com");
271        assert_eq!(data.body["age"], 30);
272    }
273
274    #[test]
275    fn test_request_data_with_query_params() {
276        let mut raw_query_params = HashMap::new();
277        raw_query_params.insert("page".to_string(), vec!["1".to_string()]);
278        raw_query_params.insert("limit".to_string(), vec!["10".to_string()]);
279        raw_query_params.insert("tags".to_string(), vec!["rust".to_string(), "web".to_string()]);
280
281        let query_params = json!({
282            "page": "1",
283            "limit": "10",
284            "tags": ["rust", "web"]
285        });
286
287        let data = create_request_data(
288            "/api/users",
289            "GET",
290            json!(null),
291            query_params,
292            RequestDataCollections {
293                raw_query_params,
294                ..Default::default()
295            },
296        );
297
298        assert_eq!(data.raw_query_params.get("page"), Some(&vec!["1".to_string()]));
299        assert_eq!(data.raw_query_params.get("limit"), Some(&vec!["10".to_string()]));
300        assert_eq!(
301            data.raw_query_params.get("tags"),
302            Some(&vec!["rust".to_string(), "web".to_string()])
303        );
304    }
305
306    #[test]
307    fn test_request_data_with_headers() {
308        let mut headers = HashMap::new();
309        headers.insert("content-type".to_string(), "application/json".to_string());
310        headers.insert("authorization".to_string(), "Bearer token123".to_string());
311        headers.insert("user-agent".to_string(), "Mozilla/5.0".to_string());
312
313        let data = create_request_data(
314            "/api/users",
315            "GET",
316            json!(null),
317            json!({}),
318            RequestDataCollections {
319                headers,
320                ..Default::default()
321            },
322        );
323
324        assert_eq!(data.headers.get("content-type"), Some(&"application/json".to_string()));
325        assert_eq!(data.headers.get("authorization"), Some(&"Bearer token123".to_string()));
326        assert_eq!(data.headers.get("user-agent"), Some(&"Mozilla/5.0".to_string()));
327    }
328
329    #[test]
330    fn test_request_data_with_cookies() {
331        let mut cookies = HashMap::new();
332        cookies.insert("session_id".to_string(), "abc123xyz".to_string());
333        cookies.insert("user_id".to_string(), "user_42".to_string());
334        cookies.insert("theme".to_string(), "dark".to_string());
335
336        let data = create_request_data(
337            "/api/users",
338            "GET",
339            json!(null),
340            json!({}),
341            RequestDataCollections {
342                cookies,
343                ..Default::default()
344            },
345        );
346
347        assert_eq!(data.cookies.get("session_id"), Some(&"abc123xyz".to_string()));
348        assert_eq!(data.cookies.get("user_id"), Some(&"user_42".to_string()));
349        assert_eq!(data.cookies.get("theme"), Some(&"dark".to_string()));
350    }
351
352    #[test]
353    fn test_request_data_with_path_params() {
354        let mut path_params = HashMap::new();
355        path_params.insert("user_id".to_string(), "123".to_string());
356        path_params.insert("post_id".to_string(), "456".to_string());
357
358        let data = create_request_data(
359            "/api/users/123/posts/456",
360            "GET",
361            json!(null),
362            json!({}),
363            RequestDataCollections {
364                path_params,
365                ..Default::default()
366            },
367        );
368
369        assert_eq!(data.path_params.get("user_id"), Some(&"123".to_string()));
370        assert_eq!(data.path_params.get("post_id"), Some(&"456".to_string()));
371    }
372
373    #[test]
374    fn test_request_data_all_fields_populated() {
375        let mut path_params = HashMap::new();
376        path_params.insert("id".to_string(), "user_99".to_string());
377
378        let mut raw_query_params = HashMap::new();
379        raw_query_params.insert("filter".to_string(), vec!["active".to_string()]);
380
381        let mut headers = HashMap::new();
382        headers.insert("content-type".to_string(), "application/json".to_string());
383
384        let mut cookies = HashMap::new();
385        cookies.insert("session".to_string(), "xyz789".to_string());
386
387        let body = json!({
388            "name": "John",
389            "email": "john@example.com"
390        });
391
392        let query_params = json!({
393            "filter": "active"
394        });
395
396        let data = create_request_data(
397            "/api/users/user_99",
398            "PUT",
399            body,
400            query_params,
401            RequestDataCollections {
402                raw_query_params,
403                headers,
404                cookies,
405                path_params,
406            },
407        );
408
409        assert_eq!(data.path, "/api/users/user_99");
410        assert_eq!(data.method, "PUT");
411        assert_eq!(data.path_params.get("id"), Some(&"user_99".to_string()));
412        assert_eq!(data.body["name"], "John");
413        assert_eq!(data.headers.get("content-type"), Some(&"application/json".to_string()));
414        assert_eq!(data.cookies.get("session"), Some(&"xyz789".to_string()));
415    }
416
417    #[test]
418    fn test_request_data_empty_collections() {
419        let data = create_request_data(
420            "/api/test",
421            "GET",
422            json!(null),
423            json!({}),
424            RequestDataCollections::default(),
425        );
426
427        assert!(data.path_params.is_empty());
428        assert!(data.raw_query_params.is_empty());
429        assert!(data.headers.is_empty());
430        assert!(data.cookies.is_empty());
431    }
432
433    #[test]
434    fn test_request_data_clone() {
435        let mut path_params = HashMap::new();
436        path_params.insert("id".to_string(), "123".to_string());
437
438        let data1 = create_request_data(
439            "/api/users/123",
440            "GET",
441            json!({"name": "test"}),
442            json!({}),
443            RequestDataCollections {
444                path_params,
445                ..Default::default()
446            },
447        );
448
449        let data2 = data1.clone();
450
451        assert_eq!(data1.path, data2.path);
452        assert_eq!(data1.method, data2.method);
453        assert_eq!(data1.body, data2.body);
454        assert_eq!(data1.path_params, data2.path_params);
455    }
456
457    #[test]
458    fn test_request_data_various_http_methods() {
459        let methods = vec!["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
460
461        for method in methods {
462            let data = create_request_data(
463                "/api/test",
464                method,
465                json!(null),
466                json!({}),
467                RequestDataCollections::default(),
468            );
469
470            assert_eq!(data.method, method);
471        }
472    }
473
474    #[test]
475    fn test_request_data_raw_body_none() {
476        let data = create_request_data(
477            "/api/test",
478            "GET",
479            json!(null),
480            json!({}),
481            RequestDataCollections::default(),
482        );
483
484        assert!(data.raw_body.is_none());
485    }
486
487    #[cfg(not(feature = "di"))]
488    #[test]
489    fn test_request_data_raw_body_some() {
490        let raw_body_bytes = vec![1, 2, 3, 4, 5];
491        let mut data = create_request_data(
492            "/api/test",
493            "POST",
494            json!(null),
495            json!({}),
496            RequestDataCollections::default(),
497        );
498
499        data.raw_body = Some(raw_body_bytes.clone());
500        assert_eq!(data.raw_body, Some(raw_body_bytes));
501    }
502
503    #[cfg(not(feature = "di"))]
504    #[test]
505    fn request_data_serializes_and_deserializes() {
506        let mut headers = HashMap::new();
507        headers.insert("content-type".to_string(), "application/json".to_string());
508
509        let mut cookies = HashMap::new();
510        cookies.insert("session".to_string(), "abc".to_string());
511
512        let mut raw_query_params = HashMap::new();
513        raw_query_params.insert("tags".to_string(), vec!["rust".to_string(), "http".to_string()]);
514
515        let mut path_params = HashMap::new();
516        path_params.insert("id".to_string(), "123".to_string());
517
518        let mut data = create_request_data(
519            "/api/items/123",
520            "POST",
521            json!({"name": "demo"}),
522            json!({"tags": ["rust", "http"]}),
523            RequestDataCollections {
524                raw_query_params,
525                headers,
526                cookies,
527                path_params,
528            },
529        );
530        data.raw_body = Some(vec![9, 8, 7]);
531
532        let encoded = serde_json::to_string(&data).expect("serialize RequestData");
533        let decoded: RequestData = serde_json::from_str(&encoded).expect("deserialize RequestData");
534
535        assert_eq!(decoded.path, "/api/items/123");
536        assert_eq!(decoded.method, "POST");
537        assert_eq!(decoded.body["name"], "demo");
538        assert_eq!(decoded.query_params["tags"][0], "rust");
539        assert_eq!(
540            decoded.raw_query_params.get("tags").unwrap(),
541            &vec!["rust".to_string(), "http".to_string()]
542        );
543        assert_eq!(decoded.headers.get("content-type").unwrap(), "application/json");
544        assert_eq!(decoded.cookies.get("session").unwrap(), "abc");
545        assert_eq!(decoded.path_params.get("id").unwrap(), "123");
546        assert_eq!(decoded.raw_body, Some(vec![9, 8, 7]));
547    }
548
549    #[test]
550    fn test_request_data_multiple_query_param_values() {
551        let mut raw_query_params = HashMap::new();
552        raw_query_params.insert(
553            "colors".to_string(),
554            vec!["red".to_string(), "green".to_string(), "blue".to_string()],
555        );
556
557        let data = create_request_data(
558            "/api/items",
559            "GET",
560            json!(null),
561            json!({}),
562            RequestDataCollections {
563                raw_query_params,
564                ..Default::default()
565            },
566        );
567
568        let colors = data.raw_query_params.get("colors").unwrap();
569        assert_eq!(colors.len(), 3);
570        assert!(colors.contains(&"red".to_string()));
571        assert!(colors.contains(&"green".to_string()));
572        assert!(colors.contains(&"blue".to_string()));
573    }
574
575    #[test]
576    fn test_request_data_complex_json_body() {
577        let body = json!({
578            "user": {
579                "name": "Alice",
580                "profile": {
581                    "age": 28,
582                    "location": "San Francisco"
583                }
584            },
585            "tags": ["admin", "active"],
586            "metadata": {
587                "created_at": "2024-01-01",
588                "updated_at": "2024-12-01"
589            }
590        });
591
592        let data = create_request_data("/api/users", "POST", body, json!({}), RequestDataCollections::default());
593
594        assert_eq!(data.body["user"]["name"], "Alice");
595        assert_eq!(data.body["user"]["profile"]["age"], 28);
596        assert_eq!(data.body["tags"][0], "admin");
597        assert_eq!(data.body["metadata"]["created_at"], "2024-01-01");
598    }
599
600    #[test]
601    fn test_request_data_special_characters_in_paths() {
602        let data = create_request_data(
603            "/api/users/john@example.com/posts",
604            "GET",
605            json!(null),
606            json!({}),
607            RequestDataCollections::default(),
608        );
609
610        assert_eq!(data.path, "/api/users/john@example.com/posts");
611    }
612
613    #[test]
614    fn test_request_data_special_characters_in_params() {
615        let mut path_params = HashMap::new();
616        path_params.insert("email".to_string(), "test@example.com".to_string());
617        path_params.insert("slug".to_string(), "my-post-title".to_string());
618
619        let data = create_request_data(
620            "/api/users/test@example.com",
621            "GET",
622            json!(null),
623            json!({}),
624            RequestDataCollections {
625                path_params,
626                ..Default::default()
627            },
628        );
629
630        assert_eq!(data.path_params.get("email"), Some(&"test@example.com".to_string()));
631        assert_eq!(data.path_params.get("slug"), Some(&"my-post-title".to_string()));
632    }
633
634    #[test]
635    fn test_request_data_null_and_empty_values() {
636        let body = json!({
637            "name": null,
638            "email": "",
639            "age": 0,
640            "active": false
641        });
642
643        let data = create_request_data("/api/users", "POST", body, json!({}), RequestDataCollections::default());
644
645        assert!(data.body["name"].is_null());
646        assert_eq!(data.body["email"], "");
647        assert_eq!(data.body["age"], 0);
648        assert_eq!(data.body["active"], false);
649    }
650
651    #[test]
652    fn test_request_data_serialization() {
653        let mut path_params = HashMap::new();
654        path_params.insert("id".to_string(), "123".to_string());
655
656        let mut headers = HashMap::new();
657        headers.insert("content-type".to_string(), "application/json".to_string());
658
659        let data = create_request_data(
660            "/api/users/123",
661            "GET",
662            json!({"name": "test"}),
663            json!({"page": "1"}),
664            RequestDataCollections {
665                headers,
666                path_params,
667                ..Default::default()
668            },
669        );
670
671        let serialized = serde_json::to_string(&data);
672        assert!(serialized.is_ok());
673    }
674
675    #[test]
676    fn test_request_data_delete_request() {
677        let mut path_params = HashMap::new();
678        path_params.insert("id".to_string(), "999".to_string());
679
680        let data = create_request_data(
681            "/api/users/999",
682            "DELETE",
683            json!(null),
684            json!({}),
685            RequestDataCollections {
686                path_params,
687                ..Default::default()
688            },
689        );
690
691        assert_eq!(data.method, "DELETE");
692        assert_eq!(data.path_params.get("id"), Some(&"999".to_string()));
693    }
694
695    #[test]
696    fn test_request_data_array_body() {
697        let body = json!([
698            {"id": 1, "name": "item1"},
699            {"id": 2, "name": "item2"},
700            {"id": 3, "name": "item3"}
701        ]);
702
703        let data = create_request_data("/api/items", "POST", body, json!({}), RequestDataCollections::default());
704
705        assert!(data.body.is_array());
706        assert_eq!(data.body.as_array().unwrap().len(), 3);
707        assert_eq!(data.body[0]["id"], 1);
708        assert_eq!(data.body[2]["name"], "item3");
709    }
710
711    #[test]
712    fn test_request_data_case_sensitive_keys() {
713        let mut headers = HashMap::new();
714        headers.insert("Content-Type".to_string(), "application/json".to_string());
715        headers.insert("content-type".to_string(), "text/plain".to_string());
716
717        let data = create_request_data(
718            "/api/test",
719            "GET",
720            json!(null),
721            json!({}),
722            RequestDataCollections {
723                headers,
724                ..Default::default()
725            },
726        );
727
728        assert_eq!(data.headers.get("Content-Type"), Some(&"application/json".to_string()));
729        assert_eq!(data.headers.get("content-type"), Some(&"text/plain".to_string()));
730    }
731
732    #[test]
733    fn test_request_data_serialization_with_all_fields() {
734        let mut path_params = HashMap::new();
735        path_params.insert("id".to_string(), "42".to_string());
736
737        let mut raw_query_params = HashMap::new();
738        raw_query_params.insert("page".to_string(), vec!["2".to_string()]);
739
740        let mut headers = HashMap::new();
741        headers.insert("authorization".to_string(), "Bearer xyz".to_string());
742
743        let mut cookies = HashMap::new();
744        cookies.insert("token".to_string(), "abc123".to_string());
745
746        let data = create_request_data(
747            "/api/resource/42",
748            "PUT",
749            json!({"status": "updated"}),
750            json!({"page": "2"}),
751            RequestDataCollections {
752                raw_query_params,
753                headers,
754                cookies,
755                path_params,
756            },
757        );
758
759        let serialized = serde_json::to_string(&data).expect("serialization failed");
760        assert!(!serialized.is_empty());
761        assert!(serialized.contains("\"id\":\"42\""));
762        assert!(serialized.contains("PUT"));
763    }
764
765    #[test]
766    fn test_request_data_deserialization() {
767        let json_str = r#"{
768            "path_params": {"user_id": "123"},
769            "query_params": {"page": "1"},
770            "raw_query_params": {"page": ["1"]},
771            "body": {"name": "test"},
772            "raw_body": null,
773            "headers": {"content-type": "application/json"},
774            "cookies": {"session": "abc"},
775            "method": "POST",
776            "path": "/api/users"
777        }"#;
778
779        let deserialized: RequestData = serde_json::from_str(json_str).expect("deserialization failed");
780
781        assert_eq!(deserialized.method, "POST");
782        assert_eq!(deserialized.path, "/api/users");
783        assert_eq!(deserialized.path_params.get("user_id"), Some(&"123".to_string()));
784        assert_eq!(deserialized.body["name"], "test");
785        assert_eq!(
786            deserialized.headers.get("content-type"),
787            Some(&"application/json".to_string())
788        );
789        assert_eq!(deserialized.cookies.get("session"), Some(&"abc".to_string()));
790    }
791
792    #[test]
793    fn test_request_data_roundtrip_serialization() {
794        let mut path_params = HashMap::new();
795        path_params.insert("item_id".to_string(), "789".to_string());
796
797        let mut raw_query_params = HashMap::new();
798        raw_query_params.insert("sort".to_string(), vec!["desc".to_string()]);
799
800        let mut headers = HashMap::new();
801        headers.insert("x-custom-header".to_string(), "value123".to_string());
802
803        let mut cookies = HashMap::new();
804        cookies.insert("prefer".to_string(), "dark_mode".to_string());
805
806        let original = create_request_data(
807            "/api/items/789",
808            "DELETE",
809            json!({"reason": "archived"}),
810            json!({"sort": "desc"}),
811            RequestDataCollections {
812                raw_query_params,
813                headers,
814                cookies,
815                path_params,
816            },
817        );
818
819        let serialized = serde_json::to_string(&original).expect("serialization failed");
820        let deserialized: RequestData = serde_json::from_str(&serialized).expect("deserialization failed");
821
822        assert_eq!(deserialized.method, original.method);
823        assert_eq!(deserialized.path, original.path);
824        assert_eq!(deserialized.body, original.body);
825        assert_eq!(deserialized.path_params, original.path_params);
826    }
827
828    #[test]
829    fn test_request_data_large_json_body() {
830        let mut large_obj = serde_json::Map::new();
831        for i in 0..100 {
832            large_obj.insert(
833                format!("field_{}", i),
834                json!({"value": i, "name": format!("item_{}", i)}),
835            );
836        }
837        let body = json!(large_obj);
838
839        let data = create_request_data(
840            "/api/batch",
841            "POST",
842            body.clone(),
843            json!({}),
844            RequestDataCollections::default(),
845        );
846
847        assert!(data.body.is_object());
848        assert_eq!(data.body.as_object().unwrap().len(), 100);
849    }
850
851    #[test]
852    fn test_request_data_deeply_nested_json() {
853        let body = json!({
854            "level1": {
855                "level2": {
856                    "level3": {
857                        "level4": {
858                            "level5": {
859                                "value": "deep"
860                            }
861                        }
862                    }
863                }
864            }
865        });
866
867        let data = create_request_data("/api/nested", "GET", body, json!({}), RequestDataCollections::default());
868
869        assert_eq!(
870            data.body["level1"]["level2"]["level3"]["level4"]["level5"]["value"],
871            "deep"
872        );
873    }
874
875    #[test]
876    fn test_request_data_unicode_in_fields() {
877        let mut path_params = HashMap::new();
878        path_params.insert("name".to_string(), "用户".to_string());
879
880        let mut cookies = HashMap::new();
881        cookies.insert("msg".to_string(), "こんにちは".to_string());
882
883        let body = json!({
884            "greeting": "مرحبا",
885            "emoji": "🚀"
886        });
887
888        let data = create_request_data(
889            "/api/users/用户",
890            "POST",
891            body,
892            json!({}),
893            RequestDataCollections {
894                cookies,
895                path_params,
896                ..Default::default()
897            },
898        );
899
900        assert_eq!(data.path_params.get("name"), Some(&"用户".to_string()));
901        assert_eq!(data.cookies.get("msg"), Some(&"こんにちは".to_string()));
902        assert_eq!(data.body["emoji"], "🚀");
903    }
904
905    #[test]
906    fn test_request_data_multiple_headers_with_same_prefix() {
907        let mut headers = HashMap::new();
908        headers.insert("x-custom-1".to_string(), "value1".to_string());
909        headers.insert("x-custom-2".to_string(), "value2".to_string());
910        headers.insert("x-custom-3".to_string(), "value3".to_string());
911
912        let data = create_request_data(
913            "/api/test",
914            "GET",
915            json!(null),
916            json!({}),
917            RequestDataCollections {
918                headers,
919                ..Default::default()
920            },
921        );
922
923        assert_eq!(data.headers.get("x-custom-1"), Some(&"value1".to_string()));
924        assert_eq!(data.headers.get("x-custom-2"), Some(&"value2".to_string()));
925        assert_eq!(data.headers.get("x-custom-3"), Some(&"value3".to_string()));
926        assert_eq!(data.headers.len(), 3);
927    }
928
929    #[test]
930    fn test_request_data_numeric_values_in_json() {
931        let body = json!({
932            "int": 42,
933            "float": 3.2,
934            "negative": -100,
935            "zero": 0,
936            "large": 9223372036854775807i64
937        });
938
939        let data = create_request_data(
940            "/api/numbers",
941            "POST",
942            body,
943            json!({}),
944            RequestDataCollections::default(),
945        );
946
947        assert_eq!(data.body["int"], 42);
948        assert_eq!(data.body["float"], 3.2);
949        assert_eq!(data.body["negative"], -100);
950        assert_eq!(data.body["zero"], 0);
951    }
952
953    #[test]
954    fn test_request_data_boolean_values_in_json() {
955        let body = json!({
956            "is_active": true,
957            "is_admin": false
958        });
959
960        let data = create_request_data("/api/config", "GET", body, json!({}), RequestDataCollections::default());
961
962        assert_eq!(data.body["is_active"], true);
963        assert_eq!(data.body["is_admin"], false);
964    }
965
966    #[test]
967    fn test_request_data_missing_optional_raw_body() {
968        let data = create_request_data(
969            "/api/test",
970            "GET",
971            json!(null),
972            json!({}),
973            RequestDataCollections::default(),
974        );
975
976        assert!(data.raw_body.is_none());
977    }
978
979    #[test]
980    fn test_request_data_path_with_query_string_format() {
981        let data = create_request_data(
982            "/api/search?q=test&limit=10",
983            "GET",
984            json!(null),
985            json!({"q": "test", "limit": "10"}),
986            RequestDataCollections::default(),
987        );
988
989        assert_eq!(data.path, "/api/search?q=test&limit=10");
990    }
991
992    #[test]
993    fn test_request_data_root_path() {
994        let data = create_request_data("/", "GET", json!(null), json!({}), RequestDataCollections::default());
995
996        assert_eq!(data.path, "/");
997    }
998
999    #[test]
1000    fn test_request_data_empty_string_values() {
1001        let mut headers = HashMap::new();
1002        headers.insert("empty-header".to_string(), "".to_string());
1003
1004        let mut path_params = HashMap::new();
1005        path_params.insert("id".to_string(), "".to_string());
1006
1007        let data = create_request_data(
1008            "/api/test",
1009            "GET",
1010            json!(null),
1011            json!({}),
1012            RequestDataCollections {
1013                headers,
1014                path_params,
1015                ..Default::default()
1016            },
1017        );
1018
1019        assert_eq!(data.headers.get("empty-header"), Some(&"".to_string()));
1020        assert_eq!(data.path_params.get("id"), Some(&"".to_string()));
1021    }
1022
1023    #[test]
1024    fn test_request_data_deserialization_missing_field() {
1025        let json_str = r#"{
1026            "path_params": {"user_id": "123"},
1027            "query_params": {"page": "1"},
1028            "raw_query_params": {"page": ["1"]},
1029            "body": {"name": "test"},
1030            "raw_body": null,
1031            "headers": {"content-type": "application/json"},
1032            "cookies": {"session": "abc"}
1033        }"#;
1034
1035        let result: Result<RequestData, _> = serde_json::from_str(json_str);
1036        assert!(result.is_err());
1037    }
1038
1039    #[test]
1040    fn test_request_data_deserialization_extra_fields_rejected() {
1041        let json_str = r#"{
1042            "path_params": {"user_id": "123"},
1043            "query_params": {"page": "1"},
1044            "raw_query_params": {"page": ["1"]},
1045            "body": {"name": "test"},
1046            "raw_body": null,
1047            "headers": {"content-type": "application/json"},
1048            "cookies": {"session": "abc"},
1049            "method": "GET",
1050            "path": "/api/test",
1051            "extra_field": "not allowed"
1052        }"#;
1053
1054        let result: Result<RequestData, _> = serde_json::from_str(json_str);
1055        assert!(result.is_err());
1056    }
1057
1058    #[test]
1059    fn test_request_data_multiple_raw_body_values() {
1060        let mut raw_query_params = HashMap::new();
1061        raw_query_params.insert(
1062            "data".to_string(),
1063            vec!["val1".to_string(), "val2".to_string(), "val3".to_string()],
1064        );
1065
1066        let data = create_request_data(
1067            "/api/test",
1068            "POST",
1069            json!(null),
1070            json!({}),
1071            RequestDataCollections {
1072                raw_query_params,
1073                ..Default::default()
1074            },
1075        );
1076
1077        let values = data.raw_query_params.get("data").unwrap();
1078        assert_eq!(values.len(), 3);
1079        assert_eq!(values[0], "val1");
1080        assert_eq!(values[1], "val2");
1081        assert_eq!(values[2], "val3");
1082    }
1083
1084    #[test]
1085    fn test_request_data_debug_output() {
1086        let data = create_request_data(
1087            "/api/test",
1088            "GET",
1089            json!({"key": "value"}),
1090            json!({}),
1091            RequestDataCollections::default(),
1092        );
1093
1094        let debug_str = format!("{:?}", data);
1095        assert!(debug_str.contains("RequestData"));
1096        assert!(debug_str.contains("/api/test"));
1097    }
1098
1099    #[test]
1100    fn test_request_data_clone_independence() {
1101        let mut path_params1 = HashMap::new();
1102        path_params1.insert("id".to_string(), "original".to_string());
1103
1104        let data1 = create_request_data(
1105            "/api/users/1",
1106            "GET",
1107            json!({"name": "original"}),
1108            json!({}),
1109            RequestDataCollections {
1110                path_params: path_params1,
1111                ..Default::default()
1112            },
1113        );
1114
1115        let data2 = data1.clone();
1116
1117        assert_eq!(data1.path_params.get("id"), data2.path_params.get("id"));
1118        assert_eq!(data1.body["name"], data2.body["name"]);
1119    }
1120
1121    #[test]
1122    fn deserialize_errors_on_missing_required_field() {
1123        let value = json!({
1124            "path_params": {},
1125            "query_params": {},
1126            "raw_query_params": {},
1127            "body": null,
1128            "raw_body": null,
1129            "headers": {},
1130            "cookies": {},
1131            "method": "GET"
1132        });
1133
1134        let err = serde_json::from_value::<RequestData>(value).unwrap_err();
1135        assert!(err.to_string().contains("missing field"));
1136    }
1137
1138    #[test]
1139    fn deserialize_errors_on_invalid_raw_body_type() {
1140        let value = json!({
1141            "path_params": {},
1142            "query_params": {},
1143            "raw_query_params": {},
1144            "body": null,
1145            "raw_body": "not-bytes",
1146            "headers": {},
1147            "cookies": {},
1148            "method": "GET",
1149            "path": "/"
1150        });
1151
1152        assert!(serde_json::from_value::<RequestData>(value).is_err());
1153    }
1154}