Skip to main content

nanofish_client/
status_code.rs

1/// HTTP/1.1 status codes as defined in RFC 2616 section 10
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3#[repr(u16)]
4pub enum StatusCode {
5    // 1xx Informational
6    /// 100 Continue: The server has received the request headers, and the client should proceed to send the request body.
7    Continue = 100,
8    /// 101 Switching Protocols: The requester has asked the server to switch protocols.
9    SwitchingProtocols = 101,
10
11    // 2xx Success
12    /// 200 OK: The request has succeeded.
13    Ok = 200,
14    /// 201 Created: The request has been fulfilled and resulted in a new resource being created.
15    Created = 201,
16    /// 202 Accepted: The request has been accepted for processing, but the processing has not been completed.
17    Accepted = 202,
18    /// 203 Non-Authoritative Information: The server successfully processed the request, but is returning information that may be from another source.
19    NonAuthoritativeInformation = 203,
20    /// 204 No Content: The server successfully processed the request, but is not returning any content.
21    NoContent = 204,
22    /// 205 Reset Content: The server successfully processed the request, but is not returning any content and requires that the requester reset the document view.
23    ResetContent = 205,
24    /// 206 Partial Content: The server is delivering only part of the resource due to a range header sent by the client.
25    PartialContent = 206,
26
27    // 3xx Redirection
28    /// 300 Multiple Choices: Indicates multiple options for the resource from which the client may choose.
29    MultipleChoices = 300,
30    /// 301 Moved Permanently: This and all future requests should be directed to the given URI.
31    MovedPermanently = 301,
32    /// 302 Found: The resource was found but at a different URI.
33    Found = 302,
34    /// 303 See Other: The response to the request can be found under another URI using a GET method.
35    SeeOther = 303,
36    /// 304 Not Modified: Indicates that the resource has not been modified since the version specified by the request headers.
37    NotModified = 304,
38    /// 305 Use Proxy: The requested resource is available only through a proxy.
39    UseProxy = 305,
40    // 306 is unused
41    /// 307 Temporary Redirect: The request should be repeated with another URI, but future requests should still use the original URI.
42    TemporaryRedirect = 307,
43
44    // 4xx Client Error
45    /// 400 Bad Request: The server could not understand the request due to invalid syntax.
46    BadRequest = 400,
47    /// 401 Unauthorized: The client must authenticate itself to get the requested response.
48    Unauthorized = 401,
49    /// 402 Payment Required: Reserved for future use.
50    PaymentRequired = 402,
51    /// 403 Forbidden: The client does not have access rights to the content.
52    Forbidden = 403,
53    /// 404 Not Found: The server can not find the requested resource.
54    NotFound = 404,
55    /// 405 Method Not Allowed: The request method is known by the server but is not supported by the target resource.
56    MethodNotAllowed = 405,
57    /// 406 Not Acceptable: The server cannot produce a response matching the list of acceptable values defined in the request's headers.
58    NotAcceptable = 406,
59    /// 407 Proxy Authentication Required: The client must first authenticate itself with the proxy.
60    ProxyAuthenticationRequired = 407,
61    /// 408 Request Timeout: The server timed out waiting for the request.
62    RequestTimeout = 408,
63    /// 409 Conflict: The request could not be completed due to a conflict with the current state of the resource.
64    Conflict = 409,
65    /// 410 Gone: The resource requested is no longer available and will not be available again.
66    Gone = 410,
67    /// 411 Length Required: The request did not specify the length of its content, which is required by the requested resource.
68    LengthRequired = 411,
69    /// 412 Precondition Failed: The server does not meet one of the preconditions that the requester put on the request.
70    PreconditionFailed = 412,
71    /// 413 Request Entity Too Large: The request is larger than the server is willing or able to process.
72    RequestEntityTooLarge = 413,
73    /// 414 Request-URI Too Long: The URI provided was too long for the server to process.
74    RequestUriTooLong = 414,
75    /// 415 Unsupported Media Type: The request entity has a media type which the server or resource does not support.
76    UnsupportedMediaType = 415,
77    /// 416 Requested Range Not Satisfiable: The client has asked for a portion of the file, but the server cannot supply that portion.
78    RequestedRangeNotSatisfiable = 416,
79    /// 417 Expectation Failed: The server cannot meet the requirements of the Expect request-header field.
80    ExpectationFailed = 417,
81
82    // 5xx Server Error
83    /// 500 Internal Server Error: The server has encountered a situation it doesn't know how to handle.
84    InternalServerError = 500,
85    /// 501 Not Implemented: The request method is not supported by the server and cannot be handled.
86    NotImplemented = 501,
87    /// 502 Bad Gateway: The server, while acting as a gateway or proxy, received an invalid response from the upstream server.
88    BadGateway = 502,
89    /// 503 Service Unavailable: The server is not ready to handle the request.
90    ServiceUnavailable = 503,
91    /// 504 Gateway Timeout: The server is acting as a gateway and cannot get a response in time.
92    GatewayTimeout = 504,
93    /// 505 HTTP Version Not Supported: The HTTP version used in the request is not supported by the server.
94    HttpVersionNotSupported = 505,
95    /// Any other (unknown or non-standard) status code
96    Other(u16),
97}
98
99#[allow(dead_code)]
100impl StatusCode {
101    /// Returns the numeric status code as u16.
102    #[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    /// Returns the status code text.
149    #[must_use]
150    pub fn text(self) -> &'static str {
151        match self {
152            // 1xx
153            StatusCode::Continue => "Continue",
154            StatusCode::SwitchingProtocols => "Switching Protocols",
155            // 2xx
156            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            // 3xx
164            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            // 4xx
172            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            // 5xx
191            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    /// Check if the status code indicates success (2xx status codes)
202    #[must_use]
203    pub fn is_success(self) -> bool {
204        let code = self.as_u16();
205        (200..300).contains(&code)
206    }
207
208    /// Check if the status code is a client error (4xx status codes)
209    #[must_use]
210    pub fn is_client_error(self) -> bool {
211        let code = self.as_u16();
212        (400..500).contains(&code)
213    }
214
215    /// Check if the status code is a server error (5xx status codes)
216    #[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    /// Parse a status code from a string slice.
275    ///
276    /// # Examples
277    ///
278    /// ```
279    /// use nanofish::StatusCode;
280    ///
281    /// let code: StatusCode = "200".try_into().unwrap();
282    /// assert_eq!(code, StatusCode::Ok);
283    ///
284    /// let result: Result<StatusCode, _> = "999".try_into();
285    /// assert_eq!(result.unwrap(), StatusCode::Other(999));
286    /// ```
287    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        // Test a few known codes using From
303        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        // Test unknown codes using From
322        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        // Ensure the discriminant matches the HTTP code
343        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        // Test valid status code strings
394        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        // Only non-numeric/invalid strings should error
410        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        // Numeric strings always succeed, even if unknown
420        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        // Test valid status codes
429        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}