Add URL struct

Combine uri + rawuri into single struct.

Try to improve ownership around URIs and URI-like things.
 - cookie & request can take *const std.Uri
   (TODO: make them aware of the new URL struct?)
 - Location (web api) should own its URL (web api URL)
 - Window should own its Location

Most of these changes result in (a) a cleaner Page and (b) not having to carry
around 2 nullable objects (URI and rawuri).
This commit is contained in:
Karl Seguin
2025-04-09 18:14:53 +08:00
parent 8b296534a4
commit be75b5b237
14 changed files with 279 additions and 241 deletions

75
src/url.zig Normal file
View File

@@ -0,0 +1,75 @@
const std = @import("std");
const Uri = std.Uri;
const Allocator = std.mem.Allocator;
const WebApiURL = @import("url/url.zig").URL;
pub const URL = struct {
uri: Uri,
raw: []const u8,
// We assume str will last as long as the URL
// In some cases, this is safe to do, because we know the URL is short lived.
// In most cases though, we assume the caller will just dupe the string URL
// into an arena
pub fn parse(str: []const u8, default_scheme: ?[]const u8) !URL {
const uri = Uri.parse(str) catch try Uri.parseAfterScheme(default_scheme orelse "https", str);
if (uri.host == null) {
return error.MissingHost;
}
std.debug.assert(uri.host.? == .percent_encoded);
return .{
.uri = uri,
.raw = str,
};
}
pub fn fromURI(arena: Allocator, uri: *const Uri) !URL {
// This is embarrassing.
var buf: std.ArrayListUnmanaged(u8) = .{};
try uri.writeToStream(.{
.scheme = true,
.authentication = true,
.authority = true,
.path = true,
.query = true,
.fragment = true,
}, buf.writer(arena));
return parse(buf.items, null);
}
// Above, in `parse, we error if a host doesn't exist
// In other words, we can't have a URL with a null host.
pub fn host(self: *const URL) []const u8 {
return self.uri.host.?.percent_encoded;
}
pub fn port(self: *const URL) ?u16 {
return self.uri.port;
}
pub fn scheme(self: *const URL) []const u8 {
return self.uri.scheme;
}
pub fn origin(self: *const URL, writer: anytype) !void {
return self.uri.writeToStream(.{ .scheme = true, .authority = true }, writer);
}
pub fn resolve(self: *const URL, arena: Allocator, url: []const u8) !URL {
var buf = try arena.alloc(u8, 1024);
const new_uri = try self.uri.resolve_inplace(url, &buf);
return fromURI(arena, &new_uri);
}
pub fn format(self: *const URL, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
return writer.writeAll(self.raw);
}
pub fn toWebApi(self: *const URL, allocator: Allocator) !WebApiURL {
return WebApiURL.init(allocator, self.uri);
}
};