1type Case = {
2 input: string;
3 base: string | null;
4 href: string;
5 origin: string;
6 protocol: string;
7 username: string;
8 password: string;
9 host: string;
10 hostname: string;
11 port: string;
12 pathname: string;
13 search: string;
14 hash: string;
15 failure: undefined;
16};
17type CaseFail = {
18 input: string;
19 base: string | null;
20 failure: true;
21};
22type CaseIDNA = {
23 input: string;
24 output: string | null;
25};
26const casesraw = (await fetch("https://github.com/web-platform-tests/wpt/raw/master/url/resources/urltestdata.json").then((x) => x.json())) as (string | Case | CaseFail)[];
27const casesidna = (await fetch("https://github.com/web-platform-tests/wpt/raw/master/url/resources/IdnaTestV2.json").then((x) => x.json())) as (string | CaseIDNA)[];
28
29const cases = casesraw.filter((v) => typeof v !== "string");
30const f = Bun.file(process.argv[2]!);
31const w = f.writer();
32const encoder = new TextEncoder();
33
34w.write(`const std = @import("std");\n`);
35w.write(`const url = @import("url");\n`);
36w.write(`const expect = @import("expect").expect;\n`);
37w.write(`\n`);
38w.write(`// zig fmt: off\n`);
39
40function stringEscape(s?: string) {
41 if (!s) return "";
42 return Array.from(encoder.encode(s.replace(/\\u([0-9A-F]{4})/g, (s) => String.fromCodePoint(parseInt(s.slice(2), 16)))))
43 .map((x) => {
44 if (x === 0x09) return "\\t";
45 if (x === 0x0a) return "\\n";
46 if (x === 0x0d) return "\\r";
47 if (x === 0x20) return " ";
48 if (x === 0x21) return "!";
49 if (x === 0x22) return `\\"`;
50 if (x >= 0x23 && x <= 0x26) return String.fromCodePoint(x);
51 if (x === 0x27) return "'";
52 if (x >= 0x28 && x <= 0x5b) return String.fromCodePoint(x);
53 if (x === 0x5c) return "\\\\";
54 if (x >= 0x5d && x <= 0x7e) return String.fromCodePoint(x);
55 return `\\x${x.toString(16).padStart(2, "0")}`;
56 })
57 .join("");
58}
59const E = stringEscape;
60
61w.write(`
62pub fn parseFail(input: []const u8, base: ?[]const u8) !void {
63 const allocator = std.testing.allocator;
64 const u = url.URL.parse(allocator, input, base) catch |err| switch (err) {
65 error.InvalidURL => return,
66 error.OutOfMemory => return error.OutOfMemory,
67 };
68 defer allocator.free(u.href);
69 return error.FailZigTest;
70}
71
72pub fn parsePass(input: []const u8, base: ?[]const u8, href: []const u8, origin: []const u8, protocol: []const u8, username: []const u8, password: []const u8, host: []const u8, hostname: []const u8, port: []const u8, pathname: []const u8, search: []const u8, hash: []const u8) !void {
73 const allocator = std.testing.allocator;
74 const u = try url.URL.parse(allocator, input, base);
75 defer allocator.free(u.href);
76 _ = origin;
77 try expect(u.protocol).toEqualString(protocol);
78 try expect(u.username).toEqualString(username);
79 try expect(u.password).toEqualString(password);
80 try expect(u.hostname).toEqualString(hostname);
81 try expect(u.port).toEqualString(port);
82 try expect(u.host).toEqualString(host);
83 try expect(u.pathname).toEqualString(pathname);
84 try expect(u.search).toEqualString(search);
85 try expect(u.hash).toEqualString(hash);
86 try expect(u.href).toEqualString(href);
87}
88
89pub fn parseIDNAFail(comptime input: []const u8) !void {
90 const allocator = std.testing.allocator;
91 const u = url.URL.parse(allocator, "https://" ++ input ++ "/x", null) catch |err| switch (err) {
92 error.InvalidURL => return,
93 error.OutOfMemory => return error.OutOfMemory,
94 };
95 defer allocator.free(u.href);
96 return error.FailZigTest;
97}
98
99pub fn parseIDNAPass(comptime input: []const u8, comptime output: []const u8) !void {
100 // new URL('https://{idnaTest.input}/x');
101 const allocator = std.testing.allocator;
102 const u = try url.URL.parse(allocator, "https://" ++ input ++ "/x", null);
103 defer allocator.free(u.href);
104 try expect(u.hostname).toEqualString(output);
105 try expect(u.host).toEqualString(output);
106 try expect(u.pathname).toEqualString("/x");
107 // assert_equals(url.href, 'https://{idnaTest.output}/x');
108}
109`);
110
111w.write(`\n`);
112for (const c of cases) {
113 if (c.base !== null) continue;
114 if (!c.failure) continue;
115 w.write(`test { try parseFail("${stringEscape(c.input)}", null); }\n`);
116}
117
118w.write(`\n`);
119for (const c of cases) {
120 if (c.base === null) continue;
121 if (!c.failure) continue;
122 w.write(`test { try parseFail("${stringEscape(c.input)}", "${stringEscape(c.base!)}"); }\n`);
123}
124
125w.write(`\n`);
126for (const c of cases) {
127 if (c.base !== null) continue;
128 if (c.failure) continue;
129 w.write(`test { try parsePass("${E(c.input)}", null, "${E(c.href)}", "${E(c.origin)}", "${E(c.protocol)}", "${E(c.username)}", "${E(c.password)}", "${E(c.host)}", "${E(c.hostname)}", "${E(c.port)}", "${E(c.pathname)}", "${E(c.search)}", "${E(c.hash)}"); }\n`);
130}
131
132w.write(`\n`);
133for (const c of cases) {
134 if (c.base === null) continue;
135 if (c.failure) continue;
136 w.write(`test { try parsePass("${E(c.input)}", "${E(c.base!)}", "${E(c.href)}", "${E(c.origin)}", "${E(c.protocol)}", "${E(c.username)}", "${E(c.password)}", "${E(c.host)}", "${E(c.hostname)}", "${E(c.port)}", "${E(c.pathname)}", "${E(c.search)}", "${E(c.hash)}"); }\n`);
137}
138
139w.write(`\n`);
140for (const c of casesidna.filter((v) => typeof v !== "string")) {
141 if (c.input.length === 0) continue;
142 if (c.output !== null) continue;
143 w.write(`test { try parseIDNAFail("${stringEscape(c.input)}"); }\n`);
144}
145
146w.write(`\n`);
147for (const c of casesidna.filter((v) => typeof v !== "string")) {
148 if (c.input.length === 0) continue;
149 if (c.output === null) continue;
150 w.write(`test { try parseIDNAPass("${stringEscape(c.input)}", "${stringEscape(c.output)}"); }\n`);
151}
152
153w.flush();