Skip to main content

spikard_http/
handler_trait.rs

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