expand Request/Response interfaces

This commit is contained in:
Muki Kiboigo
2025-09-02 23:23:14 -07:00
parent 8dcba37672
commit 6225cb38ae
5 changed files with 143 additions and 29 deletions

View File

@@ -23,8 +23,8 @@ const URL = @import("../../url.zig").URL;
const Page = @import("../page.zig").Page;
const Response = @import("./Response.zig");
const Http = @import("../../http/Http.zig");
const ReadableStream = @import("../streams/ReadableStream.zig");
const v8 = @import("v8");
const Env = @import("../env.zig").Env;
@@ -37,12 +37,49 @@ pub const RequestInput = union(enum) {
request: *Request,
};
pub const RequestCache = enum {
default,
@"no-store",
reload,
@"no-cache",
@"force-cache",
@"only-if-cached",
pub fn fromString(str: []const u8) ?RequestCache {
for (std.enums.values(RequestCache)) |cache| {
if (std.ascii.eqlIgnoreCase(str, @tagName(cache))) {
return cache;
}
} else {
return null;
}
}
};
pub const RequestCredentials = enum {
omit,
@"same-origin",
include,
pub fn fromString(str: []const u8) ?RequestCredentials {
for (std.enums.values(RequestCredentials)) |cache| {
if (std.ascii.eqlIgnoreCase(str, @tagName(cache))) {
return cache;
}
} else {
return null;
}
}
};
// https://developer.mozilla.org/en-US/docs/Web/API/RequestInit
pub const RequestInit = struct {
method: ?[]const u8 = null,
body: ?[]const u8 = null,
integrity: ?[]const u8 = null,
cache: ?[]const u8 = null,
credentials: ?[]const u8 = null,
headers: ?HeadersInit = null,
integrity: ?[]const u8 = null,
method: ?[]const u8 = null,
};
// https://developer.mozilla.org/en-US/docs/Web/API/Request/Request
@@ -50,6 +87,8 @@ const Request = @This();
method: Http.Method,
url: [:0]const u8,
cache: RequestCache,
credentials: RequestCredentials,
headers: Headers,
body: ?[]const u8,
body_used: bool = false,
@@ -68,6 +107,12 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
},
};
const body = if (options.body) |body| try arena.dupe(u8, body) else null;
const cache = (if (options.cache) |cache| RequestCache.fromString(cache) else null) orelse RequestCache.default;
const credentials = (if (options.credentials) |creds| RequestCredentials.fromString(creds) else null) orelse RequestCredentials.@"same-origin";
const integrity = if (options.integrity) |integ| try arena.dupe(u8, integ) else "";
const headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else Headers{};
const method: Http.Method = blk: {
if (options.method) |given_method| {
for (std.enums.values(Http.Method)) |method| {
@@ -82,27 +127,33 @@ pub fn constructor(input: RequestInput, _options: ?RequestInit, page: *Page) !Re
}
};
const body = if (options.body) |body| try arena.dupe(u8, body) else null;
const integrity = if (options.integrity) |integ| try arena.dupe(u8, integ) else "";
const headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else Headers{};
return .{
.method = method,
.url = url,
.cache = cache,
.credentials = credentials,
.headers = headers,
.body = body,
.integrity = integrity,
};
}
// pub fn get_body(self: *const Request) ?[]const u8 {
// return self.body;
// }
pub fn get_body(self: *const Request, page: *Page) !?*ReadableStream {
if (self.body) |body| {
const stream = try ReadableStream.constructor(null, null, page);
try stream.queue.append(page.arena, body);
return stream;
} else return null;
}
pub fn get_bodyUsed(self: *const Request) bool {
return self.body_used;
}
pub fn get_cache(self: *const Request) RequestCache {
return self.cache;
}
pub fn get_headers(self: *Request) *Headers {
return &self.headers;
}
@@ -133,6 +184,8 @@ pub fn _clone(self: *Request, page: *Page) !Request {
return Request{
.body = if (self.body) |body| try arena.dupe(u8, body) else null,
.body_used = self.body_used,
.cache = self.cache,
.credentials = self.credentials,
.headers = try self.headers.clone(arena),
.method = self.method,
.integrity = try arena.dupe(u8, self.integrity),

View File

@@ -24,6 +24,11 @@ const v8 = @import("v8");
const HttpClient = @import("../../http/Client.zig");
const Http = @import("../../http/Http.zig");
const URL = @import("../../url.zig").URL;
const ReadableStream = @import("../streams/ReadableStream.zig");
const Headers = @import("Headers.zig");
const HeadersInit = @import("Headers.zig").HeadersInit;
const Env = @import("../env.zig").Env;
const Mime = @import("../mime.zig").Mime;
const Page = @import("../page.zig").Page;
@@ -32,26 +37,28 @@ const Page = @import("../page.zig").Page;
const Response = @This();
status: u16 = 0,
headers: []const []const u8,
headers: Headers = .{},
mime: ?Mime = null,
body: []const u8,
url: []const u8 = "",
body: []const u8 = "",
body_used: bool = false,
redirected: bool = false,
const ResponseInput = union(enum) {
const ResponseBody = union(enum) {
string: []const u8,
};
const ResponseOptions = struct {
status: u16 = 200,
statusText: []const u8 = "",
// List of header pairs.
headers: []const []const u8 = &[][].{},
headers: ?HeadersInit = null,
};
pub fn constructor(_input: ?ResponseInput, page: *Page) !Response {
pub fn constructor(_input: ?ResponseBody, _options: ?ResponseOptions, page: *Page) !Response {
const arena = page.arena;
const options: ResponseOptions = _options orelse .{};
const body = blk: {
if (_input) |input| {
switch (input) {
@@ -64,20 +71,32 @@ pub fn constructor(_input: ?ResponseInput, page: *Page) !Response {
}
};
const headers: Headers = if (options.headers) |hdrs| try Headers.constructor(hdrs, page) else .{};
return .{
.body = body,
.headers = &[_][]const u8{},
.headers = headers,
};
}
pub fn get_ok(self: *const Response) bool {
return self.status >= 200 and self.status <= 299;
pub fn get_body(self: *const Response, page: *Page) !*ReadableStream {
const stream = try ReadableStream.constructor(null, null, page);
try stream.queue.append(page.arena, self.body);
return stream;
}
pub fn get_bodyUsed(self: *const Response) bool {
return self.body_used;
}
pub fn get_headers(self: *Response) *Headers {
return &self.headers;
}
pub fn get_ok(self: *const Response) bool {
return self.status >= 200 and self.status <= 299;
}
pub fn get_redirected(self: *const Response) bool {
return self.redirected;
}
@@ -86,6 +105,28 @@ pub fn get_status(self: *const Response) u16 {
return self.status;
}
pub fn get_url(self: *const Response) []const u8 {
return self.url;
}
pub fn _clone(self: *const Response, page: *Page) !Response {
if (self.body_used) {
return error.TypeError;
}
const arena = page.arena;
return Response{
.body = try arena.dupe(u8, self.body),
.body_used = self.body_used,
.mime = if (self.mime) |mime| try mime.clone(arena) else null,
.headers = try self.headers.clone(arena),
.redirected = self.redirected,
.status = self.status,
.url = try arena.dupe(u8, self.url),
};
}
pub fn _bytes(self: *Response, page: *Page) !Env.Promise {
if (self.body_used) {
return error.TypeError;

View File

@@ -290,6 +290,22 @@ pub const Mime = struct {
fn trimRight(s: []const u8) []const u8 {
return std.mem.trimRight(u8, s, &std.ascii.whitespace);
}
pub fn clone(self: *const Mime, allocator: Allocator) !Mime {
return Mime{
.content_type = blk: {
switch (self.content_type) {
.other => |data| break :blk ContentType{ .other = .{
.type = try allocator.dupe(u8, data.type),
.sub_type = try allocator.dupe(u8, data.sub_type),
} },
else => break :blk self.content_type,
}
},
.params = try allocator.dupe(u8, self.params),
.charset = if (self.charset) |charset| try allocator.dupeZ(u8, charset) else null,
};
}
};
const testing = @import("../testing.zig");

View File

@@ -34,11 +34,10 @@ const State = union(enum) {
};
// This promise resolves when a stream is canceled.
cancel_resolver: Env.PromiseResolver,
cancel_resolver: v8.Persistent(v8.PromiseResolver),
locked: bool = false,
state: State = .readable,
// A queue would be ideal here but I don't want to pay the cost of the priority operation.
queue: std.ArrayListUnmanaged([]const u8) = .empty,
const UnderlyingSource = struct {
@@ -56,10 +55,10 @@ const QueueingStrategy = struct {
pub fn constructor(underlying: ?UnderlyingSource, strategy: ?QueueingStrategy, page: *Page) !*ReadableStream {
_ = strategy;
const cancel_resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = v8.PromiseResolver.init(page.main_context.v8_context),
};
const cancel_resolver = v8.Persistent(v8.PromiseResolver).init(
page.main_context.isolate,
v8.PromiseResolver.init(page.main_context.v8_context),
);
const stream = try page.arena.create(ReadableStream);
stream.* = ReadableStream{ .cancel_resolver = cancel_resolver };
@@ -76,8 +75,13 @@ pub fn constructor(underlying: ?UnderlyingSource, strategy: ?QueueingStrategy, p
return stream;
}
pub fn _cancel(self: *const ReadableStream) Env.Promise {
return self.cancel_resolver.promise();
pub fn _cancel(self: *const ReadableStream, page: *Page) Env.Promise {
const resolver = Env.PromiseResolver{
.js_context = page.main_context,
.resolver = self.cancel_resolver.castToPromiseResolver(),
};
return resolver.promise();
}
pub fn get_locked(self: *const ReadableStream) bool {

View File

@@ -47,8 +47,8 @@ pub fn get_closed(self: *const ReadableStreamDefaultReader) Env.Promise {
return self.closed_resolver.promise();
}
pub fn _cancel(self: *ReadableStreamDefaultReader) Env.Promise {
return self.stream._cancel();
pub fn _cancel(self: *ReadableStreamDefaultReader, page: *Page) Env.Promise {
return self.stream._cancel(page);
}
pub const ReadableStreamReadResult = struct {