Skip to main content

rama_net/forwarded/
proto.rs

1use core::str::FromStr;
2
3use crate::std::string::String;
4
5use crate::Protocol;
6
7use rama_utils::macros::{error::static_str_error, str::eq_ignore_ascii_case};
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
10/// Protocols that were forwarded.
11///
12/// These are a subset of [`Protocol`].
13///
14/// Please [file an issue or open a PR][repo] if you need support for more protocols.
15/// When doing so please provide sufficient motivation and ensure
16/// it has no unintended consequences.
17///
18/// [repo]: https://github.com/plabayo/rama
19pub struct ForwardedProtocol(ProtocolKind);
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22enum ProtocolKind {
23    /// The `http` protocol.
24    Http,
25    /// The `https` protocol.
26    Https,
27}
28
29const HTTP_STR: &str = "http";
30const HTTPS_STR: &str = "https";
31
32impl ForwardedProtocol {
33    /// `HTTP` protocol.
34    pub const HTTP: Self = Self(ProtocolKind::Http);
35
36    /// `HTTPS` protocol.
37    pub const HTTPS: Self = Self(ProtocolKind::Https);
38
39    /// Returns `true` if this protocol is http(s).
40    #[must_use]
41    pub fn is_http(&self) -> bool {
42        match &self.0 {
43            ProtocolKind::Http | ProtocolKind::Https => true,
44        }
45    }
46
47    /// Returns `true` if this protocol is "secure" by itself.
48    #[must_use]
49    pub fn is_secure(&self) -> bool {
50        match self.0 {
51            ProtocolKind::Https => true,
52            ProtocolKind::Http => false,
53        }
54    }
55
56    /// Returns the scheme str for this protocol.
57    #[must_use]
58    pub fn as_scheme(&self) -> &str {
59        match &self.0 {
60            ProtocolKind::Https => HTTPS_STR,
61            ProtocolKind::Http => HTTP_STR,
62        }
63    }
64
65    #[inline]
66    /// Consumes the protocol and returns a [`Protocol`].
67    #[must_use]
68    pub fn into_protocol(self) -> Protocol {
69        self.into()
70    }
71
72    /// Returns the [`ForwardedProtocol`] as a string.
73    #[must_use]
74    pub fn as_str(&self) -> &str {
75        match &self.0 {
76            ProtocolKind::Https => HTTPS_STR,
77            ProtocolKind::Http => HTTP_STR,
78        }
79    }
80}
81
82impl From<ForwardedProtocol> for Protocol {
83    fn from(p: ForwardedProtocol) -> Self {
84        match p.0 {
85            ProtocolKind::Https => Self::HTTPS,
86            ProtocolKind::Http => Self::HTTP,
87        }
88    }
89}
90
91static_str_error! {
92    #[doc = "unknown protocol"]
93    pub struct UnknownProtocol;
94}
95
96impl TryFrom<Protocol> for ForwardedProtocol {
97    type Error = UnknownProtocol;
98
99    fn try_from(p: Protocol) -> Result<Self, Self::Error> {
100        if p.is_http() {
101            if p.is_secure() {
102                Ok(Self(ProtocolKind::Https))
103            } else {
104                Ok(Self(ProtocolKind::Http))
105            }
106        } else {
107            Err(UnknownProtocol)
108        }
109    }
110}
111
112impl TryFrom<&Protocol> for ForwardedProtocol {
113    type Error = UnknownProtocol;
114
115    fn try_from(p: &Protocol) -> Result<Self, Self::Error> {
116        if p.is_http() {
117            if p.is_secure() {
118                Ok(Self(ProtocolKind::Https))
119            } else {
120                Ok(Self(ProtocolKind::Http))
121            }
122        } else {
123            Err(UnknownProtocol)
124        }
125    }
126}
127
128static_str_error! {
129    #[doc = "invalid forwarded protocol string"]
130    pub struct InvalidProtocolStr;
131}
132
133impl TryFrom<&str> for ForwardedProtocol {
134    type Error = InvalidProtocolStr;
135
136    fn try_from(s: &str) -> Result<Self, Self::Error> {
137        if eq_ignore_ascii_case!(s, HTTP_STR) {
138            Ok(Self(ProtocolKind::Http))
139        } else if eq_ignore_ascii_case!(s, HTTPS_STR) {
140            Ok(Self(ProtocolKind::Https))
141        } else {
142            Err(InvalidProtocolStr)
143        }
144    }
145}
146
147impl TryFrom<String> for ForwardedProtocol {
148    type Error = InvalidProtocolStr;
149
150    fn try_from(s: String) -> Result<Self, Self::Error> {
151        s.as_str().try_into()
152    }
153}
154
155impl TryFrom<&String> for ForwardedProtocol {
156    type Error = InvalidProtocolStr;
157
158    fn try_from(s: &String) -> Result<Self, Self::Error> {
159        s.as_str().try_into()
160    }
161}
162
163impl FromStr for ForwardedProtocol {
164    type Err = InvalidProtocolStr;
165
166    fn from_str(s: &str) -> Result<Self, Self::Err> {
167        s.try_into()
168    }
169}
170
171impl PartialEq<str> for ForwardedProtocol {
172    fn eq(&self, other: &str) -> bool {
173        match &self.0 {
174            ProtocolKind::Https => other.eq_ignore_ascii_case(HTTPS_STR),
175            ProtocolKind::Http => other.eq_ignore_ascii_case(HTTP_STR) || other.is_empty(),
176        }
177    }
178}
179
180impl PartialEq<String> for ForwardedProtocol {
181    fn eq(&self, other: &String) -> bool {
182        self == other.as_str()
183    }
184}
185
186impl PartialEq<&str> for ForwardedProtocol {
187    fn eq(&self, other: &&str) -> bool {
188        self == *other
189    }
190}
191
192impl PartialEq<ForwardedProtocol> for str {
193    fn eq(&self, other: &ForwardedProtocol) -> bool {
194        other == self
195    }
196}
197
198impl PartialEq<ForwardedProtocol> for String {
199    fn eq(&self, other: &ForwardedProtocol) -> bool {
200        other == self.as_str()
201    }
202}
203
204impl PartialEq<ForwardedProtocol> for &str {
205    #[inline(always)]
206    fn eq(&self, other: &ForwardedProtocol) -> bool {
207        other == *self
208    }
209}
210
211impl core::fmt::Display for ForwardedProtocol {
212    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
213        self.as_scheme().fmt(f)
214    }
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn test_protocol_from_str() {
223        assert_eq!("http".parse(), Ok(ForwardedProtocol::HTTP));
224        assert_eq!("https".parse(), Ok(ForwardedProtocol::HTTPS));
225    }
226
227    #[test]
228    fn test_protocol_secure() {
229        assert!(!ForwardedProtocol::HTTP.is_secure());
230        assert!(ForwardedProtocol::HTTPS.is_secure());
231    }
232}