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