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)]
15pub struct Context {
16 inner: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
17}
18
19impl Context {
20 pub fn insert<T: Any + Send + Sync>(&mut self, value: T) {
21 self.inner.insert(TypeId::of::<T>(), Box::new(value));
22 }
23
24 pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> {
25 self.inner.get(&TypeId::of::<T>()).and_then(|b| b.downcast_ref::<T>())
26 }
27
28 pub fn get_mut<T: Any + Send + Sync>(&mut self) -> Option<&mut T> {
29 self.inner
30 .get_mut(&TypeId::of::<T>())
31 .and_then(|b| b.downcast_mut::<T>())
32 }
33}
34
35pub struct Request {
36 method: Method,
37 path: String,
38 query: String,
39 headers: HashMap<String, String>,
40 params: HashMap<String, String>,
41 body: Bytes,
42 ctx: Context,
43}
44
45impl Request {
46 pub(crate) fn new(
47 method: Method,
48 path: String,
49 query: String,
50 headers: HashMap<String, String>,
51 body: Bytes,
52 ) -> Self {
53 Self {
54 method,
55 path,
56 query,
57 headers,
58 params: HashMap::new(),
59 body,
60 ctx: Context::default(),
61 }
62 }
63
64 pub fn method(&self) -> &Method {
65 &self.method
66 }
67
68 pub fn path(&self) -> &str {
69 &self.path
70 }
71
72 pub fn query_string(&self) -> &str {
73 &self.query
74 }
75
76 pub fn query(&self) -> FormData {
77 FormData::from_urlencoded(&self.query)
78 }
79
80 pub fn header(&self, name: &str) -> Option<&str> {
81 self.headers.get(&name.to_ascii_lowercase()).map(|s| s.as_str())
82 }
83
84 pub fn param(&self, name: &str) -> Option<&str> {
85 self.params.get(name).map(|s| s.as_str())
86 }
87
88 pub fn body(&self) -> &[u8] {
89 &self.body
90 }
91
92 pub fn body_text(&self) -> Result<&str> {
93 std::str::from_utf8(&self.body)
94 .map_err(|_| Error::BadRequest("body is not valid utf-8".into()))
95 }
96
97 pub fn form(&self) -> Result<FormData> {
98 let text = self.body_text()?;
99 Ok(FormData::from_urlencoded(text))
100 }
101
102 pub fn ctx(&self) -> &Context {
103 &self.ctx
104 }
105
106 pub fn ctx_mut(&mut self) -> &mut Context {
107 &mut self.ctx
108 }
109
110 pub(crate) fn set_params(&mut self, params: HashMap<String, String>) {
111 self.params = params;
112 }
113}
114
115#[derive(Debug, Default, Clone)]
118pub struct FormData {
119 fields: HashMap<String, String>,
120}
121
122impl FormData {
123 pub fn from_urlencoded(input: &str) -> Self {
124 let mut fields = HashMap::new();
125 for pair in input.split('&') {
126 if pair.is_empty() {
127 continue;
128 }
129 let (raw_key, raw_val) = match pair.split_once('=') {
130 Some((k, v)) => (k, v),
131 None => (pair, ""),
132 };
133 let key = decode(raw_key);
134 let val = decode(raw_val);
135 fields.insert(key, val);
136 }
137 Self { fields }
138 }
139
140 pub fn get(&self, key: &str) -> Option<&str> {
141 self.fields.get(key).map(|s| s.as_str())
142 }
143
144 pub fn required(&self, key: &str) -> Result<&str> {
145 self.get(key)
146 .ok_or_else(|| Error::BadRequest(format!("field {key} is required")))
147 }
148
149 pub fn bool_flag(&self, key: &str) -> bool {
150 matches!(self.get(key), Some("on" | "true" | "1" | "yes"))
152 }
153
154 pub fn contains(&self, key: &str) -> bool {
155 self.fields.contains_key(key)
156 }
157
158 pub fn as_map(&self) -> &HashMap<String, String> {
159 &self.fields
160 }
161}
162
163fn decode(s: &str) -> String {
164 let spaced = s.replace('+', " ");
166 urlencoding::decode(&spaced).map(|c| c.into_owned()).unwrap_or(spaced)
167}
168
169pub struct Response {
171 pub status: StatusCode,
172 pub headers: Vec<(String, String)>,
173 pub body: Bytes,
174}
175
176impl Response {
177 pub fn new(status: StatusCode, body: impl Into<Bytes>) -> Self {
178 Self {
179 status,
180 headers: Vec::new(),
181 body: body.into(),
182 }
183 }
184
185 pub fn ok(body: impl Into<Bytes>) -> Self {
186 Self::new(StatusCode::OK, body)
187 }
188
189 pub fn html(body: impl Into<String>) -> Self {
190 let text = body.into();
191 Self {
192 status: StatusCode::OK,
193 headers: vec![("content-type".into(), "text/html; charset=utf-8".into())],
194 body: Bytes::from(text),
195 }
196 }
197
198 pub fn json_raw(body: impl Into<String>) -> Self {
199 let text = body.into();
200 Self {
201 status: StatusCode::OK,
202 headers: vec![("content-type".into(), "application/json".into())],
203 body: Bytes::from(text),
204 }
205 }
206
207 pub fn redirect(to: impl Into<String>) -> Self {
208 let url = to.into();
209 Self {
210 status: StatusCode::SEE_OTHER,
211 headers: vec![("location".into(), url)],
212 body: Bytes::new(),
213 }
214 }
215
216 pub fn text(body: impl Into<String>) -> Self {
217 let text = body.into();
218 Self {
219 status: StatusCode::OK,
220 headers: vec![("content-type".into(), "text/plain; charset=utf-8".into())],
221 body: Bytes::from(text),
222 }
223 }
224
225 pub fn with_header(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
226 self.headers.push((name.into(), value.into()));
227 self
228 }
229
230 pub fn with_status(mut self, status: StatusCode) -> Self {
231 self.status = status;
232 self
233 }
234}
235
236pub(crate) fn response_from_error(err: &Error) -> Response {
237 let status = StatusCode::from_u16(err.status()).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR);
238 let body = err.client_message().to_string();
239 Response {
240 status,
241 headers: vec![("content-type".into(), "text/plain; charset=utf-8".into())],
242 body: Bytes::from(body),
243 }
244}