Skip to content

Commit 8a2efde

Browse files
authored
Merge pull request #1069 from lightpanda-io/response-gettype
Adds `Response.type`
2 parents bbc2fbf + 2596232 commit 8a2efde

File tree

5 files changed

+137
-46
lines changed

5 files changed

+137
-46
lines changed

src/browser/fetch/Request.zig

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,27 @@ pub const RequestCredentials = enum {
8080
}
8181
};
8282

83+
pub const RequestMode = enum {
84+
cors,
85+
@"no-cors",
86+
@"same-origin",
87+
navigate,
88+
89+
pub fn fromString(str: []const u8) ?RequestMode {
90+
for (std.enums.values(RequestMode)) |cache| {
91+
if (std.ascii.eqlIgnoreCase(str, @tagName(cache))) {
92+
return cache;
93+
}
94+
} else {
95+
return null;
96+
}
97+
}
98+
99+
pub fn toString(self: RequestMode) []const u8 {
100+
return @tagName(self);
101+
}
102+
};
103+
83104
// https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
84105
pub const RequestInit = struct {
85106
body: ?[]const u8 = null,
@@ -88,6 +109,7 @@ pub const RequestInit = struct {
88109
headers: ?HeadersInit = null,
89110
integrity: ?[]const u8 = null,
90111
method: ?[]const u8 = null,
112+
mode: ?[]const u8 = null,
91113
};
92114

93115
// https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
@@ -97,6 +119,8 @@ method: Http.Method,
97119
url: [:0]const u8,
98120
cache: RequestCache,
99121
credentials: RequestCredentials,
122+
// no-cors is default is not built with constructor.
123+
mode: RequestMode = .@"no-cors",
100124
headers: Headers,
101125
body: ?[]const u8,
102126
body_used: bool = false,
@@ -115,11 +139,11 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
115139
},
116140
};
117141

118-
const body = if (options.body) |body| try arena.dupe(u8, body) else null;
119142
const cache = (if (options.cache) |cache| RequestCache.fromString(cache) else null) orelse RequestCache.default;
120143
const credentials = (if (options.credentials) |creds| RequestCredentials.fromString(creds) else null) orelse RequestCredentials.@"same-origin";
121144
const integrity = if (options.integrity) |integ| try arena.dupe(u8, integ) else "";
122145
const headers: Headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else .{};
146+
const mode = (if (options.mode) |mode| RequestMode.fromString(mode) else null) orelse RequestMode.cors;
123147

124148
const method: Http.Method = blk: {
125149
if (options.method) |given_method| {
@@ -135,11 +159,19 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
135159
}
136160
};
137161

162+
// Can't have a body on .GET or .HEAD.
163+
const body: ?[]const u8 = blk: {
164+
if (method == .GET or method == .HEAD) {
165+
break :blk null;
166+
} else break :blk if (options.body) |body| try arena.dupe(u8, body) else null;
167+
};
168+
138169
return .{
139170
.method = method,
140171
.url = url,
141172
.cache = cache,
142173
.credentials = credentials,
174+
.mode = mode,
143175
.headers = headers,
144176
.body = body,
145177
.integrity = integrity,
@@ -181,6 +213,10 @@ pub fn get_method(self: *const Request) []const u8 {
181213
return @tagName(self.method);
182214
}
183215

216+
pub fn get_mode(self: *const Request) RequestMode {
217+
return self.mode;
218+
}
219+
184220
pub fn get_url(self: *const Request) []const u8 {
185221
return self.url;
186222
}
@@ -210,10 +246,7 @@ pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
210246
return error.TypeError;
211247
}
212248

213-
const resolver = Env.PromiseResolver{
214-
.js_context = page.main_context,
215-
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
216-
};
249+
const resolver = page.main_context.createPromiseResolver();
217250

218251
try resolver.resolve(self.body);
219252
self.body_used = true;
@@ -225,22 +258,24 @@ pub fn _json(self: *Response, page: *Page) !Env.Promise {
225258
return error.TypeError;
226259
}
227260

228-
const resolver = Env.PromiseResolver{
229-
.js_context = page.main_context,
230-
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
231-
};
261+
const resolver = page.main_context.createPromiseResolver();
232262

