1use arrayvec::{ArrayString, ArrayVec};
12use std::fmt::Write;
13
14pub const MAX_INLINE_VALUE: usize = 64;
18
19pub const MAX_INLINE_HEADERS: usize = 8;
21
22#[derive(Debug, Clone)]
32pub enum HeaderValue {
33 Static(&'static str),
35 Inline(ArrayString<MAX_INLINE_VALUE>),
37 Heap(String),
39}
40
41impl HeaderValue {
42 #[inline(always)]
44 pub fn as_str(&self) -> &str {
45 match self {
46 HeaderValue::Static(s) => s,
47 HeaderValue::Inline(s) => s.as_str(),
48 HeaderValue::Heap(s) => s.as_str(),
49 }
50 }
51
52 #[inline]
54 fn from_owned(s: String) -> Self {
55 if s.len() <= MAX_INLINE_VALUE {
56 HeaderValue::Inline(ArrayString::from(&s).unwrap())
57 } else {
58 HeaderValue::Heap(s)
59 }
60 }
61}
62
63pub trait IntoHeaderValue {
70 fn into_header_value(self) -> HeaderValue;
71}
72
73impl IntoHeaderValue for &'static str {
75 #[inline(always)]
76 fn into_header_value(self) -> HeaderValue {
77 HeaderValue::Static(self)
78 }
79}
80
81impl IntoHeaderValue for String {
83 #[inline]
84 fn into_header_value(self) -> HeaderValue {
85 HeaderValue::from_owned(self)
86 }
87}
88
89macro_rules! impl_into_header_value_int {
94 ($($t:ty),*) => {
95 $(impl IntoHeaderValue for $t {
96 #[inline]
97 fn into_header_value(self) -> HeaderValue {
98 let mut buf = ArrayString::<MAX_INLINE_VALUE>::new();
99 write!(buf, "{}", self).ok();
101 HeaderValue::Inline(buf)
102 }
103 })*
104 };
105}
106
107impl_into_header_value_int!(
108 u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize
109);
110
111#[derive(Debug, Clone)]
118pub struct Header {
119 pub name: &'static str,
120 pub value: HeaderValue,
121}
122
123#[derive(Debug, Clone)]
136pub struct Headers {
137 slab: ArrayVec<Header, MAX_INLINE_HEADERS>,
139 spill: Option<Vec<Header>>,
141}
142
143impl Default for Headers {
144 fn default() -> Self {
145 Headers::new()
146 }
147}
148
149impl Headers {
150 #[inline(always)]
152 pub fn new() -> Self {
153 Headers {
154 slab: ArrayVec::new(),
155 spill: None,
156 }
157 }
158
159 #[inline]
167 pub fn add(&mut self, name: &'static str, value: impl IntoHeaderValue) {
168 let header = Header {
169 name,
170 value: value.into_header_value(),
171 };
172 if !self.slab.is_full() {
173 self.slab.push(header);
175 } else {
176 self.spill.get_or_insert_with(Vec::new).push(header);
177 }
178 }
179
180 #[inline(always)]
183 pub fn add_static(&mut self, name: &'static str, value: &'static str) {
184 self.add(name, value);
185 }
186
187 #[inline]
189 pub fn iter(
190 &self,
191 ) -> std::iter::Chain<std::slice::Iter<'_, Header>, std::slice::Iter<'_, Header>> {
192 let spill_slice: &[Header] = self.spill.as_deref().unwrap_or(&[]);
193 self.slab.iter().chain(spill_slice.iter())
194 }
195
196 #[inline(always)]
198 pub fn is_empty(&self) -> bool {
199 self.slab.is_empty()
200 }
201
202 #[inline(always)]
204 pub fn len(&self) -> usize {
205 self.slab.len() + self.spill.as_ref().map_or(0, |v| v.len())
206 }
207}
208
209#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn test_inline_headers_no_spill() {
217 let mut h = Headers::new();
218 for i in 0..MAX_INLINE_HEADERS {
219 h.add("X-Test", format!("value-{}", i));
220 }
221 assert!(h.spill.is_none(), "should not spill at capacity");
222 assert_eq!(h.len(), MAX_INLINE_HEADERS);
223 }
224
225 #[test]
226 fn test_spill_to_heap_on_overflow() {
227 let mut h = Headers::new();
228 for i in 0..=MAX_INLINE_HEADERS {
229 h.add("X-Test", format!("value-{}", i));
231 }
232 assert!(h.spill.is_some(), "9th header should create spill Vec");
233 assert_eq!(h.len(), MAX_INLINE_HEADERS + 1);
234 }
235
236 #[test]
237 fn test_static_str_value() {
238 let mut h = Headers::new();
239 h.add("Content-Type", "application/json");
240 let hdr = h.iter().next().unwrap();
241 assert_eq!(hdr.value.as_str(), "application/json");
242 assert!(matches!(hdr.value, HeaderValue::Static(_)));
244 }
245
246 #[test]
247 fn test_short_string_inline() {
248 let val = "gzip".to_string();
249 let mut h = Headers::new();
250 h.add("Content-Encoding", val);
251 let hdr = h.iter().next().unwrap();
252 assert!(matches!(hdr.value, HeaderValue::Inline(_)));
253 assert_eq!(hdr.value.as_str(), "gzip");
254 }
255
256 #[test]
257 fn test_long_string_heap() {
258 let long = "x".repeat(MAX_INLINE_VALUE + 1);
259 let v = HeaderValue::from_owned(long.clone());
260 assert!(matches!(v, HeaderValue::Heap(_)));
261 assert_eq!(v.as_str(), long);
262 }
263
264 #[test]
265 fn test_integer_value_inline() {
266 let mut h = Headers::new();
267 h.add("Content-Length", 12345usize);
268 let hdr = h.iter().next().unwrap();
269 assert!(matches!(hdr.value, HeaderValue::Inline(_)));
270 assert_eq!(hdr.value.as_str(), "12345");
271 }
272
273 #[test]
274 fn test_iter_order_preserved() {
275 let mut h = Headers::new();
276 h.add("X-A", "1");
277 h.add("X-B", "2");
278 h.add("X-C", "3");
279 let names: Vec<&str> = h.iter().map(|hdr| hdr.name).collect();
280 assert_eq!(names, vec!["X-A", "X-B", "X-C"]);
281 }
282
283 #[test]
284 fn test_heap_iter_order_preserved() {
285 let mut h = Headers::new();
286 for i in 0..=MAX_INLINE_HEADERS {
287 let name: &'static str = ["A", "B", "C", "D", "E", "F", "G", "H", "I"][i];
288 h.add(name, i as u32);
289 }
290 assert!(h.spill.is_some(), "9th header should have caused a spill");
291 let vals: Vec<&str> = h.iter().map(|hdr| hdr.value.as_str()).collect::<Vec<_>>();
292 assert_eq!(vals, vec!["0", "1", "2", "3", "4", "5", "6", "7", "8"]);
293 }
294}