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