Skip to content

Commit

Permalink
Adding parseUrl method
Browse files Browse the repository at this point in the history
  • Loading branch information
dravenk committed Aug 19, 2024
1 parent a9c56b1 commit c6fd14d
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 43 deletions.
122 changes: 98 additions & 24 deletions src/url.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,45 +9,98 @@ pub const URL = @This();

allocator: std.mem.Allocator = std.heap.page_allocator,
uri: Uri = undefined,
querymap: StringHashMap([]const u8) = StringHashMap([]const u8).init(std.heap.page_allocator),
scheme: ?[]const u8 = undefined,
host: ?[]const u8 = undefined,
path: []const u8 = "/",
fragment: ?[]const u8 = undefined,
query: ?[]const u8 = undefined,
querymap: ?StringHashMap([]const u8) = StringHashMap([]const u8).init(std.heap.page_allocator),

// https://developer.mozilla.org/en-US/docs/Learn/Common_questions/Web_mechanics/What_is_a_URL

pub fn init(self: URL) URL {
return .{
.allocator = self.allocator,
.uri = self.uri,
.querymap = self.querymap,
// .uri = self.uri,
// .querymap = self.querymap,
};
}

pub fn parse(self: *URL, text: []const u8) ParseError!*URL {
self.uri = try Uri.parse(text);
self.querymap = self.queryMap();
return self;
}
const SliceReader = struct {
const Self = @This();

pub fn query(self: *URL) []const u8 {
return self.uri.query.?.percent_encoded;
}
slice: []const u8,
offset: usize = 0,
fn peek(self: Self) ?u8 {
if (self.offset >= self.slice.len)
return null;
return self.slice[self.offset];
}
fn get(self: *Self) ?u8 {
if (self.offset >= self.slice.len)
return null;
const c = self.slice[self.offset];
self.offset += 1;
return c;
}

pub fn fragment(self: *URL) []const u8 {
return self.uri.fragment.?.percent_encoded;
}
fn readUntil(self: *Self, comptime predicate: fn (u8) bool) []const u8 {
const start = self.offset;
var end = start;
while (end < self.slice.len and !predicate(self.slice[end])) {
end += 1;
}
self.offset = end;
return self.slice[start..end];
}

pub fn scheme(self: *URL) []const u8 {
return self.uri.scheme;
}
fn readUntilEof(self: *Self) []const u8 {
const start = self.offset;
self.offset = self.slice.len;
return self.slice[start..];
}
};

