1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3#[repr(u16)]
4pub enum StatusCode {
5 Continue = 100,
8 SwitchingProtocols = 101,
10
11 Ok = 200,
14 Created = 201,
16 Accepted = 202,
18 NonAuthoritativeInformation = 203,
20 NoContent = 204,
22 ResetContent = 205,
24 PartialContent = 206,
26
27 MultipleChoices = 300,
30 MovedPermanently = 301,
32 Found = 302,
34 SeeOther = 303,
36 NotModified = 304,
38 UseProxy = 305,
40 TemporaryRedirect = 307,
43
44 BadRequest = 400,
47 Unauthorized = 401,
49 PaymentRequired = 402,
51 Forbidden = 403,
53 NotFound = 404,
55 MethodNotAllowed = 405,
57 NotAcceptable = 406,
59 ProxyAuthenticationRequired = 407,
61 RequestTimeout = 408,
63 Conflict = 409,
65 Gone = 410,
67 LengthRequired = 411,
69 PreconditionFailed = 412,
71 RequestEntityTooLarge = 413,
73 RequestUriTooLong = 414,
75 UnsupportedMediaType = 415,
77 RequestedRangeNotSatisfiable = 416,
79 ExpectationFailed = 417,
81
82 InternalServerError = 500,
85 NotImplemented = 501,
87 BadGateway = 502,
89 ServiceUnavailable = 503,
91 GatewayTimeout = 504,
93 HttpVersionNotSupported = 505,
95 Other(u16),
97}
98
99#[allow(dead_code)]
100impl StatusCode {
101 #[must_use]
103 pub fn as_u16(self) -> u16 {
104 match self {
105 StatusCode::Continue => 100,
106 StatusCode::SwitchingProtocols => 101,
107 StatusCode::Ok => 200,
108 StatusCode::Created => 201,
109 StatusCode::Accepted => 202,
110 StatusCode::NonAuthoritativeInformation => 203,
111 StatusCode::NoContent => 204,
112 StatusCode::ResetContent => 205,
113 StatusCode::PartialContent => 206,
114 StatusCode::MultipleChoices => 300,
115 StatusCode::MovedPermanently => 301,
116 StatusCode::Found => 302,
117 StatusCode::SeeOther => 303,
118 StatusCode::NotModified => 304,
119 StatusCode::UseProxy => 305,
120 StatusCode::TemporaryRedirect => 307,
121 StatusCode::BadRequest => 400,
122 StatusCode::Unauthorized => 401,
123 StatusCode::PaymentRequired => 402,
124 StatusCode::Forbidden => 403,
125 StatusCode::NotFound => 404,
126 StatusCode::MethodNotAllowed => 405,
127 StatusCode::NotAcceptable => 406,
128 StatusCode::ProxyAuthenticationRequired => 407,
129 StatusCode::RequestTimeout => 408,
130 StatusCode::Conflict => 409,
131 StatusCode::Gone => 410,
132 StatusCode::LengthRequired => 411,
133 StatusCode::PreconditionFailed => 412,
134 StatusCode::RequestEntityTooLarge => 413,
135 StatusCode::RequestUriTooLong => 414,
136 StatusCode::UnsupportedMediaType => 415,
137 StatusCode::RequestedRangeNotSatisfiable => 416,
138 StatusCode::ExpectationFailed => 417,
139 StatusCode::InternalServerError => 500,
140 StatusCode::NotImplemented => 501,
141 StatusCode::BadGateway => 502,
142 StatusCode::ServiceUnavailable => 503,
143 StatusCode::GatewayTimeout => 504,
144 StatusCode::HttpVersionNotSupported => 505,
145 StatusCode::Other(code) => code,
146 }
147 }
148 #[must_use]
150 pub fn text(self) -> &'static str {
151 match self {
152 StatusCode::Continue => "Continue",
154 StatusCode::SwitchingProtocols => "Switching Protocols",
155 StatusCode::Ok => "OK",
157 StatusCode::Created => "Created",
158 StatusCode::Accepted => "Accepted",
159 StatusCode::NonAuthoritativeInformation => "Non-Authoritative Information",
160 StatusCode::NoContent => "No Content",
161 StatusCode::ResetContent => "Reset Content",
162 StatusCode::PartialContent => "Partial Content",
163 StatusCode::MultipleChoices => "Multiple Choices",
165 StatusCode::MovedPermanently => "Moved Permanently",
166 StatusCode::Found => "Found",
167 StatusCode::SeeOther => "See Other",
168 StatusCode::NotModified => "Not Modified",
169 StatusCode::UseProxy => "Use Proxy",
170 StatusCode::TemporaryRedirect => "Temporary Redirect",
171 StatusCode::BadRequest => "Bad Request",
173 StatusCode::Unauthorized => "Unauthorized",
174 StatusCode::PaymentRequired => "Payment Required",
175 StatusCode::Forbidden => "Forbidden",
176 StatusCode::NotFound => "Not Found",
177 StatusCode::MethodNotAllowed => "Method Not Allowed",
178 StatusCode::NotAcceptable => "Not Acceptable",
179 StatusCode::ProxyAuthenticationRequired => "Proxy Authentication Required",
180 StatusCode::RequestTimeout => "Request Timeout",
181 StatusCode::Conflict => "Conflict",
182 StatusCode::Gone => "Gone",
183 StatusCode::LengthRequired => "Length Required",
184 StatusCode::PreconditionFailed => "Precondition Failed",
185 StatusCode::RequestEntityTooLarge => "Request Entity Too Large",
186 StatusCode::RequestUriTooLong => "Request-URI Too Long",
187 StatusCode::UnsupportedMediaType => "Unsupported Media Type",
188 StatusCode::RequestedRangeNotSatisfiable => "Requested Range Not Satisfiable",
189 StatusCode::ExpectationFailed => "Expectation Failed",
190 StatusCode::InternalServerError => "Internal Server Error",
192 StatusCode::NotImplemented => "Not Implemented",
193 StatusCode::BadGateway => "Bad Gateway",
194 StatusCode::ServiceUnavailable => "Service Unavailable",
195 StatusCode::GatewayTimeout => "Gateway Timeout",
196 StatusCode::HttpVersionNotSupported => "HTTP Version Not Supported",
197 StatusCode::Other(_) => "Other",
198 }
199 }
200
201 #[must_use]
203 pub fn is_success(self) -> bool {
204 let code = self.as_u16();
205 (200..300).contains(&code)
206 }
207
208 #[must_use]
210 pub fn is_client_error(self) -> bool {
211 let code = self.as_u16();
212 (400..500).contains(&code)
213 }
214
215 #[must_use]
217 pub fn is_server_error(self) -> bool {
218 let code = self.as_u16();
219 (500..600).contains(&code)
220 }
221}
222
223impl From<u16> for StatusCode {
224 fn from(code: u16) -> Self {
225 match code {
226 100 => StatusCode::Continue,
227 101 => StatusCode::SwitchingProtocols,
228 200 => StatusCode::Ok,
229 201 => StatusCode::Created,
230 202 => StatusCode::Accepted,
231 203 => StatusCode::NonAuthoritativeInformation,
232 204 => StatusCode::NoContent,
233 205 => StatusCode::ResetContent,
234 206 => StatusCode::PartialContent,
235 300 => StatusCode::MultipleChoices,
236 301 => StatusCode::MovedPermanently,
237 302 => StatusCode::Found,
238 303 => StatusCode::SeeOther,
239 304 => StatusCode::NotModified,
240 305 => StatusCode::UseProxy,
241 307 => StatusCode::TemporaryRedirect,
242 400 => StatusCode::BadRequest,
243 401 => StatusCode::Unauthorized,
244 402 => StatusCode::PaymentRequired,
245 403 => StatusCode::Forbidden,
246 404 => StatusCode::NotFound,
247 405 => StatusCode::MethodNotAllowed,
248 406 => StatusCode::NotAcceptable,
249 407 => StatusCode::ProxyAuthenticationRequired,
250 408 => StatusCode::RequestTimeout,
251 409 => StatusCode::Conflict,
252 410 => StatusCode::Gone,
253 411 => StatusCode::LengthRequired,
254 412 => StatusCode::PreconditionFailed,
255 413 => StatusCode::RequestEntityTooLarge,
256 414 => StatusCode::RequestUriTooLong,
257 415 => StatusCode::UnsupportedMediaType,
258 416 => StatusCode::RequestedRangeNotSatisfiable,
259 417 => StatusCode::ExpectationFailed,
260 500 => StatusCode::InternalServerError,
261 501 => StatusCode::NotImplemented,
262 502 => StatusCode::BadGateway,
263 503 => StatusCode::ServiceUnavailable,
264 504 => StatusCode::GatewayTimeout,
265 505 => StatusCode::HttpVersionNotSupported,
266 other => StatusCode::Other(other),
267 }
268 }
269}
270
271impl TryFrom<&str> for StatusCode {
272 type Error = crate::Error;
273
274 fn try_from(value: &str) -> Result<Self, Self::Error> {
288 if let Ok(code) = value.parse::<u16>() {
289 Ok(StatusCode::from(code))
290 } else {
291 Err(crate::Error::InvalidStatusCode)
292 }
293 }
294}
295
296#[cfg(test)]
297mod tests {
298 use super::*;
299
300 #[test]
301 fn test_from_u16_known_codes() {
302 let code: StatusCode = 200_u16.into();
304 assert_eq!(code, StatusCode::Ok);
305
306 let code: StatusCode = 404_u16.into();
307 assert_eq!(code, StatusCode::NotFound);
308
309 let code: StatusCode = 500_u16.into();
310 assert_eq!(code, StatusCode::InternalServerError);
311
312 let code: StatusCode = 100_u16.into();
313 assert_eq!(code, StatusCode::Continue);
314
315 let code: StatusCode = 307_u16.into();
316 assert_eq!(code, StatusCode::TemporaryRedirect);
317 }
318
319 #[test]
320 fn test_from_u16_unknown_code() {
321 let code: StatusCode = 999_u16.into();
323 assert_eq!(code, StatusCode::Other(999));
324 let code: StatusCode = 150_u16.into();
325 assert_eq!(code, StatusCode::Other(150));
326 }
327
328 #[test]
329 fn test_text() {
330 assert_eq!(StatusCode::Ok.text(), "OK");
331 assert_eq!(StatusCode::NotFound.text(), "Not Found");
332 assert_eq!(
333 StatusCode::InternalServerError.text(),
334 "Internal Server Error"
335 );
336 assert_eq!(StatusCode::BadRequest.text(), "Bad Request");
337 assert_eq!(StatusCode::TemporaryRedirect.text(), "Temporary Redirect");
338 }
339
340 #[test]
341 fn test_enum_values_match_code() {
342 assert_eq!(StatusCode::Ok.as_u16(), 200);
344 assert_eq!(StatusCode::NotFound.as_u16(), 404);
345 assert_eq!(StatusCode::InternalServerError.as_u16(), 500);
346 assert_eq!(StatusCode::Continue.as_u16(), 100);
347 assert_eq!(StatusCode::TemporaryRedirect.as_u16(), 307);
348 }
349
350 #[test]
351 fn test_is_success() {
352 assert!(StatusCode::Ok.is_success());
353 assert!(StatusCode::Created.is_success());
354 assert!(StatusCode::Accepted.is_success());
355 assert!(StatusCode::NoContent.is_success());
356
357 assert!(!StatusCode::Continue.is_success());
358 assert!(!StatusCode::NotFound.is_success());
359 assert!(!StatusCode::InternalServerError.is_success());
360 assert!(!StatusCode::MovedPermanently.is_success());
361 }
362
363 #[test]
364 fn test_is_client_error() {
365 assert!(StatusCode::BadRequest.is_client_error());
366 assert!(StatusCode::Unauthorized.is_client_error());
367 assert!(StatusCode::Forbidden.is_client_error());
368 assert!(StatusCode::NotFound.is_client_error());
369 assert!(StatusCode::MethodNotAllowed.is_client_error());
370
371 assert!(!StatusCode::Ok.is_client_error());
372 assert!(!StatusCode::Continue.is_client_error());
373 assert!(!StatusCode::InternalServerError.is_client_error());
374 assert!(!StatusCode::MovedPermanently.is_client_error());
375 }
376
377 #[test]
378 fn test_is_server_error() {
379 assert!(StatusCode::InternalServerError.is_server_error());
380 assert!(StatusCode::NotImplemented.is_server_error());
381 assert!(StatusCode::BadGateway.is_server_error());
382 assert!(StatusCode::ServiceUnavailable.is_server_error());
383 assert!(StatusCode::GatewayTimeout.is_server_error());
384
385 assert!(!StatusCode::Ok.is_server_error());
386 assert!(!StatusCode::Continue.is_server_error());
387 assert!(!StatusCode::NotFound.is_server_error());
388 assert!(!StatusCode::MovedPermanently.is_server_error());
389 }
390
391 #[test]
392 fn test_try_from_str_valid() {
393 let code: StatusCode = "200".try_into().unwrap();
395 assert_eq!(code, StatusCode::Ok);
396
397 let code: StatusCode = "404".try_into().unwrap();
398 assert_eq!(code, StatusCode::NotFound);
399
400 let code: StatusCode = "500".try_into().unwrap();
401 assert_eq!(code, StatusCode::InternalServerError);
402
403 let code: StatusCode = "100".try_into().unwrap();
404 assert_eq!(code, StatusCode::Continue);
405 }
406
407 #[test]
408 fn test_try_from_str_invalid() {
409 let result: Result<StatusCode, _> = "abc".try_into();
411 assert!(result.is_err());
412
413 let result: Result<StatusCode, _> = "".try_into();
414 assert!(result.is_err());
415
416 let result: Result<StatusCode, _> = "12345".try_into();
417 assert_eq!(result.unwrap(), StatusCode::Other(12345));
418
419 let result: Result<StatusCode, _> = "999".try_into();
421 assert_eq!(result.unwrap(), StatusCode::Other(999));
422 let result: Result<StatusCode, _> = "150".try_into();
423 assert_eq!(result.unwrap(), StatusCode::Other(150));
424 }
425
426 #[test]
427 fn test_try_from_u16_valid() {
428 let code: StatusCode = 200_u16.into();
430 assert_eq!(code, StatusCode::Ok);
431
432 let code: StatusCode = 404_u16.into();
433 assert_eq!(code, StatusCode::NotFound);
434
435 let code: StatusCode = 500_u16.into();
436 assert_eq!(code, StatusCode::InternalServerError);
437
438 let code: StatusCode = 100_u16.into();
439 assert_eq!(code, StatusCode::Continue);
440
441 let code: StatusCode = 307_u16.into();
442 assert_eq!(code, StatusCode::TemporaryRedirect);
443 }
444}