1use core::{fmt, str::FromStr};
25#[cfg(feature = "serde")]
26use serde::{Deserialize, Serialize};
27
28use crate::error::ErrorCode;
29
30#[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 Continue,
46 SwitchingProtocols,
48 Processing,
50 EarlyHints,
52
53 Ok,
56 Created,
58 Accepted,
60 NonAuthoritativeInformation,
62 NoContent,
64 ResetContent,
66 PartialContent,
68 MultiStatus,
70 AlreadyReported,
72 ImUsed,
74
75 MultipleChoices,
78 MovedPermanently,
80 Found,
82 SeeOther,
84 NotModified,
86 UseProxy,
88 TemporaryRedirect,
90 PermanentRedirect,
92
93 BadRequest,
96 Unauthorized,
98 PaymentRequired,
100 Forbidden,
102 NotFound,
104 MethodNotAllowed,
106 NotAcceptable,
108 ProxyAuthenticationRequired,
110 RequestTimeout,
112 Conflict,
114 Gone,
116 LengthRequired,
118 PreconditionFailed,
120 ContentTooLarge,
122 UriTooLong,
124 UnsupportedMediaType,
126 RangeNotSatisfiable,
128 ExpectationFailed,
130 ImATeapot,
132 MisdirectedRequest,
134 UnprocessableContent,
136 Locked,
138 FailedDependency,
140 TooEarly,
142 UpgradeRequired,
144 PreconditionRequired,
146 TooManyRequests,
148 RequestHeaderFieldsTooLarge,
150 UnavailableForLegalReasons,
152
153 InternalServerError,
156 NotImplemented,
158 BadGateway,
160 ServiceUnavailable,
162 GatewayTimeout,
164 HttpVersionNotSupported,
166 VariantAlsoNegotiates,
168 InsufficientStorage,
170 LoopDetected,
172 NotExtended,
174 NetworkAuthenticationRequired,
176}
177
178impl StatusCode {
179 #[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 #[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 #[must_use]
337 pub const fn is_informational(&self) -> bool {
338 self.as_u16() / 100 == 1
339 }
340
341 #[must_use]
351 pub const fn is_success(&self) -> bool {
352 self.as_u16() / 100 == 2
353 }
354
355 #[must_use]
364 pub const fn is_redirection(&self) -> bool {
365 self.as_u16() / 100 == 3
366 }
367
368 #[must_use]
376 pub const fn is_client_error(&self) -> bool {
377 self.as_u16() / 100 == 4
378 }
379
380 #[must_use]
388 pub const fn is_server_error(&self) -> bool {
389 self.as_u16() / 100 == 5
390 }
391
392 #[must_use]
394 pub const fn is_error(&self) -> bool {
395 self.is_client_error() || self.is_server_error()
396 }
397}
398
399impl 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#[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
507impl 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#[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#[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}