Skip to main content

ullun/
request.rs

1//! Request types and extractors
2
3use crate::cookies::Cookies;
4use crate::error::{Error, Result};
5use serde::de::DeserializeOwned;
6use std::cell::OnceCell;
7use std::collections::HashMap;
8
9/// Path parameters extracted from route
10#[derive(Debug, Clone)]
11pub struct Params {
12    inner: HashMap<String, String>,
13}
14
15impl Params {
16    pub fn new() -> Self {
17        Self {
18            inner: HashMap::new(),
19        }
20    }
21
22    pub fn insert(&mut self, key: String, value: String) {
23        self.inner.insert(key, value);
24    }
25
26    pub fn get(&self, key: &str) -> Result<&str> {
27        self.inner
28            .get(key)
29            .map(|s| s.as_str())
30            .ok_or_else(|| Error::bad_request(format!("Missing param: {}", key)))
31    }
32
33    pub fn get_opt(&self, key: &str) -> Option<&str> {
34        self.inner.get(key).map(|s| s.as_str())
35    }
36}
37
38impl Default for Params {
39    fn default() -> Self {
40        Self::new()
41    }
42}
43
44/// Query parameters from URL (also used for `application/x-www-form-urlencoded` bodies)
45#[derive(Debug, Clone)]
46pub struct Query {
47    inner: HashMap<String, String>,
48    raw: String,
49}
50
51impl Query {
52    pub fn new() -> Self {
53        Self {
54            inner: HashMap::new(),
55            raw: String::new(),
56        }
57    }
58
59    /// Parse `a=1&b=two`. Keys and values are percent-decoded and `+` is a
60    /// space. Pairs without `=` are kept with an empty value; if a key repeats,
61    /// the last value wins.
62    pub fn parse_query_string(query: &str) -> Self {
63        let mut q = Query {
64            inner: HashMap::new(),
65            raw: query.to_string(),
66        };
67        for pair in query.split('&').filter(|p| !p.is_empty()) {
68            let (key, value) = pair.split_once('=').unwrap_or((pair, ""));
69            q.inner
70                .insert(decode_component(key), decode_component(value));
71        }
72        q
73    }
74
75    pub fn get(&self, key: &str) -> Option<&str> {
76        self.inner.get(key).map(|s| s.as_str())
77    }
78
79    pub fn contains(&self, key: &str) -> bool {
80        self.inner.contains_key(key)
81    }
82
83    pub fn is_empty(&self) -> bool {
84        self.inner.is_empty()
85    }
86
87    /// Deserialize into a struct; numbers, booleans and `Option` are converted
88    /// from their string form.
89    pub fn parse<T: DeserializeOwned>(&self) -> Result<T> {
90        serde_urlencoded::from_str(&self.raw)
91            .map_err(|e| Error::bad_request(format!("Invalid query params: {}", e)))
92    }
93}
94
95fn decode_component(s: &str) -> String {
96    let s = s.replace('+', " ");
97    urlencoding::decode(&s).map(|c| c.into_owned()).unwrap_or(s)
98}
99
100impl Default for Query {
101    fn default() -> Self {
102        Self::new()
103    }
104}
105
106/// Type-keyed storage for shared application state
107#[derive(Default)]
108pub(crate) struct StateMap {
109    values: HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync>>,
110}
111
112impl StateMap {
113    pub(crate) fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
114        self.values
115            .insert(std::any::TypeId::of::<T>(), Box::new(value));
116    }
117
118    pub(crate) fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
119        self.values
120            .get(&std::any::TypeId::of::<T>())
121            .and_then(|v| v.downcast_ref::<T>())
122    }
123}
124
125/// HTTP Request wrapper
126pub struct Request {
127    pub method: http::Method,
128    pub uri: http::Uri,
129    pub headers: http::HeaderMap,
130    pub body: bytes::Bytes,
131    pub params: Params,
132    pub query: Query,
133    /// Typed per-request data; middleware can attach values for handlers
134    /// (authenticated user, request id, ...).
135    pub extensions: http::Extensions,
136    /// Address of the connected peer (the proxy when behind one)
137    pub remote_addr: Option<std::net::SocketAddr>,
138    pub(crate) state: Option<std::sync::Arc<StateMap>>,
139    cookies: OnceCell<Cookies>,
140}
141
142impl Request {
143    pub fn new(
144        method: http::Method,
145        uri: http::Uri,
146        headers: http::HeaderMap,
147        body: bytes::Bytes,
148    ) -> Self {
149        let query = uri
150            .query()
151            .map(Query::parse_query_string)
152            .unwrap_or_default();
153
154        Self {
155            method,
156            uri,
157            headers,
158            body,
159            params: Params::new(),
160            query,
161            extensions: http::Extensions::new(),
162            remote_addr: None,
163            state: None,
164            cookies: OnceCell::new(),
165        }
166    }
167
168    /// Shared application state registered with `App::state`
169    ///
170    /// Answers `500` when no value of type `T` was registered: that is a
171    /// programming error, not something the client can fix.
172    pub fn state<T: Send + Sync + 'static>(&self) -> Result<&T> {
173        self.try_state().ok_or_else(|| {
174            Error::internal(format!(
175                "State of type {} is not registered",
176                std::any::type_name::<T>()
177            ))
178        })
179    }
180
181    /// Like [`Request::state`], `None` when the type is not registered
182    pub fn try_state<T: Send + Sync + 'static>(&self) -> Option<&T> {
183        self.state.as_ref()?.get::<T>()
184    }
185
186    /// Get cookies from the request
187    pub fn cookies(&self) -> &Cookies {
188        self.cookies.get_or_init(|| {
189            if let Some(cookie_header) = self.headers.get("cookie") {
190                if let Ok(cookie_str) = cookie_header.to_str() {
191                    Cookies::parse(cookie_str)
192                } else {
193                    Cookies::new()
194                }
195            } else {
196                Cookies::new()
197            }
198        })
199    }
200
201    /// Parse an `application/x-www-form-urlencoded` body into a struct
202    ///
203    /// Answers `415` for other content types and `400` for values that do not
204    /// fit the target type.
205    pub fn form<T: DeserializeOwned>(&self) -> Result<T> {
206        self.require_form_content_type()?;
207        serde_urlencoded::from_bytes(&self.body)
208            .map_err(|e| Error::bad_request(format!("Invalid form data: {}", e)))
209    }
210
211    /// The same body as decoded key/value access (`+` and `%XX` decoded)
212    pub fn form_data(&self) -> Result<Query> {
213        self.require_form_content_type()?;
214        let text = std::str::from_utf8(&self.body)
215            .map_err(|_| Error::bad_request("Form data is not valid UTF-8"))?;
216        Ok(Query::parse_query_string(text))
217    }
218
219    fn require_form_content_type(&self) -> Result<()> {
220        let is_form = self
221            .headers
222            .get(http::header::CONTENT_TYPE)
223            .and_then(|v| v.to_str().ok())
224            .map(|v| {
225                v.split(';')
226                    .next()
227                    .unwrap_or("")
228                    .trim()
229                    .eq_ignore_ascii_case("application/x-www-form-urlencoded")
230            })
231            .unwrap_or(false);
232        if is_form {
233            Ok(())
234        } else {
235            Err(Error::custom(
236                415,
237                "Expected content-type application/x-www-form-urlencoded",
238            ))
239        }
240    }
241
242    /// Parse JSON body
243    pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
244        serde_json::from_slice(&self.body)
245            .map_err(|e| Error::bad_request(format!("Invalid JSON: {}", e)))
246    }
247}
248
249#[cfg(test)]
250mod tests {
251    use super::*;
252
253    fn form_request(content_type: Option<&str>, body: &'static str) -> Request {
254        let mut headers = http::HeaderMap::new();
255        if let Some(ct) = content_type {
256            headers.insert(http::header::CONTENT_TYPE, ct.parse().unwrap());
257        }
258        Request::new(
259            http::Method::POST,
260            "/".parse().unwrap(),
261            headers,
262            bytes::Bytes::from_static(body.as_bytes()),
263        )
264    }
265
266    #[derive(Debug, serde::Deserialize, PartialEq)]
267    struct Signup {
268        name: String,
269        age: u8,
270        newsletter: Option<bool>,
271    }
272
273    #[test]
274    fn query_decodes_keys_values_and_plus() {
275        let q = Query::parse_query_string("q=rust+web&na%20me=a%26b&flag&&x=1");
276        assert_eq!(q.get("q"), Some("rust web"));
277        assert_eq!(q.get("na me"), Some("a&b"));
278        assert_eq!(q.get("flag"), Some(""));
279        assert!(q.contains("x") && !q.is_empty());
280    }
281
282    #[test]
283    fn query_parse_converts_types() {
284        let q = Query::parse_query_string("name=Ann&age=31&newsletter=true");
285        assert_eq!(
286            q.parse::<Signup>().unwrap(),
287            Signup {
288                name: "Ann".into(),
289                age: 31,
290                newsletter: Some(true)
291            }
292        );
293        let bad = Query::parse_query_string("name=Ann&age=old");
294        assert_eq!(bad.parse::<Signup>().unwrap_err().status_code(), 400);
295    }
296
297    #[test]
298    fn form_typed_and_untyped() {
299        let req = form_request(
300            Some("application/x-www-form-urlencoded; charset=UTF-8"),
301            "name=Ann+Lee&age=31",
302        );
303        let signup: Signup = req.form().unwrap();
304        assert_eq!(
305            signup,
306            Signup {
307                name: "Ann Lee".into(),
308                age: 31,
309                newsletter: None
310            }
311        );
312        assert_eq!(req.form_data().unwrap().get("name"), Some("Ann Lee"));
313    }
314
315    #[test]
316    fn form_rejects_wrong_content_type_and_bad_values() {
317        let json = form_request(Some("application/json"), "name=Ann&age=1");
318        assert_eq!(json.form::<Signup>().unwrap_err().status_code(), 415);
319        let none = form_request(None, "name=Ann&age=1");
320        assert_eq!(none.form_data().unwrap_err().status_code(), 415);
321
322        let bad = form_request(
323            Some("application/x-www-form-urlencoded"),
324            "name=Ann&age=300",
325        );
326        assert_eq!(bad.form::<Signup>().unwrap_err().status_code(), 400);
327    }
328}