1use std::any::Any;
5use std::collections::{BTreeMap, HashMap};
6use std::fmt::{Debug, Display};
7use std::sync::Arc;
8use std::time::SystemTime;
9use bytes::Bytes;
10use chrono::{DateTime, FixedOffset};
11use maplit::hashmap;
12
13use crate::headers::HeaderValue;
14
15#[derive(Debug, Clone, PartialEq)]
17pub struct WebmachineRequest {
18 pub request_path: String,
20 pub base_path: String,
22 pub path_vars: HashMap<String, String>,
24 pub method: String,
26 pub headers: HashMap<String, Vec<HeaderValue>>,
28 pub body: Option<Bytes>,
30 pub query: HashMap<String, Vec<String>>
32}
33
34impl Default for WebmachineRequest {
35 fn default() -> WebmachineRequest {
37 WebmachineRequest {
38 request_path: "/".to_string(),
39 base_path: "/".to_string(),
40 path_vars: Default::default(),
41 method: "GET".to_string(),
42 headers: HashMap::new(),
43 body: None,
44 query: HashMap::new()
45 }
46 }
47}
48
49impl WebmachineRequest {
50 pub fn content_type(&self) -> HeaderValue {
53 match self.headers.keys().find(|k| k.to_uppercase() == "CONTENT-TYPE") {
54 Some(header) => match self.headers.get(header).unwrap().first() {
55 Some(value) => value.clone(),
56 None => HeaderValue::json()
57 },
58 None => HeaderValue::json()
59 }
60 }
61
62 pub fn is_put_or_post(&self) -> bool {
64 ["PUT", "POST"].contains(&self.method.to_uppercase().as_str())
65 }
66
67 pub fn is_get_or_head(&self) -> bool {
69 ["GET", "HEAD"].contains(&self.method.to_uppercase().as_str())
70 }
71
72 pub fn is_get(&self) -> bool {
74 self.method.to_uppercase() == "GET"
75 }
76
77 pub fn is_options(&self) -> bool {
79 self.method.to_uppercase() == "OPTIONS"
80 }
81
82 pub fn is_put(&self) -> bool {
84 self.method.to_uppercase() == "PUT"
85 }
86
87 pub fn is_post(&self) -> bool {
89 self.method.to_uppercase() == "POST"
90 }
91
92 pub fn is_delete(&self) -> bool {
94 self.method.to_uppercase() == "DELETE"
95 }
96
97 pub fn has_accept_header(&self) -> bool {
99 self.has_header("ACCEPT")
100 }
101
102 pub fn accept(&self) -> Vec<HeaderValue> {
104 self.find_header("ACCEPT")
105 }
106
107 pub fn has_accept_language_header(&self) -> bool {
109 self.has_header("ACCEPT-LANGUAGE")
110 }
111
112 pub fn accept_language(&self) -> Vec<HeaderValue> {
114 self.find_header("ACCEPT-LANGUAGE")
115 }
116
117 pub fn has_accept_charset_header(&self) -> bool {
119 self.has_header("ACCEPT-CHARSET")
120 }
121
122 pub fn accept_charset(&self) -> Vec<HeaderValue> {
124 self.find_header("ACCEPT-CHARSET")
125 }
126
127 pub fn has_accept_encoding_header(&self) -> bool {
129 self.has_header("ACCEPT-ENCODING")
130 }
131
132 pub fn accept_encoding(&self) -> Vec<HeaderValue> {
134 self.find_header("ACCEPT-ENCODING")
135 }
136
137 pub fn has_header(&self, header: &str) -> bool {
139 self.headers.keys().find(|k| k.to_uppercase() == header.to_uppercase()).is_some()
140 }
141
142 pub fn find_header(&self, header: &str) -> Vec<HeaderValue> {
145 match self.headers.keys().find(|k| k.to_uppercase() == header.to_uppercase()) {
146 Some(header) => self.headers.get(header).unwrap().clone(),
147 None => Vec::new()
148 }
149 }
150
151 pub fn has_header_value(&self, header: &str, value: &str) -> bool {
153 match self.headers.keys().find(|k| k.to_uppercase() == header.to_uppercase()) {
154 Some(header) => match self.headers.get(header).unwrap().iter().find(|val| *val == value) {
155 Some(_) => true,
156 None => false
157 },
158 None => false
159 }
160 }
161}
162
163#[derive(Debug, Clone, PartialEq)]
165pub struct WebmachineResponse {
166 pub status: u16,
168 pub headers: BTreeMap<String, Vec<HeaderValue>>,
170 pub body: Option<Bytes>
172}
173
174impl WebmachineResponse {
175 pub fn default() -> WebmachineResponse {
177 WebmachineResponse {
178 status: 200,
179 headers: BTreeMap::new(),
180 body: None
181 }
182 }
183
184 pub fn has_header(&self, header: &str) -> bool {
186 self.headers.keys().find(|k| k.to_uppercase() == header.to_uppercase()).is_some()
187 }
188
189 pub fn add_header(&mut self, header: &str, values: Vec<HeaderValue>) {
191 self.headers.insert(header.to_string(), values);
192 }
193
194 pub fn add_headers(&mut self, headers: HashMap<String, Vec<String>>) {
196 for (k, v) in headers {
197 self.headers.insert(k, v.iter().map(HeaderValue::basic).collect());
198 }
199 }
200
201 pub fn add_cors_headers(&mut self, allowed_methods: &[&str]) {
203 let cors_headers = WebmachineResponse::cors_headers(allowed_methods);
204 for (k, v) in cors_headers {
205 self.add_header(k.as_str(), v.iter().map(HeaderValue::basic).collect());
206 }
207 }
208
209 pub fn cors_headers(allowed_methods: &[&str]) -> HashMap<String, Vec<String>> {
211 hashmap!{
212 "Access-Control-Allow-Origin".to_string() => vec!["*".to_string()],
213 "Access-Control-Allow-Methods".to_string() => allowed_methods.iter().map(|v| v.to_string()).collect(),
214 "Access-Control-Allow-Headers".to_string() => vec!["Content-Type".to_string()]
215 }
216 }
217
218 pub fn has_body(&self) -> bool {
220 match &self.body {
221 &None => false,
222 &Some(ref body) => !body.is_empty()
223 }
224 }
225}
226
227pub trait MetaDataThing: Any + Debug {}
229
230#[derive(Debug, Clone)]
232pub enum MetaDataValue {
233 Empty,
235 String(String),
237 UInteger(u64),
239 Integer(i64),
241 Anything(Arc<dyn MetaDataThing + Send + Sync>)
243}
244
245impl MetaDataValue {
246 pub fn is_empty(&self) -> bool {
248 match self {
249 MetaDataValue::Empty => true,
250 _ => false
251 }
252 }
253
254 pub fn as_string(&self) -> Option<String> {
256 match self {
257 MetaDataValue::String(s) => Some(s.clone()),
258 _ => None
259 }
260 }
261
262 pub fn as_uint(&self) -> Option<u64> {
264 match self {
265 MetaDataValue::UInteger(u) => Some(*u),
266 _ => None
267 }
268 }
269
270 pub fn as_int(&self) -> Option<i64> {
272 match self {
273 MetaDataValue::Integer(i) => Some(*i),
274 _ => None
275 }
276 }
277
278 pub fn as_anything(&self) -> Option<&(dyn Any + Send + Sync)> {
280 match self {
281 MetaDataValue::Anything(thing) => Some(thing.as_ref()),
282 _ => None
283 }
284 }
285}
286
287impl Display for MetaDataValue {
288 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
289 match self {
290 MetaDataValue::String(s) => write!(f, "{}", s.as_str()),
291 MetaDataValue::UInteger(u) => write!(f, "{}", *u),
292 MetaDataValue::Integer(i) => write!(f, "{}", *i),
293 MetaDataValue::Empty => Ok(()),
294 MetaDataValue::Anything(thing) => write!(f, "any({:?})", thing)
295 }
296 }
297}
298
299impl Default for MetaDataValue {
300 fn default() -> Self {
301 MetaDataValue::Empty
302 }
303}
304
305impl Default for &MetaDataValue {
306 fn default() -> Self {
307 &MetaDataValue::Empty
308 }
309}
310
311impl From<String> for MetaDataValue {
312 fn from(value: String) -> Self {
313 MetaDataValue::String(value)
314 }
315}
316
317impl From<&String> for MetaDataValue {
318 fn from(value: &String) -> Self {
319 MetaDataValue::String(value.clone())
320 }
321}
322
323impl From<&str> for MetaDataValue {
324 fn from(value: &str) -> Self {
325 MetaDataValue::String(value.to_string())
326 }
327}
328
329impl From<u16> for MetaDataValue {
330 fn from(value: u16) -> Self {
331 MetaDataValue::UInteger(value as u64)
332 }
333}
334
335impl From<i16> for MetaDataValue {
336 fn from(value: i16) -> Self {
337 MetaDataValue::Integer(value as i64)
338 }
339}
340
341impl From<u64> for MetaDataValue {
342 fn from(value: u64) -> Self {
343 MetaDataValue::UInteger(value)
344 }
345}
346
347impl From<i64> for MetaDataValue {
348 fn from(value: i64) -> Self {
349 MetaDataValue::Integer(value)
350 }
351}
352
353#[derive(Debug, Clone)]
355pub struct WebmachineContext {
356 pub request: WebmachineRequest,
358 pub response: WebmachineResponse,
360 pub selected_media_type: Option<String>,
362 pub selected_language: Option<String>,
364 pub selected_charset: Option<String>,
366 pub selected_encoding: Option<String>,
368 pub if_unmodified_since: Option<DateTime<FixedOffset>>,
370 pub if_modified_since: Option<DateTime<FixedOffset>>,
372 pub redirect: bool,
374 pub new_resource: bool,
376 pub metadata: HashMap<String, MetaDataValue>,
378 pub start_time: SystemTime
380}
381
382impl WebmachineContext {
383 pub fn downcast_metadata_value<'a, T: 'static>(&'a self, key: &'a str) -> Option<&'a T> {
385 self.metadata.get(key)
386 .and_then(|value| value.as_anything())
387 .and_then(|value| value.downcast_ref())
388 }
389}
390
391impl Default for WebmachineContext {
392 fn default() -> WebmachineContext {
394 WebmachineContext {
395 request: WebmachineRequest::default(),
396 response: WebmachineResponse::default(),
397 selected_media_type: None,
398 selected_language: None,
399 selected_charset: None,
400 selected_encoding: None,
401 if_unmodified_since: None,
402 if_modified_since: None,
403 redirect: false,
404 new_resource: false,
405 metadata: HashMap::new(),
406 start_time: SystemTime::now()
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use expectest::prelude::*;
414
415 use crate::headers::*;
416
417 use super::*;
418
419 #[test]
420 fn request_does_not_have_header_test() {
421 let request = WebmachineRequest {
422 .. WebmachineRequest::default()
423 };
424 expect!(request.has_header("Vary")).to(be_false());
425 expect!(request.has_header_value("Vary", "*")).to(be_false());
426 }
427
428 #[test]
429 fn request_with_empty_header_test() {
430 let request = WebmachineRequest {
431 headers: hashmap!{ "HeaderA".to_string() => Vec::new() },
432 .. WebmachineRequest::default()
433 };
434 expect!(request.has_header("HeaderA")).to(be_true());
435 expect!(request.has_header_value("HeaderA", "*")).to(be_false());
436 }
437
438 #[test]
439 fn request_with_header_single_value_test() {
440 let request = WebmachineRequest {
441 headers: hashmap!{ "HeaderA".to_string() => vec![h!("*")] },
442 .. WebmachineRequest::default()
443 };
444 expect!(request.has_header("HeaderA")).to(be_true());
445 expect!(request.has_header_value("HeaderA", "*")).to(be_true());
446 expect!(request.has_header_value("HeaderA", "other")).to(be_false());
447 }
448
449 #[test]
450 fn request_with_header_multiple_value_test() {
451 let request = WebmachineRequest {
452 headers: hashmap!{ "HeaderA".to_string() => vec![h!("*"), h!("other")]},
453 .. WebmachineRequest::default()
454 };
455 expect!(request.has_header("HeaderA")).to(be_true());
456 expect!(request.has_header_value("HeaderA", "*")).to(be_true());
457 expect!(request.has_header_value("HeaderA", "other")).to(be_true());
458 expect!(request.has_header_value("HeaderA", "other2")).to(be_false());
459 }
460}