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