Skip to content

Commit b91b1a9

Browse files
committed
more Headers compatibility
1 parent db3a2d6 commit b91b1a9

File tree

1 file changed

+118
-18
lines changed

1 file changed

+118
-18
lines changed

src/browser/fetch/Headers.zig

Lines changed: 118 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,39 +23,68 @@ const Page = @import("../page.zig").Page;
2323
// https://developer.mozilla.org/en-US/docs/Web/API/Headers
2424
const Headers = @This();
2525

26-
headers: std.StringHashMapUnmanaged([]const u8),
26+
// Case-Insensitive String HashMap.
27+
// This allows us to avoid having to allocate lowercase keys all the time.
28+
const HeaderHashMap = std.HashMapUnmanaged([]const u8, []const u8, struct {
29+
pub fn hash(_: @This(), s: []const u8) u64 {
30+
var hasher = std.hash.Wyhash.init(s.len);
31+
for (s) |c| {
32+
hasher.update(&.{std.ascii.toLower(c)});
33+
}
34+
35+
return hasher.final();
36+
}
37+
pub fn eql(_: @This(), a: []const u8, b: []const u8) bool {
38+
if (a.len != b.len) return false;
39+
40+
for (a, b) |c1, c2| {
41+
if (std.ascii.toLower(c1) != std.ascii.toLower(c2)) return false;
42+
}
43+
44+
return true;
45+
}
46+
}, 80);
47+
48+
headers: HeaderHashMap = .empty,
2749

2850
// They can either be:
2951
//
3052
// 1. An array of string pairs.
3153
// 2. An object with string keys to string values.
3254
// 3. Another Headers object.
33-
const HeadersInit = union(enum) {
34-
strings: []const []const u8,
35-
// headers: Headers,
55+
pub const HeadersInit = union(enum) {
56+
// List of Pairs of []const u8
57+
strings: []const []const []const u8,
58+
headers: *Headers,
3659
};
3760

38-
pub fn constructor(_init: ?[]const HeadersInit, page: *Page) !Headers {
61+
pub fn constructor(_init: ?HeadersInit, page: *Page) !Headers {
3962
const arena = page.arena;
40-
var headers = std.StringHashMapUnmanaged([]const u8).empty;
63+
var headers: HeaderHashMap = .empty;
4164

4265
if (_init) |init| {
43-
for (init) |item| {
44-
switch (item) {
45-
.strings => |pair| {
66+
switch (init) {
67+
.strings => |kvs| {
68+
for (kvs) |pair| {
4669
// Can only have two string elements if in a pair.
4770
if (pair.len != 2) {
4871
return error.TypeError;
4972
}
5073

51-
const raw_key = pair[0];
52-
const value = pair[1];
53-
const key = try std.ascii.allocLowerString(arena, raw_key);
74+
const key = try page.arena.dupe(u8, pair[0]);
75+
const value = try page.arena.dupe(u8, pair[1]);
5476

5577
try headers.put(arena, key, value);
56-
},
57-
// .headers => |_| {},
58-
}
78+
}
79+
},
80+
.headers => |hdrs| {
81+
var iter = hdrs.headers.iterator();
82+
while (iter.next()) |entry| {
83+
const key = try page.arena.dupe(u8, entry.key_ptr.*);
84+
const value = try page.arena.dupe(u8, entry.value_ptr.*);
85+
try headers.put(arena, key, value);
86+
}
87+
},
5988
}
6089
}
6190

@@ -64,14 +93,70 @@ pub fn constructor(_init: ?[]const HeadersInit, page: *Page) !Headers {
6493
};
6594
}
6695

67-
pub fn _get(self: *const Headers, header: []const u8, page: *Page) !?[]const u8 {
96+
pub fn clone(self: *Headers, allocator: std.mem.Allocator) !Headers {
97+
return Headers{
98+
.headers = try self.headers.clone(allocator),
99+
};
100+
}
101+
102+
pub fn _append(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
68103
const arena = page.arena;
69-
const key = try std.ascii.allocLowerString(arena, header);
70104

71-
const value = (self.headers.getEntry(key) orelse return null).value_ptr.*;
105+
if (self.headers.getEntry(name)) |entry| {
106+
// If we found it, append the value.
107+
const new_value = try std.fmt.allocPrint(arena, "{s}, {s}", .{ entry.value_ptr.*, value });
108+
entry.value_ptr.* = new_value;
109+
} else {
110+
// Otherwise, we should just put it in.
111+
try self.headers.putNoClobber(
112+
arena,
113+
try arena.dupe(u8, name),
114+
try arena.dupe(u8, value),
115+
);
116+
}
117+
}
118+
119+
pub fn _delete(self: *Headers, name: []const u8) void {
120+
_ = self.headers.remove(name);
121+
}
122+
123+
// TODO: entries iterator
124+
// They should be:
125+
// 1. Sorted in lexicographical order.
126+
// 2. Duplicate header names should be combined.
127+
128+
// TODO: header for each
129+
130+
pub fn _get(self: *const Headers, name: []const u8, page: *Page) !?[]const u8 {
131+
const arena = page.arena;
132+
const value = (self.headers.getEntry(name) orelse return null).value_ptr.*;
72133
return try arena.dupe(u8, value);
73134
}
74135

136+
pub fn _has(self: *const Headers, name: []const u8) bool {
137+
return self.headers.contains(name);
138+
}
139+
140+
// TODO: keys iterator
141+
142+
pub fn _set(self: *Headers, name: []const u8, value: []const u8, page: *Page) !void {
143+
const arena = page.arena;
144+
145+
if (self.headers.getEntry(name)) |entry| {
146+
// If we found it, set the value.
147+
entry.value_ptr.* = try arena.dupe(u8, value);
148+
} else {
149+
// Otherwise, we should just put it in.
150+
try self.headers.putNoClobber(
151+
arena,
152+
try arena.dupe(u8, name),
153+
try arena.dupe(u8, value),
154+
);
155+
}
156+
}
157+
158+
// TODO: values iterator
159+
75160
const testing = @import("../../testing.zig");
76161
test "fetch: headers" {
77162
var runner = try testing.jsRunner(testing.tracking_allocator, .{ .url = "https://lightpanda.io" });
@@ -85,4 +170,19 @@ test "fetch: headers" {
85170
.{ "let headers = new Headers([['Set-Cookie', 'name=world']])", "undefined" },
86171
.{ "headers.get('set-cookie')", "name=world" },
87172
}, .{});
173+
174+
// adapted from the mdn examples
175+
try runner.testCases(&.{
176+
.{ "const myHeaders = new Headers();", "undefined" },
177+
.{ "myHeaders.append('Content-Type', 'image/jpeg')", "undefined" },
178+
.{ "myHeaders.has('Picture-Type')", "false" },
179+
.{ "myHeaders.get('Content-Type')", "image/jpeg" },
180+
.{ "myHeaders.append('Content-Type', 'image/png')", "undefined" },
181+
.{ "myHeaders.get('Content-Type')", "image/jpeg, image/png" },
182+
.{ "myHeaders.delete('Content-Type')", "undefined" },
183+
.{ "myHeaders.get('Content-Type')", "null" },
184+
.{ "myHeaders.set('Picture-Type', 'image/svg')", "undefined" },
185+
.{ "myHeaders.get('Picture-Type')", "image/svg" },
186+
.{ "myHeaders.has('Picture-Type')", "true" },
187+
}, .{});
88188
}

0 commit comments

Comments
 (0)