Skip to content

Commit 110dc75

Browse files
committed
add FormData web API
1 parent 6f9dd8d commit 110dc75

File tree

2 files changed

+246
-0
lines changed

2 files changed

+246
-0
lines changed

src/browser/env.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const WebApis = struct {
3535
@import("storage/storage.zig").Interfaces,
3636
@import("url/url.zig").Interfaces,
3737
@import("xhr/xhr.zig").Interfaces,
38+
@import("url/form_data.zig").Interfaces,
3839
@import("xmlserializer/xmlserializer.zig").Interfaces,
3940
});
4041
};

src/browser/url/form_data.zig

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
// Copyright (C) 2023-2024 Lightpanda (Selecy SAS)
2+
//
3+
// Francis Bouvier <[email protected]>
4+
// Pierre Tachoire <[email protected]>
5+
//
6+
// This program is free software: you can redistribute it and/or modify
7+
// it under the terms of the GNU Affero General Public License as
8+
// published by the Free Software Foundation, either version 3 of the
9+
// License, or (at your option) any later version.
10+
//
11+
// This program is distributed in the hope that it will be useful,
12+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
// GNU Affero General Public License for more details.
15+
//
16+
// You should have received a copy of the GNU Affero General Public License
17+
// along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
19+
const std = @import("std");
20+
21+
const Allocator = std.mem.Allocator;
22+
23+
const iterator = @import("../iterator/iterator.zig");
24+
const SessionState = @import("../env.zig").SessionState;
25+
26+
pub const Interfaces = .{
27+
FormData,
28+
KeyIterable,
29+
ValueIterable,
30+
EntryIterable,
31+
};
32+
33+
// We store the values in an ArrayList rather than a an
34+
// StringArrayHashMap([]const u8) because of the way the iterators (i.e., keys(),
35+
// values() and entries()) work. The FormData can contain duplicate keys, and
36+
// each iteration yields 1 key=>value pair. So, given:
37+
//
38+
// let f = new FormData();
39+
// f.append('a', '1');
40+
// f.append('a', '2');
41+
//
42+
// Then we'd expect f.keys(), f.values() and f.entries() to yield 2 results:
43+
// ['a', '1']
44+
// ['a', '2']
45+
//
46+
// This is much easier to do with an ArrayList than a HashMap, especially given
47+
// that the FormData could be mutated while iterating.
48+
// The downside is that most of the normal operations are O(N).
49+
pub const FormData = struct {
50+
entries: std.ArrayListUnmanaged(Entry),
51+
52+
pub fn constructor() FormData {
53+
return .{
54+
.entries = .empty,
55+
};
56+
}
57+
58+
pub fn _get(self: *const FormData, key: []const u8) ?[]const u8 {
59+
const result = self.find(key) orelse return null;
60+
return result.entry.value;
61+
}
62+
63+
pub fn _getAll(self: *const FormData, key: []const u8, state: *SessionState) ![][]const u8 {
64+
const arena = state.call_arena;
65+
var arr: std.ArrayListUnmanaged([]const u8) = .empty;
66+
for (self.entries.items) |entry| {
67+
if (std.mem.eql(u8, key, entry.key)) {
68+
try arr.append(arena, entry.value);
69+
}
70+
}
71+
return arr.items;
72+
}
73+
74+
pub fn _has(self: *const FormData, key: []const u8) bool {
75+
return self.find(key) != null;
76+
}
77+
78+
// TODO: value should be a string or blog
79+
// TODO: another optional parameter for the filename
80+
pub fn _set(self: *FormData, key: []const u8, value: []const u8, state: *SessionState) !void {
81+
self._delete(key);
82+
return self._append(key, value, state);
83+
}
84+
85+
// TODO: value should be a string or blog
86+
// TODO: another optional parameter for the filename
87+
pub fn _append(self: *FormData, key: []const u8, value: []const u8, state: *SessionState) !void {
88+
const arena = state.arena;
89+
return self.entries.append(arena, .{ .key = try arena.dupe(u8, key), .value = try arena.dupe(u8, value) });
90+
}
91+
92+
pub fn _delete(self: *FormData, key: []const u8) void {
93+
var i: usize = 0;
94+
while (i < self.entries.items.len) {
95+
const entry = self.entries.items[i];
96+
if (std.mem.eql(u8, key, entry.key)) {
97+
_ = self.entries.swapRemove(i);
98+
} else {
99+
i += 1;
100+
}
101+
}
102+
}
103+
104+
pub fn _keys(self: *const FormData) KeyIterable {
105+
return .{ .inner = .{ .entries = &self.entries } };
106+
}
107+
108+
pub fn _values(self: *const FormData) ValueIterable {
109+
return .{ .inner = .{ .entries = &self.entries } };
110+
}
111+
112+
pub fn _entries(self: *const FormData) EntryIterable {
113+
return .{ .inner = .{ .entries = &self.entries } };
114+
}
115+
116+
pub fn _symbol_iterator(self: *const FormData) EntryIterable {
117+
return self._entries();
118+
}
119+
120+
const FindResult = struct {
121+
index: usize,
122+
entry: Entry,
123+
};
124+
125+
fn find(self: *const FormData, key: []const u8) ?FindResult {
126+
for (self.entries.items, 0..) |entry, i| {
127+
if (std.mem.eql(u8, key, entry.key)) {
128+
return .{ .index = i, .entry = entry };
129+
}
130+
}
131+
return null;
132+
}
133+
};
134+
135+
const Entry = struct {
136+
key: []const u8,
137+
value: []const u8,
138+
};
139+
140+
const KeyIterable = iterator.Iterable(KeyIterator, "FormDataKeyIterator");
141+
const ValueIterable = iterator.Iterable(ValueIterator, "FormDataValueIterator");
142+
const EntryIterable = iterator.Iterable(EntryIterator, "FormDataEntryIterator");
143+
144+
const KeyIterator = struct {
145+
index: usize = 0,
146+
entries: *const std.ArrayListUnmanaged(Entry),
147+
148+
pub fn _next(self: *KeyIterator) ?[]const u8 {
149+
const index = self.index;
150+
if (index == self.entries.items.len) {
151+
return null;
152+
}
153+
self.index += 1;
154+
return self.entries.items[index].key;
155+
}
156+
};
157+
158+
const ValueIterator = struct {
159+
index: usize = 0,
160+
entries: *const std.ArrayListUnmanaged(Entry),
161+
162+
pub fn _next(self: *ValueIterator) ?[]const u8 {
163+
const index = self.index;
164+
if (index == self.entries.items.len) {
165+
return null;
166+
}
167+
self.index += 1;
168+
return self.entries.items[index].value;
169+
}
170+
};
171+
172+
const EntryIterator = struct {
173+
index: usize = 0,
174+
entries: *const std.ArrayListUnmanaged(Entry),
175+
176+
pub fn _next(self: *EntryIterator) ?struct { []const u8, []const u8 } {
177+
const index = self.index;
178+
if (index == self.entries.items.len) {
179+
return null;
180+
}
181+
self.index += 1;
182+
const entry = self.entries.items[index];
183+
return .{ entry.key, entry.value };
184+
}
185+
};
186+
187+
const testing = @import("../../testing.zig");
188+
test "FormData" {
189+
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
190+
defer runner.deinit();
191+
192+
try runner.testCases(&.{
193+
.{ "let f = new FormData()", null },
194+
.{ "f.get('a')", "null" },
195+
.{ "f.has('a')", "false" },
196+
.{ "f.getAll('a')", "" },
197+
.{ "f.delete('a')", "undefined" },
198+
199+
.{ "f.set('a', 1)", "undefined" },
200+
.{ "f.has('a')", "true" },
201+
.{ "f.get('a')", "1" },
202+
.{ "f.getAll('a')", "1" },
203+
204+
.{ "f.append('a', 2)", "undefined" },
205+
.{ "f.has('a')", "true" },
206+
.{ "f.get('a')", "1" },
207+
.{ "f.getAll('a')", "1,2" },
208+
209+
.{ "f.append('b', '3')", "undefined" },
210+
.{ "f.has('a')", "true" },
211+
.{ "f.get('a')", "1" },
212+
.{ "f.getAll('a')", "1,2" },
213+
.{ "f.has('b')", "true" },
214+
.{ "f.get('b')", "3" },
215+
.{ "f.getAll('b')", "3" },
216+
217+
.{ "let acc = [];", null },
218+
.{ "for (const key of f.keys()) { acc.push(key) }; acc;", "a,a,b" },
219+
220+
.{ "acc = [];", null },
221+
.{ "for (const value of f.values()) { acc.push(value) }; acc;", "1,2,3" },
222+
223+
.{ "acc = [];", null },
224+
.{ "for (const entry of f.entries()) { acc.push(entry) }; acc;", "a,1,a,2,b,3" },
225+
226+
.{ "acc = [];", null },
227+
.{ "for (const entry of f) { acc.push(entry) }; acc;", "a,1,a,2,b,3" },
228+
229+
.{ "f.delete('a')", "undefined" },
230+
.{ "f.has('a')", "false" },
231+
.{ "f.has('b')", "true" },
232+
233+
.{ "acc = [];", null },
234+
.{ "for (const key of f.keys()) { acc.push(key) }; acc;", "b" },
235+
236+
.{ "acc = [];", null },
237+
.{ "for (const value of f.values()) { acc.push(value) }; acc;", "3" },
238+
239+
.{ "acc = [];", null },
240+
.{ "for (const entry of f.entries()) { acc.push(entry) }; acc;", "b,3" },
241+
242+
.{ "acc = [];", null },
243+
.{ "for (const entry of f) { acc.push(entry) }; acc;", "b,3" },
244+
}, .{});
245+
}

0 commit comments

Comments
 (0)