1use std::{error::Error, fmt::Display, str::FromStr};
4
5#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
7#[non_exhaustive]
8pub enum Version {
9 Http0_9,
11
12 Http1_0,
14
15 Http1_1,
17
18 Http2_0,
20
21 Http3_0,
23}
24
25#[cfg(feature = "serde")]
26impl serde::Serialize for Version {
27 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
28 where
29 S: serde::Serializer,
30 {
31 serializer.collect_str(self)
32 }
33}
34
35#[cfg(feature = "serde")]
36impl<'de> serde::Deserialize<'de> for Version {
37 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
38 where
39 D: serde::Deserializer<'de>,
40 {
41 String::deserialize(deserializer)?
42 .parse()
43 .map_err(serde::de::Error::custom)
44 }
45}
46
47impl PartialEq<&Version> for Version {
48 #[allow(clippy::unconditional_recursion)] fn eq(&self, other: &&Version) -> bool {
50 self == *other
51 }
52}
53
54impl PartialEq<Version> for &Version {
55 #[allow(clippy::unconditional_recursion)] fn eq(&self, other: &Version) -> bool {
57 *self == other
58 }
59}
60
61impl Version {
62 pub const fn as_str(&self) -> &'static str {
64 match self {
65 Version::Http0_9 => "HTTP/0.9",
66 Version::Http1_0 => "HTTP/1.0",
67 Version::Http1_1 => "HTTP/1.1",
68 Version::Http2_0 => "HTTP/2",
69 Version::Http3_0 => "HTTP/3",
70 }
71 }
72}
73
74#[derive(Debug, Clone)]
75pub struct UnrecognizedVersion(String);
76impl Display for UnrecognizedVersion {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.write_fmt(format_args!("unrecognized http version: {}", self.0))
79 }
80}
81impl Error for UnrecognizedVersion {}
82
83impl FromStr for Version {
84 type Err = UnrecognizedVersion;
85
86 fn from_str(s: &str) -> Result<Self, Self::Err> {
87 match s {
88 "HTTP/0.9" | "http/0.9" | "0.9" => Ok(Self::Http0_9),
89 "HTTP/1.0" | "http/1.0" | "1.0" => Ok(Self::Http1_0),
90 "HTTP/1.1" | "http/1.1" | "1.1" => Ok(Self::Http1_1),
91 "HTTP/2" | "http/2" | "2" => Ok(Self::Http2_0),
92 "HTTP/3" | "http/3" | "3" => Ok(Self::Http3_0),
93 _ => Err(UnrecognizedVersion(s.to_string())),
94 }
95 }
96}
97
98impl AsRef<str> for Version {
99 fn as_ref(&self) -> &str {
100 self.as_str()
101 }
102}
103
104impl AsRef<[u8]> for Version {
105 fn as_ref(&self) -> &[u8] {
106 self.as_str().as_bytes()
107 }
108}
109
110impl Display for Version {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 f.write_str(self.as_ref())
113 }
114}
115
116#[cfg(test)]
117mod test {
118 use super::*;
119 #[test]
120 fn from_str() {
121 let versions = [
122 Version::Http0_9,
123 Version::Http1_0,
124 Version::Http1_1,
125 Version::Http2_0,
126 Version::Http3_0,
127 ];
128
129 for version in versions {
130 assert_eq!(version.as_str().parse::<Version>().unwrap(), version);
131 assert_eq!(version.to_string().parse::<Version>().unwrap(), version);
132 }
133
134 assert_eq!(
135 "not a version".parse::<Version>().unwrap_err().to_string(),
136 "unrecognized http version: not a version"
137 );
138 }
139
140 #[test]
141 fn eq() {
142 assert_eq!(Version::Http1_1, Version::Http1_1);
143 assert_eq!(Version::Http1_1, &Version::Http1_1);
144 assert_eq!(&Version::Http1_1, Version::Http1_1);
145 }
146
147 #[test]
148 fn to_string() {
149 let output = format!(
150 "{} {} {} {} {}",
151 Version::Http0_9,
152 Version::Http1_0,
153 Version::Http1_1,
154 Version::Http2_0,
155 Version::Http3_0
156 );
157 assert_eq!("HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/3", output);
158 }
159
160 #[test]
161 fn ord() {
162 use Version::{Http0_9, Http1_0, Http1_1, Http2_0, Http3_0};
163 assert!(Http3_0 > Http2_0);
164 assert!(Http2_0 > Http1_1);
165 assert!(Http1_1 > Http1_0);
166 assert!(Http1_0 > Http0_9);
167 }
168}