rocket_http/
status.rs

1use std::fmt;
2
3/// Enumeration of HTTP status classes.
4#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
5pub enum StatusClass {
6    /// Indicates a provisional response: a status code of 1XX.
7    Informational,
8    /// Indicates that a request has succeeded: a status code of 2XX.
9    Success,
10    /// Indicates that further action needs to be taken by the user agent in
11    /// order to fulfill the request: a status code of 3XX.
12    Redirection,
13    /// Intended for cases in which the client seems to have erred: a status
14    /// code of 4XX.
15    ClientError,
16    /// Indicates cases in which the server is aware that it has erred or is
17    /// incapable of performing the request: a status code of 5XX.
18    ServerError,
19    /// Indicates that the status code is nonstandard and unknown: all other
20    /// status codes.
21    Unknown
22}
23
24macro_rules! class_check_fn {
25    ($func:ident, $type:expr, $variant:ident) => (
26        /// Returns `true` if `self` is a `StatusClass` of
27        #[doc=$type]
28        /// Returns `false` otherwise.
29        #[inline(always)]
30        pub fn $func(&self) -> bool {
31            *self == StatusClass::$variant
32        }
33    )
34}
35
36impl StatusClass {
37    class_check_fn!(is_informational, "`Informational` (1XX).", Informational);
38    class_check_fn!(is_success, "`Success` (2XX).", Success);
39    class_check_fn!(is_redirection, "`Redirection` (3XX).", Redirection);
40    class_check_fn!(is_client_error, "`ClientError` (4XX).", ClientError);
41    class_check_fn!(is_server_error, "`ServerError` (5XX).", ServerError);
42    class_check_fn!(is_unknown, "`Unknown`.", Unknown);
43}
44
45/// Structure representing an HTTP status: an integer code.
46///
47/// A `Status` should rarely be created directly. Instead, an associated
48/// constant should be used; one is declared for every status defined in the
49/// HTTP standard. If a custom status code _must_ be created, note that it is
50/// not possible to set a custom reason phrase.
51///
52/// ```rust
53/// # extern crate rocket;
54/// use rocket::http::Status;
55///
56/// // Create a status from a known constant.
57/// let ok = Status::Ok;
58/// assert_eq!(ok.code, 200);
59/// assert_eq!(ok.reason(), Some("OK"));
60///
61/// let not_found = Status::NotFound;
62/// assert_eq!(not_found.code, 404);
63/// assert_eq!(not_found.reason(), Some("Not Found"));
64///
65/// // Or from a status code: `reason()` returns the phrase when known.
66/// let gone = Status::new(410);
67/// assert_eq!(gone.code, 410);
68/// assert_eq!(gone.reason(), Some("Gone"));
69///
70/// // `reason()` returns `None` when unknown.
71/// let custom = Status::new(599);
72/// assert_eq!(custom.code, 599);
73/// assert_eq!(custom.reason(), None);
74/// ```
75///
76/// # Responding
77///
78/// To set a custom `Status` on a response, use a [`response::status`]
79/// responder, which enforces correct status-based responses. Alternatively,
80/// respond with `(Status, T)` where `T: Responder`, but beware that the
81/// response may be invalid if it requires additional headers.
82///
83/// ```rust
84/// # extern crate rocket;
85/// # use rocket::get;
86/// use rocket::http::Status;
87///
88/// #[get("/")]
89/// fn index() -> (Status, &'static str) {
90///     (Status::NotFound, "Hey, there's no index!")
91/// }
92/// ```
93///
94/// [`response::status`]: ../response/status/index.html
95///
96/// # (De)serialization
97///
98/// `Status` is both `Serialize` and `Deserialize`, represented as a `u16`. For
99/// example, [`Status::Ok`] (de)serializes from/to `200`. Any integer in the
100/// range `[100, 600)` is allowed to deserialize into a `Status`.`
101///
102/// ```rust
103/// # #[cfg(feature = "serde")] mod serde {
104/// # use serde_ as serde;
105/// use serde::{Serialize, Deserialize};
106/// use rocket::http::Status;
107///
108/// #[derive(Deserialize, Serialize)]
109/// # #[serde(crate = "serde_")]
110/// struct Foo {
111///     status: Status,
112/// }
113/// # }
114/// ```
115#[derive(Debug, Clone, Copy)]
116pub struct Status {
117    /// The HTTP status code associated with this status.
118    pub code: u16,
119}
120
121impl Default for Status {
122    fn default() -> Self {
123        Status::Ok
124    }
125}
126
127macro_rules! ctrs {
128    ($($code:expr, $code_str:expr, $name:ident => $reason:expr),+) => {
129        $(
130            #[doc="[`Status`] with code <b>"]
131            #[doc=$code_str]
132            #[doc="</b>."]
133            #[allow(non_upper_case_globals)]
134            pub const $name: Status = Status { code: $code };
135        )+
136
137        /// Creates a new `Status` with `code`. This should be used _only_ to
138        /// construct non-standard HTTP statuses. Use an associated constant for
139        /// standard statuses.
140        ///
141        /// # Example
142        ///
143        /// Create a custom `299` status:
144        ///
145        /// ```rust
146        /// # extern crate rocket;
147        /// use rocket::http::Status;
148        ///
149        /// let custom = Status::new(299);
150        /// assert_eq!(custom.code, 299);
151        /// ```
152        pub const fn new(code: u16) -> Status {
153            Status { code }
154        }
155
156        /// Returns the class of a given status.
157        ///
158        /// # Example
159        ///
160        /// ```rust
161        /// # extern crate rocket;
162        /// use rocket::http::{Status, StatusClass};
163        ///
164        /// let processing = Status::Processing;
165        /// assert_eq!(processing.class(), StatusClass::Informational);
166        ///
167        /// let ok = Status::Ok;
168        /// assert_eq!(ok.class(), StatusClass::Success);
169        ///
170        /// let see_other = Status::SeeOther;
171        /// assert_eq!(see_other.class(), StatusClass::Redirection);
172        ///
173        /// let not_found = Status::NotFound;
174        /// assert_eq!(not_found.class(), StatusClass::ClientError);
175        ///
176        /// let internal_error = Status::InternalServerError;
177        /// assert_eq!(internal_error.class(), StatusClass::ServerError);
178        ///
179        /// let custom = Status::new(600);
180        /// assert_eq!(custom.class(), StatusClass::Unknown);
181        /// ```
182        pub const fn class(self) -> StatusClass {
183            match self.code / 100 {
184                1 => StatusClass::Informational,
185                2 => StatusClass::Success,
186                3 => StatusClass::Redirection,
187                4 => StatusClass::ClientError,
188                5 => StatusClass::ServerError,
189                _ => StatusClass::Unknown
190            }
191        }
192
193        /// Returns a Status given a standard status code `code`. If `code` is
194        /// not a known status code, `None` is returned.
195        ///
196        /// # Example
197        ///
198        /// Create a `Status` from a known `code`:
199        ///
200        /// ```rust
201        /// # extern crate rocket;
202        /// use rocket::http::Status;
203        ///
204        /// let not_found = Status::from_code(404);
205        /// assert_eq!(not_found, Some(Status::NotFound));
206        /// ```
207        ///
208        /// Create a `Status` from an unknown `code`:
209        ///
210        /// ```rust
211        /// # extern crate rocket;
212        /// use rocket::http::Status;
213        ///
214        /// let unknown = Status::from_code(600);
215        /// assert!(unknown.is_none());
216        /// ```
217        pub const fn from_code(code: u16) -> Option<Status> {
218            match code {
219                $($code => Some(Status::$name),)+
220                _ => None
221            }
222        }
223
224        /// Returns the canonical reason phrase if `self` corresponds to a
225        /// canonical, known status code. Otherwise, returns `None`.
226        ///
227        /// # Example
228        ///
229        /// Reason phrase from a known `code`:
230        ///
231        /// ```rust
232        /// # extern crate rocket;
233        /// use rocket::http::Status;
234        ///
235        /// assert_eq!(Status::Created.reason(), Some("Created"));
236        /// assert_eq!(Status::new(200).reason(), Some("OK"));
237        /// ```
238        ///
239        /// Absent phrase from an unknown `code`:
240        ///
241        /// ```rust
242        /// # extern crate rocket;
243        /// use rocket::http::Status;
244        ///
245        /// assert_eq!(Status::new(499).reason(), None);
246        /// ```
247        pub const fn reason(&self) -> Option<&'static str> {
248            match self.code {
249                $($code => Some($reason),)+
250                _ => None
251            }
252        }
253
254        /// Returns the canonical reason phrase if `self` corresponds to a
255        /// canonical, known status code, or an unspecified but relevant reason
256        /// phrase otherwise.
257        ///
258        /// # Example
259        ///
260        /// ```rust
261        /// # extern crate rocket;
262        /// use rocket::http::Status;
263        ///
264        /// assert_eq!(Status::NotFound.reason_lossy(), "Not Found");
265        /// assert_eq!(Status::new(100).reason_lossy(), "Continue");
266        /// assert!(!Status::new(699).reason_lossy().is_empty());
267        /// ```
268        pub const fn reason_lossy(&self) -> &'static str {
269            if let Some(lossless) = self.reason() {
270                return lossless;
271            }
272
273            match self.class() {
274                StatusClass::Informational => "Informational",
275                StatusClass::Success => "Success",
276                StatusClass::Redirection => "Redirection",
277                StatusClass::ClientError => "Client Error",
278                StatusClass::ServerError => "Server Error",
279                StatusClass::Unknown => "Unknown"
280            }
281        }
282    };
283}
284
285impl Status {
286    ctrs! {
287        100, "100", Continue => "Continue",
288        101, "101", SwitchingProtocols => "Switching Protocols",
289        102, "102", Processing => "Processing",
290        200, "200", Ok => "OK",
291        201, "201", Created => "Created",
292        202, "202", Accepted => "Accepted",
293        203, "203", NonAuthoritativeInformation => "Non-Authoritative Information",
294        204, "204", NoContent => "No Content",
295        205, "205", ResetContent => "Reset Content",
296        206, "206", PartialContent => "Partial Content",
297        207, "207", MultiStatus => "Multi-Status",
298        208, "208", AlreadyReported => "Already Reported",
299        226, "226", ImUsed => "IM Used",
300        300, "300", MultipleChoices => "Multiple Choices",
301        301, "301", MovedPermanently => "Moved Permanently",
302        302, "302", Found => "Found",
303        303, "303", SeeOther => "See Other",
304        304, "304", NotModified => "Not Modified",
305        305, "305", UseProxy => "Use Proxy",
306        307, "307", TemporaryRedirect => "Temporary Redirect",
307        308, "308", PermanentRedirect => "Permanent Redirect",
308        400, "400", BadRequest => "Bad Request",
309        401, "401", Unauthorized => "Unauthorized",
310        402, "402", PaymentRequired => "Payment Required",
311        403, "403", Forbidden => "Forbidden",
312        404, "404", NotFound => "Not Found",
313        405, "405", MethodNotAllowed => "Method Not Allowed",
314        406, "406", NotAcceptable => "Not Acceptable",
315        407, "407", ProxyAuthenticationRequired => "Proxy Authentication Required",
316        408, "408", RequestTimeout => "Request Timeout",
317        409, "409", Conflict => "Conflict",
318        410, "410", Gone => "Gone",
319        411, "411", LengthRequired => "Length Required",
320        412, "412", PreconditionFailed => "Precondition Failed",
321        413, "413", PayloadTooLarge => "Payload Too Large",
322        414, "414", UriTooLong => "URI Too Long",
323        415, "415", UnsupportedMediaType => "Unsupported Media Type",
324        416, "416", RangeNotSatisfiable => "Range Not Satisfiable",
325        417, "417", ExpectationFailed => "Expectation Failed",
326        418, "418", ImATeapot => "I'm a teapot",
327        421, "421", MisdirectedRequest => "Misdirected Request",
328        422, "422", UnprocessableEntity => "Unprocessable Entity",
329        423, "423", Locked => "Locked",
330        424, "424", FailedDependency => "Failed Dependency",
331        426, "426", UpgradeRequired => "Upgrade Required",
332        428, "428", PreconditionRequired => "Precondition Required",
333        429, "429", TooManyRequests => "Too Many Requests",
334        431, "431", RequestHeaderFieldsTooLarge => "Request Header Fields Too Large",
335        451, "451", UnavailableForLegalReasons => "Unavailable For Legal Reasons",
336        500, "500", InternalServerError => "Internal Server Error",
337        501, "501", NotImplemented => "Not Implemented",
338        502, "502", BadGateway => "Bad Gateway",
339        503, "503", ServiceUnavailable => "Service Unavailable",
340        504, "504", GatewayTimeout => "Gateway Timeout",
341        505, "505", HttpVersionNotSupported => "HTTP Version Not Supported",
342        506, "506", VariantAlsoNegotiates => "Variant Also Negotiates",
343        507, "507", InsufficientStorage => "Insufficient Storage",
344        508, "508", LoopDetected => "Loop Detected",
345        510, "510", NotExtended => "Not Extended",
346        511, "511", NetworkAuthenticationRequired => "Network Authentication Required"
347    }
348}
349
350impl fmt::Display for Status {
351    #[inline(always)]
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        write!(f, "{} {}", self.code, self.reason_lossy())
354    }
355}
356
357impl std::hash::Hash for Status {
358    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
359        self.code.hash(state)
360    }
361}
362
363impl PartialEq for Status {
364    fn eq(&self, other: &Self) -> bool {
365        self.code.eq(&other.code)
366    }
367}
368
369impl Eq for Status { }
370
371impl PartialOrd for Status {
372    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
373        self.code.partial_cmp(&other.code)
374    }
375}
376
377impl Ord for Status {
378    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
379        self.code.cmp(&other.code)
380    }
381}
382
383#[cfg(feature = "serde")]
384mod serde {
385    use std::fmt;
386    use super::*;
387
388    use serde_::ser::{Serialize, Serializer};
389    use serde_::de::{Deserialize, Deserializer, Error, Visitor, Unexpected};
390
391    impl<'a> Serialize for Status {
392        fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
393            serializer.serialize_u16(self.code)
394        }
395    }
396
397    struct DeVisitor;
398
399    impl<'de> Visitor<'de> for DeVisitor {
400        type Value = Status;
401
402        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
403            write!(formatter, "HTTP status code integer in range [100, 600)")
404        }
405
406        fn visit_i64<E: Error>(self, v: i64) -> Result<Self::Value, E> {
407            if v < 100 || v >= 600 {
408                return Err(E::invalid_value(Unexpected::Signed(v), &self));
409            }
410
411            Ok(Status::new(v as u16))
412        }
413
414        fn visit_u64<E: Error>(self, v: u64) -> Result<Self::Value, E> {
415            if v < 100 || v >= 600 {
416                return Err(E::invalid_value(Unexpected::Unsigned(v), &self));
417            }
418
419            Ok(Status::new(v as u16))
420        }
421    }
422
423    impl<'de> Deserialize<'de> for Status {
424        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
425            deserializer.deserialize_u16(DeVisitor)
426        }
427    }
428}