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}