1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
// originally from https://github.com/http-rs/http-types/blob/main/src/version.rs

use std::{error::Error, fmt::Display, str::FromStr};

/// The version of the HTTP protocol in use.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum Version {
    /// HTTP/0.9
    Http0_9,

    /// HTTP/1.0
    Http1_0,

    /// HTTP/1.1
    Http1_1,

    /// HTTP/2.0
    Http2_0,

    /// HTTP/3.0
    Http3_0,
}

#[cfg(feature = "serde")]
impl serde::Serialize for Version {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: serde::Serializer,
    {
        serializer.collect_str(self)
    }
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Version {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        String::deserialize(deserializer)?
            .parse()
            .map_err(serde::de::Error::custom)
    }
}

impl PartialEq<&Version> for Version {
    #[allow(clippy::unconditional_recursion)] // false positive
    fn eq(&self, other: &&Version) -> bool {
        self == *other
    }
}

impl PartialEq<Version> for &Version {
    #[allow(clippy::unconditional_recursion)] // false positive
    fn eq(&self, other: &Version) -> bool {
        *self == other
    }
}

impl Version {
    /// returns the http version as a static str, such as "HTTP/1.1"
    pub const fn as_str(&self) -> &'static str {
        match self {
            Version::Http0_9 => "HTTP/0.9",
            Version::Http1_0 => "HTTP/1.0",
            Version::Http1_1 => "HTTP/1.1",
            Version::Http2_0 => "HTTP/2",
            Version::Http3_0 => "HTTP/3",
        }
    }
}

#[derive(Debug, Clone)]
pub struct UnrecognizedVersion(String);
impl Display for UnrecognizedVersion {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!("unrecognized http version: {}", self.0))
    }
}
impl Error for UnrecognizedVersion {}

impl FromStr for Version {
    type Err = UnrecognizedVersion;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "HTTP/0.9" | "http/0.9" | "0.9" => Ok(Self::Http0_9),
            "HTTP/1.0" | "http/1.0" | "1.0" => Ok(Self::Http1_0),
            "HTTP/1.1" | "http/1.1" | "1.1" => Ok(Self::Http1_1),
            "HTTP/2" | "http/2" | "2" => Ok(Self::Http2_0),
            "HTTP/3" | "http/3" | "3" => Ok(Self::Http3_0),
            _ => Err(UnrecognizedVersion(s.to_string())),
        }
    }
}

impl AsRef<str> for Version {
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

impl AsRef<[u8]> for Version {
    fn as_ref(&self) -> &[u8] {
        self.as_str().as_bytes()
    }
}

impl Display for Version {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_ref())
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn from_str() {
        let versions = [
            Version::Http0_9,
            Version::Http1_0,
            Version::Http1_1,
            Version::Http2_0,
            Version::Http3_0,
        ];

        for version in versions {
            assert_eq!(version.as_str().parse::<Version>().unwrap(), version);
            assert_eq!(version.to_string().parse::<Version>().unwrap(), version);
        }

        assert_eq!(
            "not a version".parse::<Version>().unwrap_err().to_string(),
            "unrecognized http version: not a version"
        );
    }

    #[test]
    fn eq() {
        assert_eq!(Version::Http1_1, Version::Http1_1);
        assert_eq!(Version::Http1_1, &Version::Http1_1);
        assert_eq!(&Version::Http1_1, Version::Http1_1);
    }

    #[test]
    fn to_string() {
        let output = format!(
            "{} {} {} {} {}",
            Version::Http0_9,
            Version::Http1_0,
            Version::Http1_1,
            Version::Http2_0,
            Version::Http3_0
        );
        assert_eq!("HTTP/0.9 HTTP/1.0 HTTP/1.1 HTTP/2 HTTP/3", output);
    }

    #[test]
    fn ord() {
        use Version::{Http0_9, Http1_0, Http1_1, Http2_0, Http3_0};
        assert!(Http3_0 > Http2_0);
        assert!(Http2_0 > Http1_1);
        assert!(Http1_1 > Http1_0);
        assert!(Http1_0 > Http0_9);
    }
}