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