1use std::fmt;
4use std::str::FromStr;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum HttpVersion {
9 Http10,
11 Http11,
13 H2,
15 H3,
17}
18
19impl HttpVersion {
20 pub fn as_str(&self) -> &'static str {
31 match self {
32 HttpVersion::Http10 => "HTTP/1.0",
33 HttpVersion::Http11 => "HTTP/1.1",
34 HttpVersion::H2 => "HTTP/2",
35 HttpVersion::H3 => "HTTP/3",
36 }
37 }
38
39 pub fn is_http1(&self) -> bool {
41 matches!(self, HttpVersion::Http10 | HttpVersion::Http11)
42 }
43
44 pub fn is_http2(&self) -> bool {
46 matches!(self, HttpVersion::H2)
47 }
48
49 pub fn is_http3(&self) -> bool {
51 matches!(self, HttpVersion::H3)
52 }
53}
54
55impl fmt::Display for HttpVersion {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 f.write_str(self.as_str())
58 }
59}
60
61impl FromStr for HttpVersion {
62 type Err = crate::OxiHttpError;
63
64 fn from_str(s: &str) -> Result<Self, Self::Err> {
69 match s.to_ascii_uppercase().as_str() {
70 "HTTP/1.0" => Ok(HttpVersion::Http10),
71 "HTTP/1.1" => Ok(HttpVersion::Http11),
72 "HTTP/2" | "H2" => Ok(HttpVersion::H2),
73 "HTTP/3" | "H3" => Ok(HttpVersion::H3),
74 _ => Err(crate::OxiHttpError::InvalidHeader(format!(
75 "unknown HTTP version: {s}"
76 ))),
77 }
78 }
79}
80
81impl From<http::Version> for HttpVersion {
82 fn from(v: http::Version) -> Self {
83 match v {
84 http::Version::HTTP_10 => HttpVersion::Http10,
85 http::Version::HTTP_11 => HttpVersion::Http11,
86 http::Version::HTTP_2 => HttpVersion::H2,
87 http::Version::HTTP_3 => HttpVersion::H3,
88 _ => HttpVersion::Http11,
90 }
91 }
92}
93
94impl From<HttpVersion> for http::Version {
95 fn from(v: HttpVersion) -> http::Version {
96 match v {
97 HttpVersion::Http10 => http::Version::HTTP_10,
98 HttpVersion::Http11 => http::Version::HTTP_11,
99 HttpVersion::H2 => http::Version::HTTP_2,
100 HttpVersion::H3 => http::Version::HTTP_3,
101 }
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_as_str() {
111 assert_eq!(HttpVersion::Http10.as_str(), "HTTP/1.0");
112 assert_eq!(HttpVersion::Http11.as_str(), "HTTP/1.1");
113 assert_eq!(HttpVersion::H2.as_str(), "HTTP/2");
114 assert_eq!(HttpVersion::H3.as_str(), "HTTP/3");
115 }
116
117 #[test]
118 fn test_display() {
119 assert_eq!(HttpVersion::Http11.to_string(), "HTTP/1.1");
120 assert_eq!(HttpVersion::H2.to_string(), "HTTP/2");
121 }
122
123 #[test]
124 fn test_from_str_canonical() {
125 assert_eq!(
126 "HTTP/1.0".parse::<HttpVersion>().unwrap(),
127 HttpVersion::Http10
128 );
129 assert_eq!(
130 "HTTP/1.1".parse::<HttpVersion>().unwrap(),
131 HttpVersion::Http11
132 );
133 assert_eq!("HTTP/2".parse::<HttpVersion>().unwrap(), HttpVersion::H2);
134 assert_eq!("HTTP/3".parse::<HttpVersion>().unwrap(), HttpVersion::H3);
135 }
136
137 #[test]
138 fn test_from_str_case_insensitive() {
139 assert_eq!(
140 "http/1.0".parse::<HttpVersion>().unwrap(),
141 HttpVersion::Http10
142 );
143 assert_eq!(
144 "http/1.1".parse::<HttpVersion>().unwrap(),
145 HttpVersion::Http11
146 );
147 assert_eq!("http/2".parse::<HttpVersion>().unwrap(), HttpVersion::H2);
148 assert_eq!("http/3".parse::<HttpVersion>().unwrap(), HttpVersion::H3);
149 assert_eq!("h2".parse::<HttpVersion>().unwrap(), HttpVersion::H2);
150 assert_eq!("h3".parse::<HttpVersion>().unwrap(), HttpVersion::H3);
151 }
152
153 #[test]
154 fn test_from_str_invalid() {
155 assert!("HTTP/4".parse::<HttpVersion>().is_err());
156 assert!("".parse::<HttpVersion>().is_err());
157 assert!("1.1".parse::<HttpVersion>().is_err());
158 }
159
160 #[test]
161 fn test_from_http_version() {
162 assert_eq!(
163 HttpVersion::from(http::Version::HTTP_10),
164 HttpVersion::Http10
165 );
166 assert_eq!(
167 HttpVersion::from(http::Version::HTTP_11),
168 HttpVersion::Http11
169 );
170 assert_eq!(HttpVersion::from(http::Version::HTTP_2), HttpVersion::H2);
171 assert_eq!(HttpVersion::from(http::Version::HTTP_3), HttpVersion::H3);
172 }
173
174 #[test]
175 fn test_into_http_version() {
176 assert_eq!(
177 http::Version::from(HttpVersion::Http10),
178 http::Version::HTTP_10
179 );
180 assert_eq!(
181 http::Version::from(HttpVersion::Http11),
182 http::Version::HTTP_11
183 );
184 assert_eq!(http::Version::from(HttpVersion::H2), http::Version::HTTP_2);
185 assert_eq!(http::Version::from(HttpVersion::H3), http::Version::HTTP_3);
186 }
187
188 #[test]
189 fn test_round_trip() {
190 for v in [
191 HttpVersion::Http10,
192 HttpVersion::Http11,
193 HttpVersion::H2,
194 HttpVersion::H3,
195 ] {
196 let http_v: http::Version = v.into();
197 let back: HttpVersion = http_v.into();
198 assert_eq!(v, back);
199 }
200 }
201
202 #[test]
203 fn test_predicates() {
204 assert!(HttpVersion::Http10.is_http1());
205 assert!(HttpVersion::Http11.is_http1());
206 assert!(!HttpVersion::H2.is_http1());
207 assert!(HttpVersion::H2.is_http2());
208 assert!(!HttpVersion::H2.is_http3());
209 assert!(HttpVersion::H3.is_http3());
210 }
211}