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