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