Skip to main content

eggserve_core/primitives/
version.rs

1//! Canonical HTTP version type.
2//!
3//! [`HttpVersion`] represents the HTTP version used in a request or response.
4//! It covers the versions the runtime actually supports (HTTP/1.0 and HTTP/1.1).
5
6use std::fmt;
7
8/// Errors from HTTP version validation.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum HttpVersionError {
11    /// The version string is not recognized.
12    Unsupported,
13}
14
15impl fmt::Display for HttpVersionError {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            Self::Unsupported => write!(f, "unsupported HTTP version"),
19        }
20    }
21}
22
23impl std::error::Error for HttpVersionError {}
24
25/// An HTTP version.
26///
27/// Supports HTTP/1.0 and HTTP/1.1, which are the versions the runtime
28/// actually handles. Keep-alive semantics are a runtime concern, not a
29/// property of this value type.
30///
31/// # Serialization
32///
33/// `Display` produces the wire format: `HTTP/1.0` or `HTTP/1.1`.
34///
35/// # Comparison
36///
37/// Two versions are equal if and only if they represent the same HTTP
38/// version. `HTTP/1.0 != HTTP/1.1`.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
40pub enum HttpVersion {
41    /// HTTP/1.0.
42    Http10,
43    /// HTTP/1.1.
44    Http11,
45}
46
47impl HttpVersion {
48    /// Parse an HTTP version from the version string in a request line
49    /// (e.g., `HTTP/1.1`).
50    ///
51    /// # Errors
52    ///
53    /// Returns [`HttpVersionError::Unsupported`] if the version is not
54    /// `HTTP/1.0` or `HTTP/1.1`.
55    pub fn parse(version_str: &str) -> Result<Self, HttpVersionError> {
56        match version_str {
57            "HTTP/1.0" => Ok(Self::Http10),
58            "HTTP/1.1" => Ok(Self::Http11),
59            _ => Err(HttpVersionError::Unsupported),
60        }
61    }
62
63    /// Returns the wire-format string for this version.
64    pub fn as_str(&self) -> &'static str {
65        match self {
66            Self::Http10 => "HTTP/1.0",
67            Self::Http11 => "HTTP/1.1",
68        }
69    }
70
71    /// Returns the major version number.
72    pub fn major(&self) -> u8 {
73        match self {
74            Self::Http10 => 1,
75            Self::Http11 => 1,
76        }
77    }
78
79    /// Returns the minor version number.
80    pub fn minor(&self) -> u8 {
81        match self {
82            Self::Http10 => 0,
83            Self::Http11 => 1,
84        }
85    }
86}
87
88impl fmt::Display for HttpVersion {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        f.write_str(self.as_str())
91    }
92}
93
94impl AsRef<str> for HttpVersion {
95    fn as_ref(&self) -> &str {
96        self.as_str()
97    }
98}
99
100impl From<&hyper::http::Version> for HttpVersion {
101    fn from(v: &hyper::http::Version) -> Self {
102        match *v {
103            hyper::http::Version::HTTP_10 => Self::Http10,
104            hyper::http::Version::HTTP_11 => Self::Http11,
105            _ => Self::Http11, // best-effort fallback; unsupported versions are rejected at transport
106        }
107    }
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn parse_http_1_0() {
116        assert_eq!(HttpVersion::parse("HTTP/1.0").unwrap(), HttpVersion::Http10);
117    }
118
119    #[test]
120    fn parse_http_1_1() {
121        assert_eq!(HttpVersion::parse("HTTP/1.1").unwrap(), HttpVersion::Http11);
122    }
123
124    #[test]
125    fn parse_unsupported() {
126        assert_eq!(
127            HttpVersion::parse("HTTP/2.0").unwrap_err(),
128            HttpVersionError::Unsupported
129        );
130        assert_eq!(
131            HttpVersion::parse("HTTP/0.9").unwrap_err(),
132            HttpVersionError::Unsupported
133        );
134        assert_eq!(
135            HttpVersion::parse("").unwrap_err(),
136            HttpVersionError::Unsupported
137        );
138    }
139
140    #[test]
141    fn as_str() {
142        assert_eq!(HttpVersion::Http10.as_str(), "HTTP/1.0");
143        assert_eq!(HttpVersion::Http11.as_str(), "HTTP/1.1");
144    }
145
146    #[test]
147    fn major_minor() {
148        assert_eq!(HttpVersion::Http10.major(), 1);
149        assert_eq!(HttpVersion::Http10.minor(), 0);
150        assert_eq!(HttpVersion::Http11.major(), 1);
151        assert_eq!(HttpVersion::Http11.minor(), 1);
152    }
153
154    #[test]
155    fn display() {
156        assert_eq!(format!("{}", HttpVersion::Http10), "HTTP/1.0");
157        assert_eq!(format!("{}", HttpVersion::Http11), "HTTP/1.1");
158    }
159
160    #[test]
161    fn as_ref_str() {
162        let s: &str = HttpVersion::Http11.as_ref();
163        assert_eq!(s, "HTTP/1.1");
164    }
165
166    #[test]
167    fn error_display() {
168        assert!(!HttpVersionError::Unsupported.to_string().is_empty());
169    }
170
171    #[test]
172    fn error_is_error() {
173        let err: &dyn std::error::Error = &HttpVersionError::Unsupported;
174        assert!(!err.to_string().is_empty());
175    }
176}