Skip to main content

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}