spikard_core/
request_data.rs

1//! Request data structures for HTTP handlers
2//!
3//! This module provides the `RequestData` type which represents extracted
4//! HTTP request data in a language-agnostic format.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use std::collections::HashMap;
9use std::sync::Arc;
10
11#[cfg(feature = "di")]
12use crate::di::ResolvedDependencies;
13#[cfg(feature = "di")]
14use bytes::Bytes;
15
16/// Request data extracted from HTTP request
17///
18/// This is the language-agnostic representation passed to handlers.
19///
20/// Uses Arc for HashMaps to enable cheap cloning without duplicating data.
21/// When RequestData is cloned, only the Arc pointers are cloned, not the underlying data.
22///
23/// Performance optimization: raw_body stores the unparsed request body bytes.
24/// Language bindings should use raw_body when possible to avoid double-parsing.
25/// The body field is lazily parsed only when needed for validation.
26#[derive(Debug, Clone)]
27pub struct RequestData {
28    /// Path parameters extracted from the URL path
29    pub path_params: Arc<HashMap<String, String>>,
30    /// Query parameters parsed as JSON
31    pub query_params: Value,
32    /// Raw query parameters as key-value pairs
33    pub raw_query_params: Arc<HashMap<String, Vec<String>>>,
34    /// Parsed request body as JSON
35    pub body: Value,
36    /// Raw request body bytes (optional, for zero-copy access)
37    #[cfg(feature = "di")]
38    pub raw_body: Option<Bytes>,
39    #[cfg(not(feature = "di"))]
40    pub raw_body: Option<Vec<u8>>,
41    /// Request headers
42    pub headers: Arc<HashMap<String, String>>,
43    /// Request cookies
44    pub cookies: Arc<HashMap<String, String>>,
45    /// HTTP method (GET, POST, etc.)
46    pub method: String,
47    /// Request path
48    pub path: String,
49    /// Resolved dependencies for this request (populated by DI handlers)
50    #[cfg(feature = "di")]
51    pub dependencies: Option<Arc<ResolvedDependencies>>,
52}
53
54impl Serialize for RequestData {
55    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
56    where
57        S: serde::Serializer,
58    {
59        use serde::ser::SerializeStruct;
60        let mut state = serializer.serialize_struct("RequestData", 9)?;
61        state.serialize_field("path_params", &*self.path_params)?;
62        state.serialize_field("query_params", &self.query_params)?;
63        state.serialize_field("raw_query_params", &*self.raw_query_params)?;
64        state.serialize_field("body", &self.body)?;
65        #[cfg(feature = "di")]
66        state.serialize_field("raw_body", &self.raw_body.as_ref().map(|b| b.as_ref()))?;
67        #[cfg(not(feature = "di"))]
68        state.serialize_field("raw_body", &self.raw_body)?;
69        state.serialize_field("headers", &*self.headers)?;
70        state.serialize_field("cookies", &*self.cookies)?;
71        state.serialize_field("method", &self.method)?;
72        state.serialize_field("path", &self.path)?;
73        state.end()
74    }
75}
76
77impl<'de> Deserialize<'de> for RequestData {
78    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
79    where
80        D: serde::Deserializer<'de>,
81    {
82        #[derive(Deserialize)]
83        #[serde(field_identifier, rename_all = "snake_case")]
84        enum Field {
85            PathParams,
86            QueryParams,
87            RawQueryParams,
88            Body,
89            RawBody,
90            Headers,
91            Cookies,
92            Method,
93            Path,
94        }
95
96        struct RequestDataVisitor;
97
98        impl<'de> serde::de::Visitor<'de> for RequestDataVisitor {
99            type Value = RequestData;
100
101            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
102                formatter.write_str("struct RequestData")
103            }
104
105            fn visit_map<V>(self, mut map: V) -> Result<RequestData, V::Error>
106            where
107                V: serde::de::MapAccess<'de>,
108            {
109                let mut path_params = None;
110                let mut query_params = None;
111                let mut raw_query_params = None;
112                let mut body = None;
113                let mut raw_body = None;
114                let mut headers = None;
115                let mut cookies = None;
116                let mut method = None;
117                let mut path = None;
118
119                while let Some(key) = map.next_key()? {
120                    match key {
121                        Field::PathParams => {
122                            path_params = Some(Arc::new(map.next_value()?));
123                        }
124                        Field::QueryParams => {
125                            query_params = Some(map.next_value()?);
126                        }
127                        Field::RawQueryParams => {
128                            raw_query_params = Some(Arc::new(map.next_value()?));
129                        }
130                        Field::Body => {
131                            body = Some(map.next_value()?);
132                        }
133                        Field::RawBody => {
134                            let bytes_vec: Option<Vec<u8>> = map.next_value()?;
135                            #[cfg(feature = "di")]
136                            {
137                                raw_body = bytes_vec.map(Bytes::from);
138                            }
139                            #[cfg(not(feature = "di"))]
140                            {
141                                raw_body = bytes_vec;
142                            }
143                        }
144                        Field::Headers => {
145                            headers = Some(Arc::new(map.next_value()?));
146                        }
147                        Field::Cookies => {
148                            cookies = Some(Arc::new(map.next_value()?));
149                        }
150                        Field::Method => {
151                            method = Some(map.next_value()?);
152                        }
153                        Field::Path => {
154                            path = Some(map.next_value()?);
155                        }
156                    }
157                }
158
159                Ok(RequestData {
160                    path_params: path_params.ok_or_else(|| serde::de::Error::missing_field("path_params"))?,
161                    query_params: query_params.ok_or_else(|| serde::de::Error::missing_field("query_params"))?,
162                    raw_query_params: raw_query_params
163                        .ok_or_else(|| serde::de::Error::missing_field("raw_query_params"))?,
164                    body: body.ok_or_else(|| serde::de::Error::missing_field("body"))?,
165                    raw_body,
166                    headers: headers.ok_or_else(|| serde::de::Error::missing_field("headers"))?,
167                    cookies: cookies.ok_or_else(|| serde::de::Error::missing_field("cookies"))?,
168                    method: method.ok_or_else(|| serde::de::Error::missing_field("method"))?,
169                    path: path.ok_or_else(|| serde::de::Error::missing_field("path"))?,
170                    #[cfg(feature = "di")]
171                    dependencies: None,
172                })
173            }
174        }
175
176        const FIELDS: &[&str] = &[
177            "path_params",
178            "query_params",
179            "raw_query_params",
180            "body",
181            "raw_body",
182            "headers",
183            "cookies",
184            "method",
185            "path",
186        ];
187        deserializer.deserialize_struct("RequestData", FIELDS, RequestDataVisitor)
188    }
189}