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).