http_wasm_guest/host/header.rs
1use std::collections::HashMap;
2
3use crate::host::{Bytes, handler};
4
5/// Handle for accessing and mutating HTTP headers.
6///
7/// A `Header` is scoped to either the request or response, depending on how it
8/// is constructed.
9pub struct Header(i32);
10
11impl Header {
12 /// Create a header handle for a specific host-defined kind.
13 ///
14 /// The `kind` value is used by the host API to distinguish between
15 /// request and response headers.
16 pub(crate) fn new(kind: i32) -> Self {
17 Self(kind)
18 }
19
20 /// Returns an iterator over all header names as raw bytes without allocating into a vector.
21 ///
22 /// Header names are returned in the order provided by the host runtime.
23 /// This method is zero-allocation and returns an iterator that yields each
24 /// header name as `Bytes`. For heap-allocated results, use [`names`](Header::names).
25 pub fn names_iter(&self) -> impl Iterator<Item = Bytes> + use<'_> {
26 handler::header_names(self.0).into_iter().map(Bytes::from)
27 }
28
29 /// Returns all header names as raw bytes, allocating into a vector.
30 ///
31 /// Header names are returned in the order provided by the host runtime.
32 /// This method collects results into a `Vec`, which allocates heap memory.
33 /// Use [`names_iter`](Header::names_iter) for zero-allocation access.
34 pub fn names(&self) -> Vec<Bytes> {
35 self.names_iter().collect()
36 }
37
38 /// Returns an iterator over all values for the given header name without allocating into a vector.
39 ///
40 /// The `name` is matched by the host according to its header normalization
41 /// rules (often case-insensitive). This method is zero-allocation and returns
42 /// an iterator that yields each header value as `Bytes`. For heap-allocated results,
43 /// use [`values`](Header::values).
44 pub fn values_iter(&self, name: &[u8]) -> impl Iterator<Item = Bytes> + use<'_> {
45 handler::header_values(self.0, name).into_iter().map(Bytes::from)
46 }
47
48 /// Return the first value for the given header name, if present.
49 pub fn get(&self, name: &[u8]) -> Option<Bytes> {
50 self.values_iter(name).next()
51 }
52
53 /// Returns all values for the given header name, allocating into a vector.
54 ///
55 /// The `name` is matched by the host according to its header normalization
56 /// rules (often case-insensitive). This method collects results into a `Vec`,
57 /// which allocates heap memory. Use [`values_iter`](Header::values_iter) for
58 /// zero-allocation access.
59 pub fn values(&self, name: &[u8]) -> Vec<Bytes> {
60 self.values_iter(name).collect()
61 }
62
63 /// Set a header value, replacing any existing values.
64 pub fn set(&self, name: &[u8], value: &[u8]) {
65 handler::set_header(self.0, name, value);
66 }
67
68 /// Add an additional value for a header name.
69 pub fn add(&self, name: &[u8], value: &[u8]) {
70 handler::add_header_value(self.0, name, value);
71 }
72
73 /// Remove a header and all of its values.
74 pub fn remove(&self, name: &[u8]) {
75 handler::remove_header(self.0, name);
76 }
77
78 /// Return all headers as an iterator of names to value lists.
79 ///
80 /// This returns an iterator over all header entries. Each entry contains
81 /// the header name paired with a vector containing its associated values.
82 /// For zero-allocation access, use [`names_iter`](Header::names_iter) and
83 /// [`values_iter`](Header::values_iter).
84 pub fn entries_iter(&self) -> impl Iterator<Item = (Bytes, Vec<Bytes>)> + '_ {
85 self.names_iter().map(|name| {
86 let values: Vec<Bytes> = self.values_iter(&name).collect();
87 (name, values)
88 })
89 }
90
91 /// Return all headers as a map of names to value lists.
92 ///
93 /// This collects all names and then queries each set of values, allocating
94 /// into a `HashMap` and multiple `Vec`s for the values. Each header name is
95 /// paired with a vector containing its associated values. Use
96 /// [`entries_iter`](Header::entries_iter) for zero-allocation access.
97 pub fn entries(&self) -> HashMap<Bytes, Vec<Bytes>> {
98 self.entries_iter().collect()
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn header_get_existing() {
108 let header = Header::new(0);
109 // The mock has "X-FOO" header with value "test1"
110 let value = header.get(b"X-FOO");
111 assert!(value.is_some());
112 assert_eq!(&value.unwrap(), b"test1");
113 }
114
115 #[test]
116 fn header_get_nonexistent() {
117 let header = Header::new(0);
118 let value = header.get(b"X-NONEXISTENT");
119 assert!(value.is_none());
120 }
121
122 #[test]
123 fn header_get_all_single_value() {
124 let header = Header::new(0);
125 let values = header.values(b"X-FOO");
126 assert_eq!(values.len(), 1);
127 assert_eq!(values[0], b"test1");
128 }
129
130 #[test]
131 fn header_get_all_multiple_values() {
132 let header = Header::new(0);
133 // The mock has "x-bar" with values "test2" and "test3"
134 let values = header.values(b"x-bar");
135 assert_eq!(values.len(), 2);
136 assert_eq!(values[0], "test2");
137 assert_eq!(values[1], b"test3");
138 }
139
140 #[test]
141 fn header_names() {
142 let header = Header::new(0);
143 let names = header.names();
144 // The mock provides: X-FOO, x-bar, x-baz
145 assert_eq!(names.len(), 3);
146 }
147
148 #[test]
149 fn header_values_map() {
150 let header = Header::new(0);
151 let values_map = header.entries();
152 // Should have 3 distinct header names
153 assert_eq!(values_map.len(), 3);
154 // X-FOO should have 1 value
155 assert_eq!(values_map.get(&Bytes::from("X-FOO")).unwrap().len(), 1);
156 // x-bar should have 2 values
157 assert_eq!(values_map.get(&Bytes::from(b"x-bar")).map(|v| v.len()), Some(2));
158 }
159
160 #[test]
161 fn header_values_iter() {
162 let header = Header::new(0);
163 let values = header.entries_iter();
164
165 assert_eq!(values.count(), 3);
166 }
167
168 #[test]
169 fn header_operations_with_bytes() {
170 let header = Header::new(0);
171 let name = Bytes::from("x-bar");
172 let values = header.values(&name);
173 assert!(!values.is_empty());
174 }
175
176 #[test]
177 fn header_values_map_with_duplicate_values() {
178 let header = Header::new(0);
179 let values_map = header.entries();
180
181 //should have 2 values
182 let dup_values = values_map.get(&Bytes::from("x-baz")).unwrap();
183 assert_eq!(dup_values.len(), 2);
184 }
185}