rama_hyper/ext/
h1_reason_phrase.rs1use std::convert::TryFrom;
2
3use bytes::Bytes;
4
5#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
36pub struct ReasonPhrase(Bytes);
37
38impl ReasonPhrase {
39    pub fn as_bytes(&self) -> &[u8] {
41        &self.0
42    }
43
44    pub const fn from_static(reason: &'static [u8]) -> Self {
46        if find_invalid_byte(reason).is_some() {
48            panic!("invalid byte in static reason phrase");
49        }
50        Self(Bytes::from_static(reason))
51    }
52
53    #[cfg(feature = "client")]
59    pub(crate) fn from_bytes_unchecked(reason: Bytes) -> Self {
60        Self(reason)
61    }
62}
63
64impl TryFrom<&[u8]> for ReasonPhrase {
65    type Error = InvalidReasonPhrase;
66
67    fn try_from(reason: &[u8]) -> Result<Self, Self::Error> {
68        if let Some(bad_byte) = find_invalid_byte(reason) {
69            Err(InvalidReasonPhrase { bad_byte })
70        } else {
71            Ok(Self(Bytes::copy_from_slice(reason)))
72        }
73    }
74}
75
76impl TryFrom<Vec<u8>> for ReasonPhrase {
77    type Error = InvalidReasonPhrase;
78
79    fn try_from(reason: Vec<u8>) -> Result<Self, Self::Error> {
80        if let Some(bad_byte) = find_invalid_byte(&reason) {
81            Err(InvalidReasonPhrase { bad_byte })
82        } else {
83            Ok(Self(Bytes::from(reason)))
84        }
85    }
86}
87
88impl TryFrom<String> for ReasonPhrase {
89    type Error = InvalidReasonPhrase;
90
91    fn try_from(reason: String) -> Result<Self, Self::Error> {
92        if let Some(bad_byte) = find_invalid_byte(reason.as_bytes()) {
93            Err(InvalidReasonPhrase { bad_byte })
94        } else {
95            Ok(Self(Bytes::from(reason)))
96        }
97    }
98}
99
100impl TryFrom<Bytes> for ReasonPhrase {
101    type Error = InvalidReasonPhrase;
102
103    fn try_from(reason: Bytes) -> Result<Self, Self::Error> {
104        if let Some(bad_byte) = find_invalid_byte(&reason) {
105            Err(InvalidReasonPhrase { bad_byte })
106        } else {
107            Ok(Self(reason))
108        }
109    }
110}
111
112impl Into<Bytes> for ReasonPhrase {
113    fn into(self) -> Bytes {
114        self.0
115    }
116}
117
118impl AsRef<[u8]> for ReasonPhrase {
119    fn as_ref(&self) -> &[u8] {
120        &self.0
121    }
122}
123
124#[derive(Debug)]
130pub struct InvalidReasonPhrase {
131    bad_byte: u8,
132}
133
134impl std::fmt::Display for InvalidReasonPhrase {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        write!(f, "Invalid byte in reason phrase: {}", self.bad_byte)
137    }
138}
139
140impl std::error::Error for InvalidReasonPhrase {}
141
142const fn is_valid_byte(b: u8) -> bool {
143    const fn is_vchar(b: u8) -> bool {
145        0x21 <= b && b <= 0x7E
146    }
147
148    #[allow(unused_comparisons)]
153    const fn is_obs_text(b: u8) -> bool {
154        0x80 <= b && b <= 0xFF
155    }
156
157    b == b'\t' || b == b' ' || is_vchar(b) || is_obs_text(b)
159}
160
161const fn find_invalid_byte(bytes: &[u8]) -> Option<u8> {
162    let mut i = 0;
163    while i < bytes.len() {
164        let b = bytes[i];
165        if !is_valid_byte(b) {
166            return Some(b);
167        }
168        i += 1;
169    }
170    None
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176
177    #[test]
178    fn basic_valid() {
179        const PHRASE: &'static [u8] = b"OK";
180        assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
181        assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
182    }
183
184    #[test]
185    fn empty_valid() {
186        const PHRASE: &'static [u8] = b"";
187        assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
188        assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
189    }
190
191    #[test]
192    fn obs_text_valid() {
193        const PHRASE: &'static [u8] = b"hyp\xe9r";
194        assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
195        assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
196    }
197
198    const NEWLINE_PHRASE: &'static [u8] = b"hyp\ner";
199
200    #[test]
201    #[should_panic]
202    fn newline_invalid_panic() {
203        ReasonPhrase::from_static(NEWLINE_PHRASE);
204    }
205
206    #[test]
207    fn newline_invalid_err() {
208        assert!(ReasonPhrase::try_from(NEWLINE_PHRASE).is_err());
209    }
210
211    const CR_PHRASE: &'static [u8] = b"hyp\rer";
212
213    #[test]
214    #[should_panic]
215    fn cr_invalid_panic() {
216        ReasonPhrase::from_static(CR_PHRASE);
217    }
218
219    #[test]
220    fn cr_invalid_err() {
221        assert!(ReasonPhrase::try_from(CR_PHRASE).is_err());
222    }
223}