trillium_http/headers/
header_value.rs1use crate::compact_cow::CompactCow;
2use HeaderValueInner::{Bytes, Utf8};
3use compact_str::CompactString;
4use std::{
5 borrow::Cow,
6 fmt::{Debug, Display, Formatter, Write},
7 sync::Arc,
8};
9
10#[derive(Clone)]
13pub struct HeaderValue {
14 pub(crate) inner: HeaderValueInner,
15 pub(crate) never_indexed: bool,
20}
21
22impl HeaderValue {
23 pub(crate) const fn from_inner(inner: HeaderValueInner) -> Self {
24 Self {
25 inner,
26 never_indexed: false,
27 }
28 }
29
30 pub(crate) fn is_never_indexed(&self) -> bool {
31 self.never_indexed
32 }
33
34 pub(crate) fn set_never_indexed(&mut self, never_indexed: bool) {
35 self.never_indexed = never_indexed;
36 }
37}
38
39impl PartialEq for HeaderValue {
40 fn eq(&self, other: &Self) -> bool {
41 self.inner == other.inner
42 }
43}
44
45impl Eq for HeaderValue {}
46
47impl std::hash::Hash for HeaderValue {
48 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
49 self.inner.hash(state);
50 }
51}
52
53impl PartialOrd for HeaderValue {
54 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
55 Some(self.cmp(other))
56 }
57}
58
59impl Ord for HeaderValue {
60 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
61 self.inner.cmp(&other.inner)
62 }
63}
64
65impl From<Cow<'static, [u8]>> for HeaderValue {
66 fn from(value: Cow<'static, [u8]>) -> Self {
67 match value {
68 Cow::Borrowed(bytes) => match std::str::from_utf8(bytes) {
69 Ok(s) => Self::from_inner(Utf8(CompactCow::Borrowed(s))),
70 Err(_) => Self::from_inner(Bytes(bytes.into())),
71 },
72
73 Cow::Owned(bytes) => match String::from_utf8(bytes) {
74 Ok(s) => Self::from_inner(Utf8(CompactCow::Owned(s.into()))),
75 Err(e) => Self::from_inner(Bytes(e.into_bytes().into())),
76 },
77 }
78 }
79}
80
81#[derive(Eq, PartialEq, Clone, Hash)]
82pub(crate) enum HeaderValueInner {
83 Utf8(CompactCow<'static>),
84 Bytes(Arc<[u8]>),
85}
86
87impl PartialOrd for HeaderValueInner {
88 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
89 Some(self.cmp(other))
90 }
91}
92impl Ord for HeaderValueInner {
93 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
94 let this: &[u8] = self.as_ref();
95 let that: &[u8] = other.as_ref();
96 this.cmp(that)
97 }
98}
99
100#[cfg(feature = "serde")]
101impl serde::Serialize for HeaderValue {
102 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103 where
104 S: serde::Serializer,
105 {
106 match &self.inner {
107 Utf8(s) => serializer.serialize_str(s),
108 Bytes(bytes) => serializer.serialize_bytes(bytes),
109 }
110 }
111}
112
113impl Debug for HeaderValue {
114 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
115 match &self.inner {
116 Utf8(s) => Debug::fmt(s, f),
117 Bytes(b) => Debug::fmt(&String::from_utf8_lossy(b), f),
118 }
119 }
120}
121
122impl HeaderValue {
123 pub const fn const_new(value: &'static str) -> Self {
125 Self::from_inner(Utf8(CompactCow::Borrowed(value)))
126 }
127
128 pub fn is_valid(&self) -> bool {
130 memchr::memchr3(b'\r', b'\n', 0, self.as_ref()).is_none()
131 }
132
133 pub fn as_str(&self) -> Option<&str> {
136 match &self.inner {
137 Utf8(utf8) => Some(utf8),
138 Bytes(_) => None,
139 }
140 }
141}
142
143impl HeaderValue {
144 pub(crate) fn parse(bytes: &[u8]) -> Self {
145 match std::str::from_utf8(bytes) {
146 Ok(s) => Self::from_inner(Utf8(CompactCow::Owned(s.into()))),
147 Err(_) => Self::from_inner(Bytes(bytes.into())),
148 }
149 }
150}
151
152impl Display for HeaderValue {
153 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
154 match &self.inner {
155 Utf8(s) => f.write_str(s),
156 Bytes(b) => f.write_str(&String::from_utf8_lossy(b)),
157 }
158 }
159}
160
161impl From<Vec<u8>> for HeaderValue {
162 fn from(v: Vec<u8>) -> Self {
163 match String::from_utf8(v) {
164 Ok(s) => Self::from_inner(Utf8(CompactCow::Owned(s.into()))),
165 Err(e) => Self::from_inner(Bytes(e.into_bytes().into())),
166 }
167 }
168}
169
170impl From<Cow<'static, str>> for HeaderValue {
171 fn from(c: Cow<'static, str>) -> Self {
172 Self::from_inner(Utf8(CompactCow::from(c)))
173 }
174}
175
176impl From<&'static [u8]> for HeaderValue {
177 fn from(b: &'static [u8]) -> Self {
178 match std::str::from_utf8(b) {
179 Ok(s) => Self::from_inner(Utf8(CompactCow::Borrowed(s))),
180 Err(_) => Self::from_inner(Bytes(b.into())),
181 }
182 }
183}
184
185impl From<String> for HeaderValue {
186 fn from(s: String) -> Self {
187 Self::from_inner(Utf8(CompactCow::Owned(s.into())))
188 }
189}
190
191impl From<&'static str> for HeaderValue {
192 fn from(s: &'static str) -> Self {
193 Self::from_inner(Utf8(CompactCow::Borrowed(s)))
194 }
195}
196
197macro_rules! from_integer {
200 ($($t:ty),*) => {
201 $(
202 impl From<$t> for HeaderValue {
203 fn from(value: $t) -> Self {
204 let mut digits = itoa::Buffer::new();
205 let value = CompactString::new(digits.format(value));
206 Self::from_inner(Utf8(CompactCow::Owned(value)))
207 }
208 }
209 )*
210 };
211}
212
213from_integer!(usize, u64, u16, u32, i32, i64);
214
215impl From<std::fmt::Arguments<'_>> for HeaderValue {
216 fn from(value: std::fmt::Arguments<'_>) -> Self {
217 let mut s = CompactString::default();
218 s.write_fmt(value).unwrap();
219 Self::from_inner(Utf8(CompactCow::Owned(s)))
220 }
221}
222
223impl AsRef<[u8]> for HeaderValueInner {
224 fn as_ref(&self) -> &[u8] {
225 match self {
226 Utf8(utf8) => utf8.as_bytes(),
227 Bytes(b) => b,
228 }
229 }
230}
231
232impl AsRef<[u8]> for HeaderValue {
233 fn as_ref(&self) -> &[u8] {
234 self.inner.as_ref()
235 }
236}
237
238impl PartialEq<&str> for HeaderValue {
239 fn eq(&self, other: &&str) -> bool {
240 self.as_str() == Some(*other)
241 }
242}
243
244impl PartialEq<&[u8]> for HeaderValue {
245 fn eq(&self, other: &&[u8]) -> bool {
246 self.as_ref() == *other
247 }
248}
249
250impl PartialEq<[u8]> for HeaderValue {
251 fn eq(&self, other: &[u8]) -> bool {
252 self.as_ref() == other
253 }
254}
255
256impl PartialEq<str> for HeaderValue {
257 fn eq(&self, other: &str) -> bool {
258 self.as_str() == Some(other)
259 }
260}
261
262impl PartialEq<String> for HeaderValue {
263 fn eq(&self, other: &String) -> bool {
264 self.as_str() == Some(other)
265 }
266}
267
268impl PartialEq<&String> for HeaderValue {
269 fn eq(&self, other: &&String) -> bool {
270 self.as_str() == Some(&**other)
271 }
272}
273
274impl PartialEq<[u8]> for &HeaderValue {
275 fn eq(&self, other: &[u8]) -> bool {
276 self.as_ref() == other
277 }
278}
279
280impl PartialEq<str> for &HeaderValue {
281 fn eq(&self, other: &str) -> bool {
282 self.as_str() == Some(other)
283 }
284}
285
286impl PartialEq<String> for &HeaderValue {
287 fn eq(&self, other: &String) -> bool {
288 self.as_str() == Some(other)
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::HeaderValue;
295
296 #[test]
297 fn integers_format_as_decimal() {
298 assert_eq!(HeaderValue::from(0_u64), "0");
299 assert_eq!(HeaderValue::from(u64::MAX), "18446744073709551615");
300 assert_eq!(HeaderValue::from(i64::MIN), "-9223372036854775808");
301 assert_eq!(HeaderValue::from(1234_usize), "1234");
302 assert_eq!(HeaderValue::from(u16::MAX), "65535");
303 assert_eq!(HeaderValue::from(-42_i32), "-42");
304 }
305}