Skip to main content

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