thruster_context/
basic_context.rs

1use std::collections::HashMap;
2use std::str;
3
4use thruster_core::context::Context;
5use thruster_core::response::Response;
6use thruster_core::request::Request;
7
8use thruster_middleware::query_params::HasQueryParams;
9use thruster_middleware::cookies::{Cookie, HasCookies, CookieOptions, SameSite};
10
11pub fn generate_context(request: Request) -> BasicContext {
12  let mut ctx = BasicContext::new();
13  ctx.params = request.params().clone();
14  ctx.request = request;
15
16  ctx
17}
18
19#[derive(Default)]
20pub struct BasicContext {
21  response: Response,
22  pub cookies: Vec<Cookie>,
23  pub params: HashMap<String, String>,
24  pub query_params: HashMap<String, String>,
25  pub request: Request,
26  pub status: u32,
27  pub headers: HashMap<String, String>,
28}
29
30impl BasicContext {
31  pub fn new() -> BasicContext {
32    let mut ctx = BasicContext {
33      response: Response::new(),
34      cookies: Vec::new(),
35      params: HashMap::new(),
36      query_params: HashMap::new(),
37      request: Request::new(),
38      headers: HashMap::new(),
39      status: 200
40    };
41
42    ctx.set("Server", "Thruster");
43
44    ctx
45  }
46
47  ///
48  /// Set the body as a string
49  ///
50  pub fn body(&mut self, body_string: &str) {
51    self.response.body_bytes_from_vec(body_string.as_bytes().to_vec());
52  }
53
54  pub fn get_body(&self) -> String {
55    str::from_utf8(&self.response.response).unwrap_or("").to_owned()
56  }
57
58  ///
59  /// Set a header on the response
60  ///
61  pub fn set(&mut self, key: &str, value: &str) {
62    self.response.header(key, value);
63  }
64
65  ///
66  /// Remove a header on the response
67  ///
68  pub fn remove(&mut self, key: &str) {
69    self.headers.remove(key);
70  }
71
72  ///
73  /// Set the response status code
74  ///
75  pub fn status(&mut self, code: u32) {
76    self.status = code;
77  }
78
79  ///
80  /// Set the response `Content-Type`. A shortcode for
81  ///
82  /// ```ignore
83  /// ctx.set("Content-Type", "some-val");
84  /// ```
85  ///
86  pub fn content_type(&mut self, c_type: &str) {
87    self.set("Content-Type", c_type);
88  }
89
90  ///
91  /// Set up a redirect, will default to 302, but can be changed after
92  /// the fact.
93  ///
94  /// ```ignore
95  /// ctx.set("Location", "/some-path");
96  /// ctx.status(302);
97  /// ```
98  ///
99  pub fn redirect(&mut self, destination: &str) {
100    self.status(302);
101
102    self.set("Location", destination);
103  }
104
105  ///
106  /// Sets a cookie on the response
107  ///
108  pub fn cookie(&mut self, name: &str, value: &str, options: &CookieOptions) {
109    let cookie_value = match self.headers.get("Set-Cookie") {
110      Some(val) => format!("{}, {}", val, self.cookify_options(name, value, &options)),
111      None => self.cookify_options(name, value, &options)
112    };
113
114    self.set("Set-Cookie", &cookie_value);
115  }
116
117  fn cookify_options(&self, name: &str, value: &str, options: &CookieOptions) -> String {
118    let mut pieces = vec![format!("Path={}", options.path)];
119
120    if options.expires > 0 {
121      pieces.push(format!("Expires={}", options.expires));
122    }
123
124    if options.max_age > 0 {
125      pieces.push(format!("Max-Age={}", options.max_age));
126    }
127
128    if !options.domain.is_empty() {
129      pieces.push(format!("Domain={}", options.domain));
130    }
131
132    if options.secure {
133      pieces.push("Secure".to_owned());
134    }
135
136    if options.http_only {
137      pieces.push("HttpOnly".to_owned());
138    }
139
140    if let Some(ref same_site) = options.same_site {
141      match same_site {
142        SameSite::Strict => pieces.push("SameSite=Strict".to_owned()),
143        SameSite::Lax => pieces.push("SameSite=Lax".to_owned())
144      };
145    }
146
147    format!("{}={}; {}", name, value, pieces.join(", "))
148  }
149}
150
151impl Context for BasicContext {
152  type Response = Response;
153
154  fn get_response(mut self) -> Self::Response {
155    self.response.status_code(self.status, "");
156
157    self.response
158  }
159
160  fn set_body(&mut self, body: Vec<u8>) {
161    self.response.body_bytes_from_vec(body);
162  }
163}
164
165impl HasQueryParams for BasicContext {
166  fn set_query_params(&mut self, query_params: HashMap<String, String>) {
167    self.query_params = query_params;
168  }
169
170  fn route(&self) -> &str {
171    self.request.path()
172  }
173}
174
175impl HasCookies for BasicContext {
176  fn set_cookies(&mut self, cookies: Vec<Cookie>) {
177    self.cookies = cookies;
178  }
179
180  fn headers(&self) -> HashMap<String, String> {
181    self.request.headers()
182  }
183}