pub fn parseUrl(self: *URL, text: []const u8) ParseError!*URL {
var reader = SliceReader{ .slice = text };
self.path = reader.readUntil(isPathSeparator);
if ((reader.peek() orelse 0) == '?') { // query part
std.debug.assert(reader.get().? == '?');
self.query = reader.readUntil(isQuerySeparator);
}

if ((reader.peek() orelse 0) == '#') { // fragment part
std.debug.assert(reader.get().? == '#');
self.fragment = reader.readUntilEof();
}

pub fn host(self: *URL) []const u8 {
return self.uri.host.?.percent_encoded;
return self;
}

pub fn path(self: *URL) []const u8 {
return self.uri.path.percent_encoded;
fn uriToUrl(self: *URL, uri: Uri) void {
self.uri = uri;
self.scheme = uri.scheme;
if (uri.host != null) {
if (!uri.host.?.isEmpty()) {
self.host = uri.host.?.percent_encoded;
}
}
self.path = uri.path.percent_encoded;

if (uri.query != null) {
self.query = @constCast(uri.query.?.percent_encoded);
self.querymap = parseQuery(@constCast(uri.query.?.percent_encoded));
}
if (uri.fragment != null) {
self.fragment = @constCast(uri.fragment.?.percent_encoded);
}
return;
}

pub fn queryMap(self: *URL) StringHashMap([]const u8) {
self.querymap = parseQuery(self.query());
return self.querymap;
pub fn parseUri(self: *URL, text: []const u8) ParseError!*URL {
const uri = try Uri.parse(text);
self.uriToUrl(uri);
return self;
}

pub fn parseQuery(uri_query: []const u8) StringHashMap([]const u8) {
Expand All @@ -74,3 +127,24 @@ pub fn parseQuery(uri_query: []const u8) StringHashMap([]const u8) {
}
return querymap;
}

fn isAuthoritySeparator(c: u8) bool {
return switch (c) {
'/', '?', '#' => true,
else => false,
};
}

fn isPathSeparator(c: u8) bool {
return switch (c) {
'?', '#' => true,
else => false,
};
}

fn isQuerySeparator(c: u8) bool {
return switch (c) {
'#' => true,
else => false,
};
}
67 changes: 48 additions & 19 deletions src/url_test.zig
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ const ParseError = std.Uri.ParseError;
const StringHashMap = @import("std").StringHashMap;
const URL = @import("url.zig");

test "URL" {
test "parseUri 1" {
var url = URL.init(.{});
const text = "http://example.com/path?query=1&query2=2";
const result = url.parse(text) catch return;
try testing.expectEqualStrings("http", result.scheme());
const result = url.parseUri(text) catch return;
try testing.expectEqualStrings("http", result.scheme.?);
try testing.expectEqualStrings(
"example.com",
result.host(),
result.host.?,
);
try testing.expectEqualStrings(
"/path",
result.path(),
result.path,
);
try testing.expectEqualStrings("query=1&query2=2", result.query());
try testing.expectEqualStrings("query=1&query2=2", result.query.?);

var querymap = result.queryMap();
var querymap = result.querymap.?;
defer querymap.deinit();
try testing.expectEqualStrings("1", querymap.get("query").?);
try testing.expectEqualStrings("2", querymap.get("query2").?);

Expand All @@ -30,28 +31,56 @@ test "URL" {
}

// query=1&query2=2
var qm = URL.parseQuery(result.query());

var qm = URL.parseQuery(result.query.?);
defer qm.deinit();
try testing.expectEqualStrings("1", qm.get("query").?);
try testing.expectEqualStrings("2", qm.get("query2").?);

if (qm.get("query3") != null) {
try testing.expect(false);
}

const uri = "foo://example.com:8042/over/there?name=ferret#nose";
var url2 = URL.init(.{});
const result2 = url2.parse(uri) catch return;
try testing.expectEqualStrings("foo", result2.scheme());
}
test "parseUri 2" {
const text = "foo://example.com:8042/over/there?name=ferret#nose";
var url = URL.init(.{});
const result = url.parseUri(text) catch return;
try testing.expectEqualStrings("foo", result.scheme.?);
try testing.expectEqualStrings(
"example.com",
result2.host(),
result.host.?,
);
try testing.expectEqualStrings(
"/over/there",
result2.path(),
result.path,
);
try testing.expectEqualStrings("name=ferret", result2.query());
var qm2 = url2.queryMap();
try testing.expectEqualStrings("ferret", qm2.get("name").?);
try testing.expectEqualStrings("nose", result2.fragment());
try testing.expectEqualStrings("name=ferret", result.query.?);
var qm = url.querymap.?;
defer qm.deinit();
try testing.expectEqualStrings("ferret", qm.get("name").?);
try testing.expectEqualStrings("nose", result.fragment.?);
}

test "url target" {
const text = "/path?query=1&query2=2";
const result = Uri.parseAfterScheme("http", text) catch return;
try testing.expectEqualStrings("/path", result.path.percent_encoded);
try testing.expectEqualStrings("query=1&query2=2", result.query.?.percent_encoded);
}

test "url parse" {
const text = "/path?query=1&query2=2";
var url = URL.init(.{});
const result = url.parseUrl(text) catch return;
try testing.expectEqualStrings("/path", result.path);
try testing.expectEqualStrings("query=1&query2=2", result.query.?);
}

test "RFC example 1" {
const text = "/over/there?name=ferret#nose";
var url = URL.init(.{});
const result = url.parseUrl(text) catch return;
try testing.expectEqualStrings("/over/there", result.path);
try testing.expectEqualStrings("name=ferret", result.query.?);
try testing.expectEqualStrings("nose", result.fragment.?);
}

0 comments on commit c6fd14d

Please sign in to comment.