tokio_gemini/
status.rs

1//! Response status code representation
2
3use crate::error::LibError;
4
5use num_enum::{IntoPrimitive, TryFromPrimitive};
6
7/// Handy representation of a Gemini response status code
8#[derive(Debug, Clone, Copy)]
9pub struct Status {
10    status_code: StatusCode,
11    reply_type: ReplyType,
12    second_digit: u8,
13}
14
15/// Type of a Gemini response defined by the first digit of a status code
16#[derive(Debug, Clone, Copy, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)]
17#[num_enum(error_type(name = LibError, constructor = LibError::status_out_of_range))]
18#[repr(u8)]
19pub enum ReplyType {
20    /// User input in a query argument is expected for this path
21    Input = 1,
22    /// Request has been processed successfully, content is served
23    Success,
24    /// Server redirects to another URL
25    Redirect,
26    /// Temporary failure, try again later
27    TempFail,
28    /// Permanent failure, request is incorrect
29    PermFail,
30    /// Server requires authorization via client certificates to access this resource
31    Auth,
32}
33
34/// 2-digit status code enum; all the codes defined in Gemini spec are listed
35#[derive(Debug, Clone, Copy, Eq, PartialEq, TryFromPrimitive, IntoPrimitive)]
36#[num_enum(error_type(name = LibError, constructor = LibError::status_out_of_range))]
37#[repr(u8)]
38pub enum StatusCode {
39    /// User input is expected
40    Input = 10,
41    /// User input with sensitive data such as password is expected
42    InputSensitive = 11,
43
44    /// Request has been processed successfully, content is served
45    Success = 20,
46
47    /// Temporary redirection to another URL
48    TempRedirect = 30,
49    /// Permanent redirection to another URL
50    PermRedirect = 31,
51
52    /// General status code for temporary failures
53    TempFail = 40,
54    /// Server is unavailable due to overload or maintenance
55    ServerUnavailable = 41,
56    /// CGI process returned an error or timed out
57    CgiError = 42,
58    /// Request to a remote host was not successful
59    ProxyError = 43,
60    /// Client must slow down requests (some kind of ratelimit)
61    SlowDown = 44,
62
63    /// General status code for permanent failures
64    PermFail = 50,
65    /// Requested resource was not found
66    NotFound = 51,
67    /// Requested resource is no longer available
68    Gone = 52,
69    /// Given URL is not meant to be processed by this server
70    /// (host does not match, scheme is not `gemini://`, etc.),
71    /// but the server does not accept proxy requests
72    ProxyRequestRefused = 53,
73    /// Server is unable to parse the request
74    BadRequest = 59,
75
76    /// Client certificate is required to access the content
77    ClientCerts = 60,
78    /// Provided certificate is not authorized for accessing this resource
79    CertNotAuthorized = 61,
80    /// Provided certificate is not valid: violates X.509 standard,
81    /// has invalid signature or expiry date
82    CertNotValid = 62,
83
84    /// Undefined status code between 10 and 69 inclusive
85    // 1..6 first digit range check is still
86    // covered by conversion into ReplyType
87    // (see Status::parse_status)
88    #[num_enum(catch_all)]
89    Unknown(u8),
90}
91
92const ASCII_ZERO: u8 = 48; // '0'
93
94impl Status {
95    /// Take first two bytes from the buffer and parse them as a status code,
96    /// returning [`Status`] on success or [`LibError::StatusOutOfRange`] on error
97    pub fn parse_status(buf: &[u8]) -> Result<Self, LibError> {
98        // simple decimal digit conversion
99        // '2' - '0' = 50 - 48 = 2 (from byte '2' to uint 2)
100        let first = buf[0] - ASCII_ZERO;
101        let second = buf[1] - ASCII_ZERO;
102
103        let code = first * 10 + second;
104
105        Ok(Status {
106            // get enum entry for the first digit
107            reply_type: ReplyType::try_from_primitive(first)?,
108            // get enum item for the 2-digit status code
109            status_code: StatusCode::try_from_primitive(code)?,
110            // provide separate field for the 2nd digit
111            second_digit: second,
112        })
113    }
114
115    /// Get the 2-digit status code as a [`StatusCode`] enum item
116    pub fn status_code(&self) -> StatusCode {
117        self.status_code
118    }
119
120    /// Get this status code as a number
121    pub fn num(&self) -> u8 {
122        self.status_code.into()
123    }
124
125    /// Get the response type (the first digit) as a [`ReplyType`] enum item
126    pub fn reply_type(&self) -> ReplyType {
127        self.reply_type
128    }
129
130    /// Get the second digit of this status code
131    pub fn second_digit(&self) -> u8 {
132        self.second_digit
133    }
134}