Skip to main content

api_bones/
status.rs

1//! Full HTTP status code type covering 1xx/2xx/3xx/4xx/5xx.
2//!
3//! [`StatusCode`] extends the domain of [`ErrorCode`](crate::error::ErrorCode),
4//! which only covers 4xx/5xx error responses, to include informational (1xx),
5//! successful (2xx), and redirect (3xx) status codes as well.
6//!
7//! # Example
8//!
9//! ```rust
10//! use api_bones::status::StatusCode;
11//!
12//! let sc = StatusCode::Ok;
13//! assert_eq!(sc.as_u16(), 200);
14//! assert!(sc.is_success());
15//! assert!(!sc.is_error());
16//!
17//! let redirect = StatusCode::MovedPermanently;
18//! assert!(redirect.is_redirection());
19//!
20//! let sc2: StatusCode = 201u16.try_into().unwrap();
21//! assert_eq!(sc2, StatusCode::Created);
22//! ```
23
24use core::{fmt, str::FromStr};
25#[cfg(feature = "serde")]
26use serde::{Deserialize, Serialize};
27
28use crate::error::ErrorCode;
29
30// ---------------------------------------------------------------------------
31// StatusCode
32// ---------------------------------------------------------------------------
33
34/// All standard HTTP status codes (RFC 9110 + common extensions).
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
37#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
38#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
39#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
40#[cfg_attr(feature = "proptest", derive(proptest_derive::Arbitrary))]
41#[non_exhaustive]
42pub enum StatusCode {
43    // 1xx Informational
44    /// 100 Continue
45    Continue,
46    /// 101 Switching Protocols
47    SwitchingProtocols,
48    /// 102 Processing (`WebDAV`)
49    Processing,
50    /// 103 Early Hints
51    EarlyHints,
52
53    // 2xx Success
54    /// 200 OK
55    Ok,
56    /// 201 Created
57    Created,
58    /// 202 Accepted
59    Accepted,
60    /// 203 Non-Authoritative Information
61    NonAuthoritativeInformation,
62    /// 204 No Content
63    NoContent,
64    /// 205 Reset Content
65    ResetContent,
66    /// 206 Partial Content
67    PartialContent,
68    /// 207 Multi-Status (`WebDAV`)
69    MultiStatus,
70    /// 208 Already Reported (`WebDAV`)
71    AlreadyReported,
72    /// 226 IM Used
73    ImUsed,
74
75    // 3xx Redirection
76    /// 300 Multiple Choices
77    MultipleChoices,
78    /// 301 Moved Permanently
79    MovedPermanently,
80    /// 302 Found
81    Found,
82    /// 303 See Other
83    SeeOther,
84    /// 304 Not Modified
85    NotModified,
86    /// 305 Use Proxy
87    UseProxy,
88    /// 307 Temporary Redirect
89    TemporaryRedirect,
90    /// 308 Permanent Redirect
91    PermanentRedirect,
92
93    // 4xx Client Error
94    /// 400 Bad Request
95    BadRequest,
96    /// 401 Unauthorized
97    Unauthorized,
98    /// 402 Payment Required
99    PaymentRequired,
100    /// 403 Forbidden
101    Forbidden,
102    /// 404 Not Found
103    NotFound,
104    /// 405 Method Not Allowed
105    MethodNotAllowed,
106    /// 406 Not Acceptable
107    NotAcceptable,
108    /// 407 Proxy Authentication Required
109    ProxyAuthenticationRequired,
110    /// 408 Request Timeout
111    RequestTimeout,
112    /// 409 Conflict
113    Conflict,
114    /// 410 Gone
115    Gone,
116    /// 411 Length Required
117    LengthRequired,
118    /// 412 Precondition Failed
119    PreconditionFailed,
120    /// 413 Content Too Large
121    ContentTooLarge,
122    /// 414 URI Too Long
123    UriTooLong,
124    /// 415 Unsupported Media Type
125    UnsupportedMediaType,
126    /// 416 Range Not Satisfiable
127    RangeNotSatisfiable,
128    /// 417 Expectation Failed
129    ExpectationFailed,
130    /// 418 I'm a Teapot (RFC 2324)
131    ImATeapot,
132    /// 421 Misdirected Request
133    MisdirectedRequest,
134    /// 422 Unprocessable Content
135    UnprocessableContent,
136    /// 423 Locked (`WebDAV`)
137    Locked,
138    /// 424 Failed Dependency (`WebDAV`)
139    FailedDependency,
140    /// 425 Too Early
141    TooEarly,
142    /// 426 Upgrade Required
143    UpgradeRequired,
144    /// 428 Precondition Required
145    PreconditionRequired,
146    /// 429 Too Many Requests
147    TooManyRequests,
148    /// 431 Request Header Fields Too Large
149    RequestHeaderFieldsTooLarge,
150    /// 451 Unavailable For Legal Reasons
151    UnavailableForLegalReasons,
152
153    // 5xx Server Error
154    /// 500 Internal Server Error
155    InternalServerError,
156    /// 501 Not Implemented
157    NotImplemented,
158    /// 502 Bad Gateway
159    BadGateway,
160    /// 503 Service Unavailable
161    ServiceUnavailable,
162    /// 504 Gateway Timeout
163    GatewayTimeout,
164    /// 505 HTTP Version Not Supported
165    HttpVersionNotSupported,
166    /// 506 Variant Also Negotiates
167    VariantAlsoNegotiates,
168    /// 507 Insufficient Storage (`WebDAV`)
169    InsufficientStorage,
170    /// 508 Loop Detected (`WebDAV`)
171    LoopDetected,
172    /// 510 Not Extended
173    NotExtended,
174    /// 511 Network Authentication Required
175    NetworkAuthenticationRequired,
176}
177
178impl StatusCode {
179    /// Return the numeric status code.
180    ///
181    /// ```
182    /// use api_bones::status::StatusCode;
183    ///
184    /// assert_eq!(StatusCode::Ok.as_u16(), 200);
185    /// assert_eq!(StatusCode::NotFound.as_u16(), 404);
186    /// ```
187    #[must_use]
188    pub const fn as_u16(&self) -> u16 {
189        match self {
190            Self::Continue => 100,
191            Self::SwitchingProtocols => 101,
192            Self::Processing => 102,
193            Self::EarlyHints => 103,
194            Self::Ok => 200,
195            Self::Created => 201,
196            Self::Accepted => 202,
197            Self::NonAuthoritativeInformation => 203,
198            Self::NoContent => 204,
199            Self::ResetContent => 205,
200            Self::PartialContent => 206,
201            Self::MultiStatus => 207,
202            Self::AlreadyReported => 208,
203            Self::ImUsed => 226,
204            Self::MultipleChoices => 300,
205            Self::MovedPermanently => 301,
206            Self::Found => 302,
207            Self::SeeOther => 303,
208            Self::NotModified => 304,
209            Self::UseProxy => 305,
210            Self::TemporaryRedirect => 307,
211            Self::PermanentRedirect => 308,
212            Self::BadRequest => 400,
213            Self::Unauthorized => 401,
214            Self::PaymentRequired => 402,
215            Self::Forbidden => 403,
216            Self::NotFound => 404,
217            Self::MethodNotAllowed => 405,
218            Self::NotAcceptable => 406,
219            Self::ProxyAuthenticationRequired => 407,
220            Self::RequestTimeout => 408,
221            Self::Conflict => 409,
222            Self::Gone => 410,
223            Self::LengthRequired => 411,
224            Self::PreconditionFailed => 412,
225            Self::ContentTooLarge => 413,
226            Self::UriTooLong => 414,
227            Self::UnsupportedMediaType => 415,
228            Self::RangeNotSatisfiable => 416,
229            Self::ExpectationFailed => 417,
230            Self::ImATeapot => 418,
231            Self::MisdirectedRequest => 421,
232            Self::UnprocessableContent => 422,
233            Self::Locked => 423,
234            Self::FailedDependency => 424,
235            Self::TooEarly => 425,
236            Self::UpgradeRequired => 426,
237            Self::PreconditionRequired => 428,
238            Self::TooManyRequests => 429,
239            Self::RequestHeaderFieldsTooLarge => 431,
240            Self::UnavailableForLegalReasons => 451,
241            Self::InternalServerError => 500,
242            Self::NotImplemented => 501,
243            Self::BadGateway => 502,
244            Self::ServiceUnavailable => 503,
245            Self::GatewayTimeout => 504,
246            Self::HttpVersionNotSupported => 505,
247            Self::VariantAlsoNegotiates => 506,
248            Self::InsufficientStorage => 507,
249            Self::LoopDetected => 508,
250            Self::NotExtended => 510,
251            Self::NetworkAuthenticationRequired => 511,
252        }
253    }
254
255    /// Return the canonical reason phrase.
256    ///
257    /// ```
258    /// use api_bones::status::StatusCode;
259    ///
260    /// assert_eq!(StatusCode::Ok.reason_phrase(), "OK");
261    /// assert_eq!(StatusCode::NotFound.reason_phrase(), "Not Found");
262    /// ```
263    #[must_use]
264    pub const fn reason_phrase(&self) -> &'static str {
265        match self {
266            Self::Continue => "Continue",
267            Self::SwitchingProtocols => "Switching Protocols",
268            Self::Processing => "Processing",
269            Self::EarlyHints => "Early Hints",
270            Self::Ok => "OK",
271            Self::Created => "Created",
272            Self::Accepted => "Accepted",
273            Self::NonAuthoritativeInformation => "Non-Authoritative Information",
274            Self::NoContent => "No Content",
275            Self::ResetContent => "Reset Content",
276            Self::PartialContent => "Partial Content",
277            Self::MultiStatus => "Multi-Status",
278            Self::AlreadyReported => "Already Reported",
279            Self::ImUsed => "IM Used",
280            Self::MultipleChoices => "Multiple Choices",
281            Self::MovedPermanently => "Moved Permanently",
282            Self::Found => "Found",
283            Self::SeeOther => "See Other",
284            Self::NotModified => "Not Modified",
285            Self::UseProxy => "Use Proxy",
286            Self::TemporaryRedirect => "Temporary Redirect",
287            Self::PermanentRedirect => "Permanent Redirect",
288            Self::BadRequest => "Bad Request",
289            Self::Unauthorized => "Unauthorized",
290            Self::PaymentRequired => "Payment Required",
291            Self::Forbidden => "Forbidden",
292            Self::NotFound => "Not Found",
293            Self::MethodNotAllowed => "Method Not Allowed",
294            Self::NotAcceptable => "Not Acceptable",
295            Self::ProxyAuthenticationRequired => "Proxy Authentication Required",
296            Self::RequestTimeout => "Request Timeout",
297            Self::Conflict => "Conflict",
298            Self::Gone => "Gone",
299            Self::LengthRequired => "Length Required",
300            Self::PreconditionFailed => "Precondition Failed",
301            Self::ContentTooLarge => "Content Too Large",
302            Self::UriTooLong => "URI Too Long",
303            Self::UnsupportedMediaType => "Unsupported Media Type",
304            Self::RangeNotSatisfiable => "Range Not Satisfiable",
305            Self::ExpectationFailed => "Expectation Failed",
306            Self::ImATeapot => "I'm a teapot",
307            Self::MisdirectedRequest => "Misdirected Request",
308            Self::UnprocessableContent => "Unprocessable Content",
309            Self::Locked => "Locked",
310            Self::FailedDependency => "Failed Dependency",
311            Self::TooEarly => "Too Early",
312            Self::UpgradeRequired => "Upgrade Required",
313            Self::PreconditionRequired => "Precondition Required",
314            Self::TooManyRequests => "Too Many Requests",
315            Self::RequestHeaderFieldsTooLarge => "Request Header Fields Too Large",
316            Self::UnavailableForLegalReasons => "Unavailable For Legal Reasons",
317            Self::InternalServerError => "Internal Server Error",
318            Self::NotImplemented => "Not Implemented",
319            Self::BadGateway => "Bad Gateway",
320            Self::ServiceUnavailable => "Service Unavailable",
321            Self::GatewayTimeout => "Gateway Timeout",
322            Self::HttpVersionNotSupported => "HTTP Version Not Supported",
323            Self::VariantAlsoNegotiates => "Variant Also Negotiates",
324            Self::InsufficientStorage => "Insufficient Storage",
325            Self::LoopDetected => "Loop Detected",
326            Self::NotExtended => "Not Extended",
327            Self::NetworkAuthenticationRequired => "Network Authentication Required",
328        }
329    }
330
331    // -----------------------------------------------------------------------
332    // Category predicates
333    // -----------------------------------------------------------------------
334
335    /// Returns `true` for 1xx Informational responses.
336    #[must_use]
337    pub const fn is_informational(&self) -> bool {
338        self.as_u16() / 100 == 1
339    }
340
341    /// Returns `true` for 2xx Successful responses.
342    ///
343    /// ```
344    /// use api_bones::status::StatusCode;
345    ///
346    /// assert!(StatusCode::Ok.is_success());
347    /// assert!(StatusCode::Created.is_success());
348    /// assert!(!StatusCode::BadRequest.is_success());
349    /// ```
350    #[must_use]
351    pub const fn is_success(&self) -> bool {
352        self.as_u16() / 100 == 2
353    }
354
355    /// Returns `true` for 3xx Redirection responses.
356    ///
357    /// ```
358    /// use api_bones::status::StatusCode;
359    ///
360    /// assert!(StatusCode::MovedPermanently.is_redirection());
361    /// assert!(!StatusCode::Ok.is_redirection());
362    /// ```
363    #[must_use]
364    pub const fn is_redirection(&self) -> bool {
365        self.as_u16() / 100 == 3
366    }
367
368    /// Returns `true` for 4xx Client Error responses.
369    ///
370    /// ```
371    /// use api_bones::status::StatusCode;
372    ///
373    /// assert!(StatusCode::NotFound.is_client_error());
374    /// ```
375    #[must_use]
376    pub const fn is_client_error(&self) -> bool {
377        self.as_u16() / 100 == 4
378    }
379
380    /// Returns `true` for 5xx Server Error responses.
381    ///
382    /// ```
383    /// use api_bones::status::StatusCode;
384    ///
385    /// assert!(StatusCode::InternalServerError.is_server_error());
386    /// ```
387    #[must_use]
388    pub const fn is_server_error(&self) -> bool {
389        self.as_u16() / 100 == 5
390    }
391
392    /// Returns `true` for either 4xx or 5xx responses.
393    #[must_use]
394    pub const fn is_error(&self) -> bool {
395        self.is_client_error() || self.is_server_error()
396    }
397}
398
399// ---------------------------------------------------------------------------
400// Display
401// ---------------------------------------------------------------------------
402
403impl fmt::Display for StatusCode {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        write!(f, "{} {}", self.as_u16(), self.reason_phrase())
406    }
407}
408
409// ---------------------------------------------------------------------------
410// TryFrom<u16>
411// ---------------------------------------------------------------------------
412
413/// Error returned when converting an unknown numeric status code.
414#[derive(Debug, Clone, PartialEq, Eq)]
415pub struct UnknownStatusCode(pub u16);
416
417impl fmt::Display for UnknownStatusCode {
418    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
419        write!(f, "unknown HTTP status code: {}", self.0)
420    }
421}
422
423#[cfg(feature = "std")]
424impl std::error::Error for UnknownStatusCode {}
425
426impl TryFrom<u16> for StatusCode {
427    type Error = UnknownStatusCode;
428
429    fn try_from(code: u16) -> Result<Self, Self::Error> {
430        match code {
431            100 => Ok(Self::Continue),
432            101 => Ok(Self::SwitchingProtocols),
433            102 => Ok(Self::Processing),
434            103 => Ok(Self::EarlyHints),
435            200 => Ok(Self::Ok),
436            201 => Ok(Self::Created),
437            202 => Ok(Self::Accepted),
438            203 => Ok(Self::NonAuthoritativeInformation),
439            204 => Ok(Self::NoContent),
440            205 => Ok(Self::ResetContent),
441            206 => Ok(Self::PartialContent),
442            207 => Ok(Self::MultiStatus),
443            208 => Ok(Self::AlreadyReported),
444            226 => Ok(Self::ImUsed),
445            300 => Ok(Self::MultipleChoices),
446            301 => Ok(Self::MovedPermanently),
447            302 => Ok(Self::Found),
448            303 => Ok(Self::SeeOther),
449            304 => Ok(Self::NotModified),
450            305 => Ok(Self::UseProxy),
451            307 => Ok(Self::TemporaryRedirect),
452            308 => Ok(Self::PermanentRedirect),
453            400 => Ok(Self::BadRequest),
454            401 => Ok(Self::Unauthorized),
455            402 => Ok(Self::PaymentRequired),
456            403 => Ok(Self::Forbidden),
457            404 => Ok(Self::NotFound),
458            405 => Ok(Self::MethodNotAllowed),
459            406 => Ok(Self::NotAcceptable),
460            407 => Ok(Self::ProxyAuthenticationRequired),
461            408 => Ok(Self::RequestTimeout),
462            409 => Ok(Self::Conflict),
463            410 => Ok(Self::Gone),
464            411 => Ok(Self::LengthRequired),
465            412 => Ok(Self::PreconditionFailed),
466            413 => Ok(Self::ContentTooLarge),
467            414 => Ok(Self::UriTooLong),
468            415 => Ok(Self::UnsupportedMediaType),
469            416 => Ok(Self::RangeNotSatisfiable),
470            417 => Ok(Self::ExpectationFailed),
471            418 => Ok(Self::ImATeapot),
472            421 => Ok(Self::MisdirectedRequest),
473            422 => Ok(Self::UnprocessableContent),
474            423 => Ok(Self::Locked),
475            424 => Ok(Self::FailedDependency),
476            425 => Ok(Self::TooEarly),
477            426 => Ok(Self::UpgradeRequired),
478            428 => Ok(Self::PreconditionRequired),
479            429 => Ok(Self::TooManyRequests),
480            431 => Ok(Self::RequestHeaderFieldsTooLarge),
481            451 => Ok(Self::UnavailableForLegalReasons),
482            500 => Ok(Self::InternalServerError),
483            501 => Ok(Self::NotImplemented),
484            502 => Ok(Self::BadGateway),
485            503 => Ok(Self::ServiceUnavailable),
486            504 => Ok(Self::GatewayTimeout),
487            505 => Ok(Self::HttpVersionNotSupported),
488            506 => Ok(Self::VariantAlsoNegotiates),
489            507 => Ok(Self::InsufficientStorage),
490            508 => Ok(Self::LoopDetected),
491            510 => Ok(Self::NotExtended),
492            511 => Ok(Self::NetworkAuthenticationRequired),
493            other => Err(UnknownStatusCode(other)),
494        }
495    }
496}
497
498impl FromStr for StatusCode {
499    type Err = UnknownStatusCode;
500
501    fn from_str(s: &str) -> Result<Self, Self::Err> {
502        let n: u16 = s.trim().parse().map_err(|_| UnknownStatusCode(0))?;
503        Self::try_from(n)
504    }
505}
506
507// ---------------------------------------------------------------------------
508// Conversion from/to ErrorCode
509// ---------------------------------------------------------------------------
510
511impl From<ErrorCode> for StatusCode {
512    fn from(code: ErrorCode) -> Self {
513        match code {
514            ErrorCode::BadRequest | ErrorCode::ValidationFailed => Self::BadRequest,
515            ErrorCode::Unauthorized
516            | ErrorCode::InvalidCredentials
517            | ErrorCode::TokenExpired
518            | ErrorCode::TokenInvalid => Self::Unauthorized,
519            ErrorCode::Forbidden
520            | ErrorCode::InsufficientPermissions
521            | ErrorCode::OrgOutsideSubtree
522            | ErrorCode::AncestorRequired
523            | ErrorCode::CrossSubtreeAccess => Self::Forbidden,
524            ErrorCode::ResourceNotFound => Self::NotFound,
525            ErrorCode::MethodNotAllowed => Self::MethodNotAllowed,
526            ErrorCode::NotAcceptable => Self::NotAcceptable,
527            ErrorCode::RequestTimeout => Self::RequestTimeout,
528            ErrorCode::Conflict | ErrorCode::ResourceAlreadyExists => Self::Conflict,
529            ErrorCode::Gone => Self::Gone,
530            ErrorCode::PreconditionFailed => Self::PreconditionFailed,
531            ErrorCode::PayloadTooLarge => Self::ContentTooLarge,
532            ErrorCode::UnsupportedMediaType => Self::UnsupportedMediaType,
533            ErrorCode::UnprocessableEntity => Self::UnprocessableContent,
534            ErrorCode::PreconditionRequired => Self::PreconditionRequired,
535            ErrorCode::RateLimited => Self::TooManyRequests,
536            ErrorCode::RequestHeaderFieldsTooLarge => Self::RequestHeaderFieldsTooLarge,
537            ErrorCode::InternalServerError => Self::InternalServerError,
538            ErrorCode::NotImplemented => Self::NotImplemented,
539            ErrorCode::BadGateway => Self::BadGateway,
540            ErrorCode::ServiceUnavailable => Self::ServiceUnavailable,
541            ErrorCode::GatewayTimeout => Self::GatewayTimeout,
542        }
543    }
544}
545
546// ---------------------------------------------------------------------------
547// Interop with `http` crate
548// ---------------------------------------------------------------------------
549
550#[cfg(feature = "http")]
551mod http_interop {
552    use super::{StatusCode, UnknownStatusCode};
553
554    impl TryFrom<http::StatusCode> for StatusCode {
555        type Error = UnknownStatusCode;
556
557        fn try_from(sc: http::StatusCode) -> Result<Self, Self::Error> {
558            Self::try_from(sc.as_u16())
559        }
560    }
561
562    impl TryFrom<StatusCode> for http::StatusCode {
563        type Error = http::status::InvalidStatusCode;
564
565        fn try_from(sc: StatusCode) -> Result<Self, Self::Error> {
566            Self::from_u16(sc.as_u16())
567        }
568    }
569}
570
571// ---------------------------------------------------------------------------
572// Tests
573// ---------------------------------------------------------------------------
574
575#[cfg(test)]
576mod tests {
577    use super::*;
578
579    #[test]
580    fn numeric_round_trip() {
581        let codes: &[u16] = &[
582            100, 101, 102, 103, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302,
583            303, 304, 305, 307, 308, 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411,
584            412, 413, 414, 415, 416, 417, 418, 421, 422, 423, 424, 425, 426, 428, 429, 431, 451,
585            500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511,
586        ];
587        for &code in codes {
588            let sc = StatusCode::try_from(code).expect("should be known");
589            assert_eq!(sc.as_u16(), code, "round-trip failed for {code}");
590        }
591    }
592
593    #[test]
594    fn unknown_status_errors() {
595        assert!(StatusCode::try_from(0u16).is_err());
596        assert!(StatusCode::try_from(600u16).is_err());
597        let err = StatusCode::try_from(999u16).unwrap_err();
598        assert_eq!(err.0, 999);
599        assert!(err.to_string().contains("999"));
600    }
601
602    #[test]
603    fn from_str_round_trip() {
604        assert_eq!("200".parse::<StatusCode>().unwrap(), StatusCode::Ok);
605        assert_eq!(" 404 ".parse::<StatusCode>().unwrap(), StatusCode::NotFound);
606        assert!("abc".parse::<StatusCode>().is_err());
607        assert!("999".parse::<StatusCode>().is_err());
608    }
609
610    #[test]
611    fn reason_phrases_spot_check() {
612        assert_eq!(StatusCode::Continue.reason_phrase(), "Continue");
613        assert_eq!(StatusCode::ImUsed.reason_phrase(), "IM Used");
614        assert_eq!(StatusCode::ImATeapot.reason_phrase(), "I'm a teapot");
615        assert_eq!(
616            StatusCode::NetworkAuthenticationRequired.reason_phrase(),
617            "Network Authentication Required"
618        );
619    }
620
621    #[test]
622    fn category_predicates_all_ranges() {
623        assert!(StatusCode::Processing.is_informational());
624        assert!(StatusCode::EarlyHints.is_informational());
625        assert!(StatusCode::Accepted.is_success());
626        assert!(StatusCode::ImUsed.is_success());
627        assert!(StatusCode::PermanentRedirect.is_redirection());
628        assert!(StatusCode::Gone.is_client_error());
629        assert!(StatusCode::UnavailableForLegalReasons.is_client_error());
630        assert!(StatusCode::GatewayTimeout.is_server_error());
631        assert!(StatusCode::NetworkAuthenticationRequired.is_server_error());
632    }
633
634    #[test]
635    fn category_predicates() {
636        assert!(StatusCode::Continue.is_informational());
637        assert!(!StatusCode::Continue.is_success());
638
639        assert!(StatusCode::Ok.is_success());
640        assert!(StatusCode::Created.is_success());
641        assert!(!StatusCode::Ok.is_error());
642
643        assert!(StatusCode::MovedPermanently.is_redirection());
644        assert!(!StatusCode::Ok.is_redirection());
645
646        assert!(StatusCode::NotFound.is_client_error());
647        assert!(StatusCode::NotFound.is_error());
648        assert!(!StatusCode::NotFound.is_server_error());
649
650        assert!(StatusCode::InternalServerError.is_server_error());
651        assert!(StatusCode::InternalServerError.is_error());
652    }
653
654    #[test]
655    fn display_format() {
656        assert_eq!(StatusCode::Ok.to_string(), "200 OK");
657        assert_eq!(StatusCode::NotFound.to_string(), "404 Not Found");
658    }
659
660    #[test]
661    fn from_error_code() {
662        assert_eq!(
663            StatusCode::from(ErrorCode::ResourceNotFound),
664            StatusCode::NotFound
665        );
666        assert_eq!(
667            StatusCode::from(ErrorCode::RateLimited),
668            StatusCode::TooManyRequests
669        );
670        assert_eq!(
671            StatusCode::from(ErrorCode::InternalServerError),
672            StatusCode::InternalServerError
673        );
674    }
675
676    #[cfg(feature = "serde")]
677    #[test]
678    fn serde_round_trip() {
679        let sc = StatusCode::Created;
680        let json = serde_json::to_string(&sc).unwrap();
681        let back: StatusCode = serde_json::from_str(&json).unwrap();
682        assert_eq!(back, sc);
683    }
684
685    #[cfg(feature = "http")]
686    #[test]
687    fn http_crate_round_trip() {
688        let sc = StatusCode::Ok;
689        let hsc: http::StatusCode = sc.try_into().unwrap();
690        assert_eq!(hsc, http::StatusCode::OK);
691        let back: StatusCode = hsc.try_into().unwrap();
692        assert_eq!(back, StatusCode::Ok);
693    }
694}