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