1use crate::cookies::Cookies;
4use crate::error::{Error, Result};
5use serde::de::DeserializeOwned;
6use std::cell::OnceCell;
7use std::collections::HashMap;
8
9#[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 pub fn parse<T: std::str::FromStr>(&self, key: &str) -> Result<T> {
41 self.get(key)?
42 .parse()
43 .map_err(|_| Error::bad_request(format!("Invalid path parameter '{}'", key)))
44 }
45}
46
47impl Default for Params {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct Query {
56 inner: HashMap<String, String>,
57 raw: String,
58}
59
60impl Query {
61 pub fn new() -> Self {
62 Self {
63 inner: HashMap::new(),
64 raw: String::new(),
65 }
66 }
67
68 pub fn parse_query_string(query: &str) -> Self {
72 let mut q = Query {
73 inner: HashMap::new(),
74 raw: query.to_string(),
75 };
76 for pair in query.split('&').filter(|p| !p.is_empty()) {
77 let (key, value) = pair.split_once('=').unwrap_or((pair, ""));
78 q.inner
79 .insert(decode_component(key), decode_component(value));
80 }
81 q
82 }
83
84 pub fn get(&self, key: &str) -> Option<&str> {
85 self.inner.get(key).map(|s| s.as_str())
86 }
87
88 pub fn contains(&self, key: &str) -> bool {
89 self.inner.contains_key(key)
90 }
91
92 pub fn is_empty(&self) -> bool {
93 self.inner.is_empty()
94 }
95
96 pub fn parse<T: DeserializeOwned>(&self) -> Result<T> {
99 serde_urlencoded::from_str(&self.raw)
100 .map_err(|e| Error::bad_request(format!("Invalid query params: {}", e)))
101 }
102}
103
104fn decode_component(s: &str) -> String {
105 let s = s.replace('+', " ");
106 urlencoding::decode(&s).map(|c| c.into_owned()).unwrap_or(s)
107}
108
109impl Default for Query {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114
115#[derive(Default)]
117pub(crate) struct StateMap {
118 values: HashMap<std::any::TypeId, Box<dyn std::any::Any + Send + Sync>>,
119}
120
121impl StateMap {
122 pub(crate) fn insert<T: Send + Sync + 'static>(&mut self, value: T) {
123 self.values
124 .insert(std::any::TypeId::of::<T>(), Box::new(value));
125 }
126
127 pub(crate) fn get<T: Send + Sync + 'static>(&self) -> Option<&T> {
128 self.values
129 .get(&std::any::TypeId::of::<T>())
130 .and_then(|v| v.downcast_ref::<T>())
131 }
132}
133
134pub struct Request {
136 pub method: http::Method,
137 pub uri: http::Uri,
138 pub headers: http::HeaderMap,
139 pub body: bytes::Bytes,
140 pub params: Params,
141 pub query: Query,
142 pub extensions: http::Extensions,
145 pub remote_addr: Option<std::net::SocketAddr>,
147 pub(crate) state: Option<std::sync::Arc<StateMap>>,
148 cookies: OnceCell<Cookies>,
149}
150
151impl Request {
152 pub fn new(
153 method: http::Method,
154 uri: http::Uri,
155 headers: http::HeaderMap,
156 body: bytes::Bytes,
157 ) -> Self {
158 let query = uri
159 .query()
160 .map(Query::parse_query_string)
161 .unwrap_or_default();
162
163 Self {
164 method,
165 uri,
166 headers,
167 body,
168 params: Params::new(),
169 query,
170 extensions: http::Extensions::new(),
171 remote_addr: None,
172 state: None,
173 cookies: OnceCell::new(),
174 }
175 }
176
177 pub fn state<T: Send + Sync + 'static>(&self) -> Result<&T> {
182 self.try_state().ok_or_else(|| {
183 Error::internal(format!(
184 "State of type {} is not registered",
185 std::any::type_name::<T>()
186 ))
187 })
188 }
189
190 pub fn try_state<T: Send + Sync + 'static>(&self) -> Option<&T> {
192 self.state.as_ref()?.get::<T>()
193 }
194
195 pub fn cookies(&self) -> &Cookies {
197 self.cookies.get_or_init(|| {
198 if let Some(cookie_header) = self.headers.get("cookie") {
199 if let Ok(cookie_str) = cookie_header.to_str() {
200 Cookies::parse(cookie_str)
201 } else {
202 Cookies::new()
203 }
204 } else {
205 Cookies::new()
206 }
207 })
208 }
209
210 pub fn form<T: DeserializeOwned>(&self) -> Result<T> {
215 self.require_form_content_type()?;
216 serde_urlencoded::from_bytes(&self.body)
217 .map_err(|e| Error::bad_request(format!("Invalid form data: {}", e)))
218 }
219
220 pub fn form_data(&self) -> Result<Query> {
222 self.require_form_content_type()?;
223 let text = std::str::from_utf8(&self.body)
224 .map_err(|_| Error::bad_request("Form data is not valid UTF-8"))?;
225 Ok(Query::parse_query_string(text))
226 }
227
228 fn require_form_content_type(&self) -> Result<()> {
229 let is_form = self
230 .headers
231 .get(http::header::CONTENT_TYPE)
232 .and_then(|v| v.to_str().ok())
233 .map(|v| {
234 v.split(';')
235 .next()
236 .unwrap_or("")
237 .trim()
238 .eq_ignore_ascii_case("application/x-www-form-urlencoded")
239 })
240 .unwrap_or(false);
241 if is_form {
242 Ok(())
243 } else {
244 Err(Error::custom(
245 415,
246 "Expected content-type application/x-www-form-urlencoded",
247 ))
248 }
249 }
250
251 pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
253 serde_json::from_slice(&self.body)
254 .map_err(|e| Error::bad_request(format!("Invalid JSON: {}", e)))
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use super::*;
261
262 fn form_request(content_type: Option<&str>, body: &'static str) -> Request {
263 let mut headers = http::HeaderMap::new();
264 if let Some(ct) = content_type {
265 headers.insert(http::header::CONTENT_TYPE, ct.parse().unwrap());
266 }
267 Request::new(
268 http::Method::POST,
269 "/".parse().unwrap(),
270 headers,
271 bytes::Bytes::from_static(body.as_bytes()),
272 )
273 }
274
275 #[derive(Debug, serde::Deserialize, PartialEq)]
276 struct Signup {
277 name: String,
278 age: u8,
279 newsletter: Option<bool>,
280 }
281
282 #[test]
283 fn params_parse_typed_values() {
284 let mut p = Params::new();
285 p.insert("id".into(), "42".into());
286 p.insert("name".into(), "x".into());
287 assert_eq!(p.parse::<u64>("id").unwrap(), 42);
288 assert_eq!(p.parse::<String>("name").unwrap(), "x");
289 let bad = p.parse::<u64>("name").unwrap_err();
290 assert_eq!(bad.status_code(), 400);
291 assert_eq!(bad.message(), "Invalid path parameter 'name'");
292 assert_eq!(p.parse::<u64>("missing").unwrap_err().status_code(), 400);
293 assert!(p.parse::<u8>("id").is_ok());
294 p.insert("big".into(), "300".into());
295 assert!(p.parse::<u8>("big").is_err());
296 }
297
298 #[test]
299 fn query_decodes_keys_values_and_plus() {
300 let q = Query::parse_query_string("q=rust+web&na%20me=a%26b&flag&&x=1");
301 assert_eq!(q.get("q"), Some("rust web"));
302 assert_eq!(q.get("na me"), Some("a&b"));
303 assert_eq!(q.get("flag"), Some(""));
304 assert!(q.contains("x") && !q.is_empty());
305 }
306
307 #[test]
308 fn query_parse_converts_types() {
309 let q = Query::parse_query_string("name=Ann&age=31&newsletter=true");
310 assert_eq!(
311 q.parse::<Signup>().unwrap(),
312 Signup {
313 name: "Ann".into(),
314 age: 31,
315 newsletter: Some(true)
316 }
317 );
318 let bad = Query::parse_query_string("name=Ann&age=old");
319 assert_eq!(bad.parse::<Signup>().unwrap_err().status_code(), 400);
320 }
321
322 #[test]
323 fn form_typed_and_untyped() {
324 let req = form_request(
325 Some("application/x-www-form-urlencoded; charset=UTF-8"),
326 "name=Ann+Lee&age=31",
327 );
328 let signup: Signup = req.form().unwrap();
329 assert_eq!(
330 signup,
331 Signup {
332 name: "Ann Lee".into(),
333 age: 31,
334 newsletter: None
335 }
336 );
337 assert_eq!(req.form_data().unwrap().get("name"), Some("Ann Lee"));
338 }
339
340 #[test]
341 fn form_rejects_wrong_content_type_and_bad_values() {
342 let json = form_request(Some("application/json"), "name=Ann&age=1");
343 assert_eq!(json.form::<Signup>().unwrap_err().status_code(), 415);
344 let none = form_request(None, "name=Ann&age=1");
345 assert_eq!(none.form_data().unwrap_err().status_code(), 415);
346
347 let bad = form_request(
348 Some("application/x-www-form-urlencoded"),
349 "name=Ann&age=300",
350 );
351 assert_eq!(bad.form::<Signup>().unwrap_err().status_code(), 400);
352 }
353}