233-
const p = std.json.parseFromSliceLeaky(
234-
std.json.Value,
235-
page.call_arena,
236-
self.body,
237-
.{},
238-
) catch |e| {
239-
log.info(.browser, "invalid json", .{ .err = e, .source = "Request" });
240-
return error.SyntaxError;
241-
};
263+
if (self.body) |body| {
264+
const p = std.json.parseFromSliceLeaky(
265+
std.json.Value,
266+
page.call_arena,
267+
body,
268+
.{},
269+
) catch |e| {
270+
log.info(.browser, "invalid json", .{ .err = e, .source = "Request" });
271+
return error.SyntaxError;
272+
};
273+
274+
try resolver.resolve(p);
275+
} else {
276+
try resolver.resolve(null);
277+
}
242278

243-
try resolver.resolve(p);
244279
self.body_used = true;
245280
return resolver.promise();
246281
}
@@ -250,10 +285,7 @@ pub fn _text(self: *Response, page: *Page) !Env.Promise {
250285
return error.TypeError;
251286
}
252287

253-
const resolver = Env.PromiseResolver{
254-
.js_context = page.main_context,
255-
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
256-
};
288+
const resolver = page.main_context.createPromiseResolver();
257289

258290
try resolver.resolve(self.body);
259291
self.body_used = true;

src/browser/fetch/Response.zig

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ status_text: []const u8 = "",
4141
headers: Headers,
4242
mime: ?Mime = null,
4343
url: []const u8 = "",
44-
body: []const u8 = "",
44+
body: ?[]const u8 = null,
4545
body_used: bool = false,
4646
redirected: bool = false,
47+
type: ResponseType = .basic,
4748

4849
const ResponseBody = union(enum) {
4950
string: []const u8,
@@ -55,6 +56,28 @@ const ResponseOptions = struct {
5556
headers: ?HeadersInit = null,
5657
};
5758

59+
pub const ResponseType = enum {
60+
basic,
61+
cors,
62+
@"error",
63+
@"opaque",
64+
opaqueredirect,
65+
66+
pub fn fromString(str: []const u8) ?ResponseType {
67+
for (std.enums.values(ResponseType)) |cache| {
68+
if (std.ascii.eqlIgnoreCase(str, @tagName(cache))) {
69+
return cache;
70+
}
71+
} else {
72+
return null;
73+
}
74+
}
75+
76+
pub fn toString(self: ResponseType) []const u8 {
77+
return @tagName(self);
78+
}
79+
};
80+
5881
pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Page) !Response {
5982
const arena = page.arena;
6083

@@ -68,7 +91,7 @@ pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Pag
6891
},
6992
}
7093
} else {
71-
break :blk "";
94+
break :blk null;
7295
}
7396
};
7497

@@ -85,7 +108,9 @@ pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Pag
85108

86109
pub fn get_body(self: *const Response, page: *Page) !*ReadableStream {
87110
const stream = try ReadableStream.constructor(null, null, page);
88-
try stream.queue.append(page.arena, self.body);
111+
if (self.body) |body| {
112+
try stream.queue.append(page.arena, body);
113+
}
89114
return stream;
90115
}
91116

@@ -113,6 +138,10 @@ pub fn get_statusText(self: *const Response) []const u8 {
113138
return self.status_text;
114139
}
115140

141+
pub fn get_type(self: *const Response) ResponseType {
142+
return self.type;
143+
}
144+
116145
pub fn get_url(self: *const Response) []const u8 {
117146
return self.url;
118147
}
@@ -132,6 +161,7 @@ pub fn _clone(self: *const Response) !Response {
132161
.redirected = self.redirected,
133162
.status = self.status,
134163
.url = self.url,
164+
.type = self.type,
135165
};
136166
}
137167

@@ -155,22 +185,24 @@ pub fn _json(self: *Response, page: *Page) !Env.Promise {
155185
return error.TypeError;
156186
}
157187

158-
const resolver = Env.PromiseResolver{
159-
.js_context = page.main_context,
160-
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
161-
};
162-
163-
const p = std.json.parseFromSliceLeaky(
164-
std.json.Value,
165-
page.call_arena,
166-
self.body,
167-
.{},
168-
) catch |e| {
169-
log.info(.browser, "invalid json", .{ .err = e, .source = "Response" });
170-
return error.SyntaxError;
171-
};
188+
const resolver = page.main_context.createPromiseResolver();
189+
190+
if (self.body) |body| {
191+
const p = std.json.parseFromSliceLeaky(
192+
std.json.Value,
193+
page.call_arena,
194+
body,
195+
.{},
196+
) catch |e| {
197+
log.info(.browser, "invalid json", .{ .err = e, .source = "Response" });
198+
return error.SyntaxError;
199+
};
200+
201+
try resolver.resolve(p);
202+
} else {
203+
try resolver.resolve(null);
204+
}
172205

