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
38impl Default for Params {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44#[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 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 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
106pub struct Request {
108 pub method: http::Method,
109 pub uri: http::Uri,
110 pub headers: http::HeaderMap,
111 pub body: bytes::Bytes,
112 pub params: Params,
113 pub query: Query,
114 pub extensions: http::Extensions,
117 pub remote_addr: Option<std::net::SocketAddr>,
119 cookies: OnceCell<Cookies>,
120}
121
122impl Request {
123 pub fn new(
124 method: http::Method,
125 uri: http::Uri,
126 headers: http::HeaderMap,
127 body: bytes::Bytes,
128 ) -> Self {
129 let query = uri
130 .query()
131 .map(Query::parse_query_string)
132 .unwrap_or_default();
133
134 Self {
135 method,
136 uri,
137 headers,
138 body,
139 params: Params::new(),
140 query,
141 extensions: http::Extensions::new(),
142 remote_addr: None,
143 cookies: OnceCell::new(),
144 }
145 }
146
147 pub fn cookies(&self) -> &Cookies {
149 self.cookies.get_or_init(|| {
150 if let Some(cookie_header) = self.headers.get("cookie") {
151 if let Ok(cookie_str) = cookie_header.to_str() {
152 Cookies::parse(cookie_str)
153 } else {
154 Cookies::new()
155 }
156 } else {
157 Cookies::new()
158 }
159 })
160 }
161
162 pub fn form<T: DeserializeOwned>(&self) -> Result<T> {
167 self.require_form_content_type()?;
168 serde_urlencoded::from_bytes(&self.body)
169 .map_err(|e| Error::bad_request(format!("Invalid form data: {}", e)))
170 }
171
172 pub fn form_data(&self) -> Result<Query> {
174 self.require_form_content_type()?;
175 let text = std::str::from_utf8(&self.body)
176 .map_err(|_| Error::bad_request("Form data is not valid UTF-8"))?;
177 Ok(Query::parse_query_string(text))
178 }
179
180 fn require_form_content_type(&self) -> Result<()> {
181 let is_form = self
182 .headers
183 .get(http::header::CONTENT_TYPE)
184 .and_then(|v| v.to_str().ok())
185 .map(|v| {
186 v.split(';')
187 .next()
188 .unwrap_or("")
189 .trim()
190 .eq_ignore_ascii_case("application/x-www-form-urlencoded")
191 })
192 .unwrap_or(false);
193 if is_form {
194 Ok(())
195 } else {
196 Err(Error::custom(
197 415,
198 "Expected content-type application/x-www-form-urlencoded",
199 ))
200 }
201 }
202
203 pub fn json<T: DeserializeOwned>(&self) -> Result<T> {
205 serde_json::from_slice(&self.body)
206 .map_err(|e| Error::bad_request(format!("Invalid JSON: {}", e)))
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 fn form_request(content_type: Option<&str>, body: &'static str) -> Request {
215 let mut headers = http::HeaderMap::new();
216 if let Some(ct) = content_type {
217 headers.insert(http::header::CONTENT_TYPE, ct.parse().unwrap());
218 }
219 Request::new(
220 http::Method::POST,
221 "/".parse().unwrap(),
222 headers,
223 bytes::Bytes::from_static(body.as_bytes()),
224 )
225 }
226
227 #[derive(Debug, serde::Deserialize, PartialEq)]
228 struct Signup {
229 name: String,
230 age: u8,
231 newsletter: Option<bool>,
232 }
233
234 #[test]
235 fn query_decodes_keys_values_and_plus() {
236 let q = Query::parse_query_string("q=rust+web&na%20me=a%26b&flag&&x=1");
237 assert_eq!(q.get("q"), Some("rust web"));
238 assert_eq!(q.get("na me"), Some("a&b"));
239 assert_eq!(q.get("flag"), Some(""));
240 assert!(q.contains("x") && !q.is_empty());
241 }
242
243 #[test]
244 fn query_parse_converts_types() {
245 let q = Query::parse_query_string("name=Ann&age=31&newsletter=true");
246 assert_eq!(
247 q.parse::<Signup>().unwrap(),
248 Signup {
249 name: "Ann".into(),
250 age: 31,
251 newsletter: Some(true)
252 }
253 );
254 let bad = Query::parse_query_string("name=Ann&age=old");
255 assert_eq!(bad.parse::<Signup>().unwrap_err().status_code(), 400);
256 }
257
258 #[test]
259 fn form_typed_and_untyped() {
260 let req = form_request(
261 Some("application/x-www-form-urlencoded; charset=UTF-8"),
262 "name=Ann+Lee&age=31",
263 );
264 let signup: Signup = req.form().unwrap();
265 assert_eq!(
266 signup,
267 Signup {
268 name: "Ann Lee".into(),
269 age: 31,
270 newsletter: None
271 }
272 );
273 assert_eq!(req.form_data().unwrap().get("name"), Some("Ann Lee"));
274 }
275
276 #[test]
277 fn form_rejects_wrong_content_type_and_bad_values() {
278 let json = form_request(Some("application/json"), "name=Ann&age=1");
279 assert_eq!(json.form::<Signup>().unwrap_err().status_code(), 415);
280 let none = form_request(None, "name=Ann&age=1");
281 assert_eq!(none.form_data().unwrap_err().status_code(), 415);
282
283 let bad = form_request(
284 Some("application/x-www-form-urlencoded"),
285 "name=Ann&age=300",
286 );
287 assert_eq!(bad.form::<Signup>().unwrap_err().status_code(), 400);
288 }
289}