1use std::any::{Any, TypeId};
5use std::collections::HashMap;
6
7use bytes::Bytes;
8use hyper::{Method, StatusCode};
9
10use crate::error::{Error, Result};
11
12#[derive(Default)]
16pub struct Context {
17 inner: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
18}
19
20impl Context {
21 pub fn insert<T: Any + Send + Sync>(&mut self, value: T) {
23 self.inner.insert(TypeId::of::<T>(), Box::new(value));
24 }
25
26 pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> {
28 self.inner
29 .get(&TypeId::of::<T>())
30 .and_then(|b| b.downcast_ref::<T>())
31 }
32
33 pub fn get_mut<T: Any + Send + Sync>(&mut self) -> Option<&mut T> {
35 self.inner
36 .get_mut(&TypeId::of::<T>())
37 .and_then(|b| b.downcast_mut::<T>())
38 }
39}
40
41pub struct Request {
43 method: Method,
44 path: String,
45 query: String,
46 headers: HashMap<String, String>,
47 params: HashMap<String, String>,
48 body: Bytes,
49 ctx: Context,
50}
51
52impl Request {
53 pub(crate) fn new(
54 method: Method,
55 path: String,
56 query: String,
57 headers: HashMap<String, String>,
58 body: Bytes,
59 ) -> Self {
60 Self {
61 method,
62 path,
63 query,
64 headers,
65 params: HashMap::new(),
66 body,
67 ctx: Context::default(),
68 }
69 }
70
71 pub fn method(&self) -> &Method {
73 &self.method
74 }
75
76 pub fn path(&self) -> &str {
78 &self.path
79 }
80
81 pub fn query_string(&self) -> &str {
83 &self.query
84 }
85
86 pub fn query(&self) -> FormData {
88 FormData::from_urlencoded(&self.query)
89 }
90
91 pub fn header(&self, name: &str) -> Option<&str> {
93 self.headers
94 .get(&name.to_ascii_lowercase())
95 .map(|s| s.as_str())
96 }
97
98 pub fn param(&self, name: &str) -> Option<&str> {
100 self.params.get(name).map(|s| s.as_str())
101 }
102
103 pub fn body(&self) -> &[u8] {
105 &self.body
106 }
107
108 pub fn body_text(&self) -> Result<&str> {
110 std::str::from_utf8(&self.body)
111 .map_err(|_| Error::BadRequest("body is not valid utf-8".into()))
112 }
113
114 pub fn form(&self) -> Result<FormData> {
116 let ct = self.header("content-type").unwrap_or("");
122 if let Some(boundary) = crate::multipart::boundary_from_content_type(ct) {
123 return crate::multipart::parse_multipart(&self.body, &boundary)
124 .map(|mp| {
125 let mut form = FormData::default();
126 for part in mp.parts {
127 if part.filename.is_none() {
128 let text = String::from_utf8_lossy(&part.body).into_owned();
129 form.set(part.name, text);
130 }
131 }
132 form
133 })
134 .map_err(|e| Error::BadRequest(format!("multipart: {e}")));
135 }
136 let text = self.body_text()?;
137 Ok(FormData::from_urlencoded(text))
138 }
139
140 pub fn ctx(&self) -> &Context {
142 &self.ctx
143 }
144
145 pub fn ctx_mut(&mut self) -> &mut Context {
147 &mut self.ctx
148 }
149
150 pub(crate) fn set_params(&mut self, params: HashMap<String, String>) {
151 self.params = params;
152 }
153
154 #[doc(hidden)]
161 #[cfg(feature = "integration-test")]
162 pub(crate) fn __integration_test_fake(path: String, headers: HashMap<String, String>) -> Self {
163 Self::new(
164 hyper::Method::POST,
165 path,
166 String::new(),
167 headers,
168 bytes::Bytes::new(),
169 )
170 }
171}
172
173#[derive(Debug, Default, Clone)]
183pub struct FormData {
184 fields: HashMap<String, String>,
188 multi: HashMap<String, Vec<String>>,
194}
195
196impl FormData {
197 pub fn from_urlencoded(input: &str) -> Self {
199 let mut fields = HashMap::new();
200 let mut multi: HashMap<String, Vec<String>> = HashMap::new();
201 for pair in input.split('&') {
202 if pair.is_empty() {
203 continue;
204 }
205 let (raw_key, raw_val) = match pair.split_once('=') {
206 Some((k, v)) => (k, v),
207 None => (pair, ""),
208 };
209 let key = decode(raw_key);
210 let val = decode(raw_val);
211 fields.insert(key.clone(), val.clone());
212 multi.entry(key).or_default().push(val);
213 }
214 Self { fields, multi }
215 }
216
217 pub fn get(&self, key: &str) -> Option<&str> {
219 self.fields.get(key).map(|s| s.as_str())
220 }
221
222 pub fn get_all(&self, key: &str) -> &[String] {
227 self.multi.get(key).map(Vec::as_slice).unwrap_or(&[])
228 }
229
230 pub fn required(&self, key: &str) -> Result<&str> {
232 self.get(key)
233 .ok_or_else(|| Error::BadRequest(format!("field {key} is required")))
234 }
235
236 pub fn bool_flag(&self, key: &str) -> bool {
238 matches!(self.get(key), Some("on" | "true" | "1" | "yes"))
240 }
241
242 pub fn contains(&self, key: &str) -> bool {
244 self.fields.contains_key(key)
245 }
246
247 pub fn as_map(&self) -> &HashMap<String, String> {
249 &self.fields
250 }
251
252 pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
259 let key = key.into();
260 let value = value.into();
261 self.fields.insert(key.clone(), value.clone());
262 self.multi.insert(key, vec![value]);
263 }
264}
265
266fn decode(s: &str) -> String {
267 let spaced = s.replace('+', " ");
268 urlencoding::decode(&spaced)
269 .map(|c| c.into_owned())
270 .unwrap_or(spaced)
271}
272
273pub struct Response {
276 pub status: StatusCode,
277 pub headers: Vec<(String, String)>,
278 pub body: Bytes,
279}
280
281impl Response {
282 pub fn new(status: StatusCode, body: impl Into<Bytes>) -> Self {
284 Self {
285 status,
286 headers: Vec::new(),
287 body: body.into(),
288 }
289 }
290
291 pub fn ok(body: impl Into<Bytes>) -> Self {
293 Self::new(StatusCode::OK, body)
294 }
295
296 pub fn html(body: impl Into<String>) -> Self {
298 let text = body.into();
299 Self {
300 status: StatusCode::OK,
301 headers: vec![("content-type".into(), "text/html; charset=utf-8".into())],
302 body: Bytes::from(text),
303 }
304 }
305
306 pub fn json_raw(body: impl Into<String>) -> Self {
308 let text = body.into();
309 Self {
310 status: StatusCode::OK,
311 headers: vec![("content-type".into(), "application/json".into())],
312 body: Bytes::from(text),
313 }
314 }
315
316 pub fn redirect(to: impl Into<String>) -> Self {
318 let url = to.into();
319 Self {
320 status: StatusCode::SEE_OTHER,
321 headers: vec![("location".into(), url)],
322 body: Bytes::new(),
323 }
324 }
325
326 pub fn text(body: impl Into<String>) -> Self {
328 let text = body.into();
329 Self {
330 status: StatusCode::OK,
331 headers: vec![("content-type".into(), "text/plain; charset=utf-8".into())],
332 body: Bytes::from(text),
333 }
334 }
335
336 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
338 self.headers.push((name.into(), value.into()));
339 self
340 }
341
342 pub fn with_status(mut self, status: StatusCode) -> Self {
344 self.status = status;
345 self
346 }
347}
348
349pub(crate) fn response_from_error(err: &Error) -> Response {
350 let status = StatusCode::from_u16(err.status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
351 let body = err.client_message().to_string();
352 Response {
353 status,
354 headers: vec![("content-type".into(), "text/plain; charset=utf-8".into())],
355 body: Bytes::from(body),
356 }
357}
358
359#[cfg(test)]
360mod formdata_tests {
361 use super::FormData;
362
363 #[test]
364 fn duplicate_keys_collapse_for_get_but_preserved_in_get_all() {
365 let f = FormData::from_urlencoded("status=active&status=pending&status=resolved");
366 assert_eq!(f.get("status"), Some("resolved"));
369 assert_eq!(
372 f.get_all("status"),
373 &[
374 "active".to_string(),
375 "pending".to_string(),
376 "resolved".to_string()
377 ],
378 );
379 }
380
381 #[test]
382 fn get_all_returns_empty_slice_for_missing_key() {
383 let f = FormData::from_urlencoded("a=1");
384 assert!(f.get_all("not-a-key").is_empty());
385 }
386
387 #[test]
388 fn single_submission_is_visible_via_both_views() {
389 let f = FormData::from_urlencoded("a=1");
390 assert_eq!(f.get("a"), Some("1"));
391 assert_eq!(f.get_all("a"), &["1".to_string()]);
392 }
393
394 #[test]
395 fn set_resets_multi_view_to_single_entry() {
396 let mut f = FormData::from_urlencoded("status=active&status=pending");
397 f.set("status", "resolved");
398 assert_eq!(f.get("status"), Some("resolved"));
400 assert_eq!(f.get_all("status"), &["resolved".to_string()]);
401 }
402}