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