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}