173-
try resolver.resolve(p);
174206
self.body_used = true;
175207
return resolver.promise();
176208
}
@@ -180,10 +212,7 @@ pub fn _text(self: *Response, page: *Page) !Env.Promise {
180212
return error.TypeError;
181213
}
182214

183-
const resolver = Env.PromiseResolver{
184-
.js_context = page.main_context,
185-
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
186-
};
215+
const resolver = page.main_context.createPromiseResolver();
187216

188217
try resolver.resolve(self.body);
189218
self.body_used = true;

src/browser/fetch/fetch.zig

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub const FetchContext = struct {
5353
headers: std.ArrayListUnmanaged([]const u8) = .empty,
5454
status: u16 = 0,
5555
mime: ?Mime = null,
56+
mode: Request.RequestMode,
5657
transfer: ?*HttpClient.Transfer = null,
5758

5859
/// This effectively takes ownership of the FetchContext.
@@ -62,6 +63,19 @@ pub const FetchContext = struct {
6263
pub fn toResponse(self: *const FetchContext) !Response {
6364
var headers: Headers = .{};
6465

66+
// If the mode is "no-cors", we need to return this opaque/stripped Response.
67+
// https://developer.mozilla.org/en-US/docs/Web/API/Response/type
68+
if (self.mode == .@"no-cors") {
69+
return Response{
70+
.status = 0,
71+
.headers = headers,
72+
.mime = self.mime,
73+
.body = null,
74+
.url = self.url,
75+
.type = .@"opaque",
76+
};
77+
}
78+
6579
// convert into Headers
6680
for (self.headers.items) |hdr| {
6781
var iter = std.mem.splitScalar(u8, hdr, ':');
@@ -70,12 +84,25 @@ pub const FetchContext = struct {
7084
try headers.append(name, value, self.arena);
7185
}
7286

87+
const resp_type: Response.ResponseType = blk: {
88+
if (std.mem.startsWith(u8, self.url, "data:")) {
89+
break :blk .basic;
90+
}
91+
92+
break :blk switch (self.mode) {
93+
.cors => .cors,
94+
.@"same-origin", .navigate => .basic,
95+
.@"no-cors" => unreachable,
96+
};
97+
};
98+
7399
return Response{
74100
.status = self.status,
75101
.headers = headers,
76102
.mime = self.mime,
77103
.body = self.body.items,
78104
.url = self.url,
105+
.type = resp_type,
79106
};
80107
}
81108
};
@@ -110,6 +137,7 @@ pub fn fetch(input: RequestInput, options: ?RequestInit, page: *Page) !Env.Promi
110137
.promise_resolver = resolver,
111138
.method = req.method,
112139
.url = req.url,
140+
.mode = req.mode,
113141
};
114142

115143
try page.http_client.request(.{

src/runtime/js.zig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2843,7 +2843,7 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
28432843

28442844
const T = @TypeOf(value);
28452845
switch (@typeInfo(T)) {
2846-
.void, .bool, .int, .comptime_int, .float, .comptime_float, .@"enum" => {
2846+
.void, .bool, .int, .comptime_int, .float, .comptime_float, .@"enum", .null => {
28472847
// Need to do this to keep the compiler happy
28482848
// simpleZigValueToJs handles all of these cases.
28492849
unreachable;
@@ -3634,6 +3634,7 @@ fn jsUnsignedIntToZig(comptime T: type, max: comptime_int, maybe: u32) !T {
36343634
fn simpleZigValueToJs(isolate: v8.Isolate, value: anytype, comptime fail: bool) if (fail) v8.Value else ?v8.Value {
36353635
switch (@typeInfo(@TypeOf(value))) {
36363636
.void => return v8.initUndefined(isolate).toValue(),
3637+
.null => return v8.initNull(isolate).toValue(),
36373638
.bool => return v8.getValue(if (value) v8.initTrue(isolate) else v8.initFalse(isolate)),
36383639
.int => |n| switch (n.signedness) {
36393640
.signed => {

src/tests/fetch/response.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
testing.expectEqual("no-cache", response2.headers.get("cache-control"));
2626

2727
let response3 = new Response("Created", { status: 201, statusText: "Created" });
28+
testing.expectEqual("basic", response3.type);
2829
testing.expectEqual(201, response3.status);
2930
testing.expectEqual("Created", response3.statusText);
3031
testing.expectEqual(true, response3.ok);

0 commit comments

Comments
 (0)