rocket_community/response/status.rs
1//! Contains types that set the status code and corresponding headers of a
2//! response.
3//!
4//! # Responding
5//!
6//! Types in this module designed to make it easier to construct correct
7//! responses with a given status code. Each type takes in the minimum number of
8//! parameters required to construct a correct response. Some types take in
9//! responders; when they do, the responder finalizes the response by writing
10//! out additional headers and, importantly, the body of the response.
11//!
12//! The [`Custom`] type allows responding with _any_ `Status` but _does not_
13//! ensure that all of the required headers are present. As a convenience,
14//! `(Status, R)` where `R: Responder` is _also_ a `Responder`, identical to
15//! `Custom`.
16//!
17//! ```rust
18//! # extern crate rocket_community as rocket;
19//! # use rocket::get;
20//! use rocket::http::Status;
21//!
22//! #[get("/")]
23//! fn index() -> (Status, &'static str) {
24//! (Status::NotFound, "Hey, there's no index!")
25//! }
26//! ```
27
28use std::borrow::Cow;
29use std::collections::hash_map::DefaultHasher;
30use std::hash::{Hash, Hasher};
31
32use crate::http::Status;
33use crate::request::Request;
34use crate::response::{self, Responder, Response};
35
36/// Sets the status of the response to 201 Created.
37///
38/// Sets the `Location` header and optionally the `ETag` header in the response.
39/// The body of the response, which identifies the created resource, can be set
40/// via the builder methods [`Created::body()`] and [`Created::tagged_body()`].
41/// While both builder methods set the responder, the [`Created::tagged_body()`]
42/// additionally computes a hash for the responder which is used as the value of
43/// the `ETag` header when responding.
44///
45/// # Example
46///
47/// ```rust
48/// # extern crate rocket_community as rocket;
49/// use rocket::response::status;
50///
51/// let response = status::Created::new("http://myservice.com/resource.json")
52/// .tagged_body("{ 'resource': 'Hello, world!' }");
53/// ```
54#[derive(Debug, Clone, PartialEq)]
55pub struct Created<R>(Cow<'static, str>, Option<R>, Option<u64>);
56
57impl<R> Created<R> {
58 /// Constructs a `Created` response with a `location` and no body.
59 ///
60 /// # Example
61 ///
62 /// ```rust
63 /// # extern crate rocket_community as rocket;
64 /// # use rocket::{get, routes, local::blocking::Client};
65 /// use rocket::response::status;
66 ///
67 /// #[get("/")]
68 /// fn create() -> status::Created<&'static str> {
69 /// status::Created::new("http://myservice.com/resource.json")
70 /// }
71 ///
72 /// # let client = Client::debug_with(routes![create]).unwrap();
73 /// let response = client.get("/").dispatch();
74 ///
75 /// let loc = response.headers().get_one("Location");
76 /// assert_eq!(loc, Some("http://myservice.com/resource.json"));
77 /// assert!(response.body().is_none());
78 /// ```
79 pub fn new<L: Into<Cow<'static, str>>>(location: L) -> Self {
80 Created(location.into(), None, None)
81 }
82
83 /// Adds `responder` as the body of `self`.
84 ///
85 /// Unlike [`tagged_body()`](self::Created::tagged_body()), this method
86 /// _does not_ result in an `ETag` header being set in the response.
87 ///
88 /// # Example
89 ///
90 /// ```rust
91 /// # extern crate rocket_community as rocket;
92 /// # use rocket::{get, routes, local::blocking::Client};
93 /// use rocket::response::status;
94 ///
95 /// #[get("/")]
96 /// fn create() -> status::Created<&'static str> {
97 /// status::Created::new("http://myservice.com/resource.json")
98 /// .body("{ 'resource': 'Hello, world!' }")
99 /// }
100 ///
101 /// # let client = Client::debug_with(routes![create]).unwrap();
102 /// let response = client.get("/").dispatch();
103 ///
104 /// let loc = response.headers().get_one("Location");
105 /// assert_eq!(loc, Some("http://myservice.com/resource.json"));
106 ///
107 /// let etag = response.headers().get_one("ETag");
108 /// assert_eq!(etag, None);
109 ///
110 /// let body = response.into_string();
111 /// assert_eq!(body.unwrap(), "{ 'resource': 'Hello, world!' }");
112 /// ```
113 pub fn body(mut self, responder: R) -> Self {
114 self.1 = Some(responder);
115 self
116 }
117
118 /// Adds `responder` as the body of `self`. Computes a hash of the
119 /// `responder` to be used as the value of the `ETag` header.
120 ///
121 /// # Example
122 ///
123 /// ```rust
124 /// # extern crate rocket_community as rocket;
125 /// # use rocket::{get, routes, local::blocking::Client};
126 /// use rocket::response::status;
127 ///
128 /// #[get("/")]
129 /// fn create() -> status::Created<&'static str> {
130 /// status::Created::new("http://myservice.com/resource.json")
131 /// .tagged_body("{ 'resource': 'Hello, world!' }")
132 /// }
133 ///
134 /// # let client = Client::debug_with(routes![create]).unwrap();
135 /// let response = client.get("/").dispatch();
136 ///
137 /// let loc = response.headers().get_one("Location");
138 /// assert_eq!(loc, Some("http://myservice.com/resource.json"));
139 ///
140 /// let etag = response.headers().get_one("ETag");
141 /// assert_eq!(etag, Some(r#""13046220615156895040""#));
142 ///
143 /// let body = response.into_string();
144 /// assert_eq!(body.unwrap(), "{ 'resource': 'Hello, world!' }");
145 /// ```
146 pub fn tagged_body(mut self, responder: R) -> Self
147 where
148 R: Hash,
149 {
150 let mut hasher = &mut DefaultHasher::default();
151 responder.hash(&mut hasher);
152 let hash = hasher.finish();
153 self.1 = Some(responder);
154 self.2 = Some(hash);
155 self
156 }
157}
158
159/// Sets the status code of the response to 201 Created. Sets the `Location`
160/// header to the parameter in the [`Created::new()`] constructor.
161///
162/// The optional responder, set via [`Created::body()`] or
163/// [`Created::tagged_body()`] finalizes the response if it exists. The wrapped
164/// responder should write the body of the response so that it contains
165/// information about the created resource. If no responder is provided, the
166/// response body will be empty.
167///
168/// In addition to setting the status code, `Location` header, and finalizing
169/// the response with the `Responder`, the `ETag` header is set conditionally if
170/// a hashable `Responder` is provided via [`Created::tagged_body()`]. The `ETag`
171/// header is set to a hash value of the responder.
172impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Created<R> {
173 fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
174 let mut response = Response::build();
175 if let Some(responder) = self.1 {
176 response.merge(responder.respond_to(req)?);
177 }
178
179 if let Some(hash) = self.2 {
180 response.raw_header("ETag", format!(r#""{}""#, hash));
181 }
182
183 response
184 .status(Status::Created)
185 .raw_header("Location", self.0)
186 .ok()
187 }
188}
189
190/// Sets the status of the response to 204 No Content.
191///
192/// The response body will be empty.
193///
194/// # Example
195///
196/// A 204 No Content response:
197///
198/// ```rust
199/// # extern crate rocket_community as rocket;
200/// # use rocket::get;
201/// use rocket::response::status;
202///
203/// #[get("/")]
204/// fn foo() -> status::NoContent {
205/// status::NoContent
206/// }
207/// ```
208#[derive(Debug, Clone, PartialEq)]
209pub struct NoContent;
210
211/// Sets the status code of the response to 204 No Content.
212impl<'r> Responder<'r, 'static> for NoContent {
213 fn respond_to(self, _: &'r Request<'_>) -> response::Result<'static> {
214 Response::build().status(Status::NoContent).ok()
215 }
216}
217
218/// Creates a response with a status code and underlying responder.
219///
220/// Note that this is equivalent to `(Status, R)`.
221///
222/// # Example
223///
224/// ```rust
225/// # extern crate rocket_community as rocket;
226/// # use rocket::get;
227/// use rocket::response::status;
228/// use rocket::http::Status;
229///
230/// #[get("/")]
231/// fn handler() -> status::Custom<&'static str> {
232/// status::Custom(Status::ImATeapot, "Hi!")
233/// }
234///
235/// // This is equivalent to the above.
236/// #[get("/")]
237/// fn handler2() -> (Status, &'static str) {
238/// (Status::ImATeapot, "Hi!")
239/// }
240/// ```
241#[derive(Debug, Clone, PartialEq)]
242pub struct Custom<R>(pub Status, pub R);
243
244/// Sets the status code of the response and then delegates the remainder of the
245/// response to the wrapped responder.
246impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for Custom<R> {
247 #[inline]
248 fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
249 Response::build_from(self.1.respond_to(req)?)
250 .status(self.0)
251 .ok()
252 }
253}
254
255impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for (Status, R) {
256 #[inline(always)]
257 fn respond_to(self, request: &'r Request<'_>) -> response::Result<'o> {
258 Custom(self.0, self.1).respond_to(request)
259 }
260}
261
262macro_rules! status_response {
263 ($T:ident $kind:expr) => {
264 /// Sets the status of the response to
265 #[doc = concat!($kind, concat!(" ([`Status::", stringify!($T), "`])."))]
266 ///
267 /// The remainder of the response is delegated to `self.0`.
268 /// # Examples
269 ///
270 /// A
271 #[doc = $kind]
272 /// response without a body:
273 ///
274 /// ```rust
275 /// # extern crate rocket_community as rocket;
276 /// # use rocket::get;
277 /// use rocket::response::status;
278 ///
279 /// #[get("/")]
280 #[doc = concat!("fn handler() -> status::", stringify!($T), "<()> {")]
281 #[doc = concat!(" status::", stringify!($T), "(())")]
282 /// }
283 /// ```
284 ///
285 /// A
286 #[doc = $kind]
287 /// response _with_ a body:
288 ///
289 /// ```rust
290 /// # extern crate rocket_community as rocket;
291 /// # use rocket::get;
292 /// use rocket::response::status;
293 ///
294 /// #[get("/")]
295 #[doc = concat!("fn handler() -> status::", stringify!($T), "<&'static str> {")]
296 #[doc = concat!(" status::", stringify!($T), "(\"body\")")]
297 /// }
298 /// ```
299 #[derive(Debug, Clone, PartialEq)]
300 pub struct $T<R>(pub R);
301
302 impl<'r, 'o: 'r, R: Responder<'r, 'o>> Responder<'r, 'o> for $T<R> {
303 #[inline(always)]
304 fn respond_to(self, req: &'r Request<'_>) -> response::Result<'o> {
305 Custom(Status::$T, self.0).respond_to(req)
306 }
307 }
308 };
309}
310
311status_response!(Accepted "202 Accepted");
312status_response!(BadRequest "400 Bad Request");
313status_response!(Unauthorized "401 Unauthorized");
314status_response!(Forbidden "403 Forbidden");
315status_response!(NotFound "404 NotFound");
316status_response!(Conflict "409 Conflict");
317
318// The following are unimplemented.
319// 206 Partial Content (variant), 203 Non-Authoritative Information (headers).