Refactoring

This commit is contained in:
jaina heartles 2022-12-01 20:41:52 -08:00
parent b2093128de
commit 16c574bdd6
8 changed files with 92 additions and 146 deletions

View File

@ -1,8 +1,8 @@
const std = @import("std");
const request = @import("./request.zig");
const server = @import("./server.zig");
pub const urlencode = @import("./urlencode.zig");
pub const socket = @import("./socket.zig");
@ -15,7 +15,6 @@ pub const Handler = server.Handler;
pub const Server = server.Server;
pub const middleware = @import("./middleware.zig");
pub const queryStringify = @import("./query.zig").queryStringify;
pub const Fields = @import("./headers.zig").Fields;

View File

@ -14,9 +14,9 @@
/// Terminal middlewares that are not implemented using other middlewares should
/// only accept a `void` value for `next_handler`.
const std = @import("std");
const http = @import("./lib.zig");
const util = @import("util");
const query_utils = @import("./query.zig");
const http = @import("./lib.zig");
const urlencode = @import("./urlencode.zig");
const json_utils = @import("./json.zig");
/// Takes an iterable of middlewares and chains them together.
@ -606,13 +606,13 @@ fn parseBodyFromRequest(comptime T: type, content_type: ?[]const u8, reader: any
return try util.deepClone(alloc, body);
},
.url_encoded => return query_utils.parseQuery(alloc, T, buf) catch |err| switch (err) {
.url_encoded => return urlencode.parse(alloc, T, buf) catch |err| switch (err) {
//error.NoQuery => error.NoBody,
else => err,
},
.multipart_formdata => {
const param_string = std.mem.split(u8, eff_type, ";").rest();
const params = query_utils.parseQuery(alloc, struct {
const params = urlencode.parse(alloc, struct {
boundary: []const u8,
}, param_string) catch |err| return switch (err) {
//error.NoQuery => error.MissingBoundary,
@ -722,7 +722,7 @@ pub fn ParseQueryParams(comptime QueryParams: type) type {
return struct {
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
if (QueryParams == void) return next.handle(req, res, addField(ctx, "query_params", {}), {});
const query = try query_utils.parseQuery(ctx.allocator, QueryParams, ctx.query_string);
const query = try urlencode.parse(ctx.allocator, QueryParams, ctx.query_string);
defer util.deepFree(ctx.allocator, query);
return next.handle(

View File

@ -183,6 +183,7 @@ test "MultipartStream" {
var src = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
if (true) return error.SkipZigTest;
var stream = try openMultipart("abcd", src.reader());
while (try stream.next(std.testing.allocator)) |p| {
var part = p;
@ -202,7 +203,7 @@ test "parseFormData" {
\\--abcd--
\\
);
if (true) return error.SkipZigTest;
var src = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
const val = try parseFormData(struct {
foo: []const u8,

View File

@ -2,5 +2,5 @@ test {
_ = @import("./request/test_parser.zig");
_ = @import("./middleware.zig");
_ = @import("./multipart.zig");
_ = @import("./query.zig");
_ = @import("./urlencode.zig");
}

View File

@ -1,7 +1,38 @@
const std = @import("std");
const util = @import("util");
const QueryIter = util.QueryIter;
pub const Iter = struct {
const Pair = struct {
key: []const u8,
value: ?[]const u8,
};
iter: std.mem.SplitIterator(u8),
pub fn from(q: []const u8) Iter {
return Iter{
.iter = std.mem.split(u8, std.mem.trimLeft(u8, q, "?"), "&"),
};
}
pub fn next(self: *Iter) ?Pair {
while (true) {
const part = self.iter.next() orelse return null;
if (part.len == 0) continue;
const key = std.mem.sliceTo(part, '=');
if (key.len == part.len) return Pair{
.key = key,
.value = null,
};
return Pair{
.key = key,
.value = part[key.len + 1 ..],
};
}
}
};
/// Parses a set of query parameters described by the struct `T`.
///
@ -67,8 +98,8 @@ const QueryIter = util.QueryIter;
/// Would be used to parse a query string like
/// `?foo.baz=12345`
///
pub fn parseQuery(alloc: std.mem.Allocator, comptime T: type, query: []const u8) !T {
var iter = QueryIter.from(query);
pub fn parse(alloc: std.mem.Allocator, comptime T: type, query: []const u8) !T {
var iter = Iter.from(query);
var deserializer = Deserializer(T){};
@ -104,7 +135,7 @@ fn Deserializer(comptime Result: type) type {
});
}
pub fn parseQueryFree(alloc: std.mem.Allocator, val: anytype) void {
pub fn parseFree(alloc: std.mem.Allocator, val: anytype) void {
util.deepFree(alloc, val);
}
@ -143,7 +174,7 @@ fn isScalar(comptime T: type) bool {
return false;
}
pub fn QueryStringify(comptime Params: type) type {
pub fn EncodeStruct(comptime Params: type) type {
return struct {
params: Params,
pub fn format(v: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
@ -151,8 +182,8 @@ pub fn QueryStringify(comptime Params: type) type {
}
};
}
pub fn queryStringify(val: anytype) QueryStringify(@TypeOf(val)) {
return QueryStringify(@TypeOf(val)){ .params = val };
pub fn encodeStruct(val: anytype) EncodeStruct(@TypeOf(val)) {
return EncodeStruct(@TypeOf(val)){ .params = val };
}
fn urlFormatString(writer: anytype, val: []const u8) !void {
@ -214,11 +245,11 @@ fn formatQuery(comptime prefix: []const u8, comptime name: []const u8, params: a
}
}
test "parseQuery" {
test "parse" {
const testCase = struct {
fn case(comptime T: type, expected: T, query_string: []const u8) !void {
const result = try parseQuery(std.testing.allocator, T, query_string);
defer parseQueryFree(std.testing.allocator, result);
const result = try parse(std.testing.allocator, T, query_string);
defer parseFree(std.testing.allocator, result);
try util.testing.expectDeepEqual(expected, result);
}
}.case;
@ -304,14 +335,46 @@ test "parseQuery" {
try testCase(SubUnion2, .{ .sub = .{ .foo = 1, .val = .{ .baz = "abc" } } }, "sub.foo=1&sub.baz=abc");
}
test "formatQuery" {
try std.testing.expectFmt("", "{}", .{queryStringify(.{})});
try std.testing.expectFmt("id=3&", "{}", .{queryStringify(.{ .id = 3 })});
try std.testing.expectFmt("id=3&id2=4&", "{}", .{queryStringify(.{ .id = 3, .id2 = 4 })});
test "encodeStruct" {
try std.testing.expectFmt("", "{}", .{encodeStruct(.{})});
try std.testing.expectFmt("id=3&", "{}", .{encodeStruct(.{ .id = 3 })});
try std.testing.expectFmt("id=3&id2=4&", "{}", .{encodeStruct(.{ .id = 3, .id2 = 4 })});
try std.testing.expectFmt("str=foo&", "{}", .{queryStringify(.{ .str = "foo" })});
try std.testing.expectFmt("enum_str=foo&", "{}", .{queryStringify(.{ .enum_str = .foo })});
try std.testing.expectFmt("str=foo&", "{}", .{encodeStruct(.{ .str = "foo" })});
try std.testing.expectFmt("enum_str=foo&", "{}", .{encodeStruct(.{ .enum_str = .foo })});
try std.testing.expectFmt("boolean=false&", "{}", .{queryStringify(.{ .boolean = false })});
try std.testing.expectFmt("boolean=true&", "{}", .{queryStringify(.{ .boolean = true })});
try std.testing.expectFmt("boolean=false&", "{}", .{encodeStruct(.{ .boolean = false })});
try std.testing.expectFmt("boolean=true&", "{}", .{encodeStruct(.{ .boolean = true })});
}
test "Iter" {
const testCase = struct {
fn case(str: []const u8, pairs: []const Iter.Pair) !void {
var iter = Iter.from(str);
for (pairs) |pair| {
try util.testing.expectDeepEqual(@as(?Iter.Pair, pair), iter.next());
}
try std.testing.expect(iter.next() == null);
}
}.case;
try testCase("", &.{});
try testCase("abc", &.{.{ .key = "abc", .value = null }});
try testCase("abc=", &.{.{ .key = "abc", .value = "" }});
try testCase("abc=def", &.{.{ .key = "abc", .value = "def" }});
try testCase("abc=def&", &.{.{ .key = "abc", .value = "def" }});
try testCase("?abc=def&", &.{.{ .key = "abc", .value = "def" }});
try testCase("?abc=def&foo&bar=baz&qux=", &.{
.{ .key = "abc", .value = "def" },
.{ .key = "foo", .value = null },
.{ .key = "bar", .value = "baz" },
.{ .key = "qux", .value = "" },
});
try testCase("?abc=def&&foo&bar=baz&&qux=&", &.{
.{ .key = "abc", .value = "def" },
.{ .key = "foo", .value = null },
.{ .key = "bar", .value = "baz" },
.{ .key = "qux", .value = "" },
});
try testCase("&=def&", &.{.{ .key = "", .value = "def" }});
}

View File

@ -267,13 +267,13 @@ pub const helpers = struct {
try std.fmt.format(
writer,
"<{s}://{s}/{s}?{}>; rel=\"{s}\"",
.{ @tagName(c.scheme), c.host, path, http.queryStringify(params), rel },
.{ @tagName(c.scheme), c.host, path, http.urlencode.encodeStruct(params), rel },
);
} else {
try std.fmt.format(
writer,
"<{s}?{}>; rel=\"{s}\"",
.{ path, http.queryStringify(params), rel },
.{ path, http.urlencode.encodeStruct(params), rel },
);
}
// TODO: percent-encode

View File

@ -19,34 +19,6 @@ pub fn Separator(comptime separator: u8) type {
};
}
pub const QueryIter = struct {
const Pair = struct {
key: []const u8,
value: ?[]const u8,
};
iter: Separator('&'),
pub fn from(q: []const u8) QueryIter {
return QueryIter{ .iter = Separator('&').from(std.mem.trimLeft(u8, q, "?")) };
}
pub fn next(self: *QueryIter) ?Pair {
const part = self.iter.next() orelse return null;
const key = std.mem.sliceTo(part, '=');
if (key.len == part.len) return Pair{
.key = key,
.value = null,
};
return Pair{
.key = key,
.value = part[key.len + 1 ..],
};
}
};
pub const PathIter = struct {
is_first: bool,
iter: std.mem.SplitIterator(u8),
@ -76,94 +48,6 @@ pub const PathIter = struct {
}
};
test "QueryIter" {
const t = @import("std").testing;
if (true) return error.SkipZigTest;
{
var iter = QueryIter.from("");
try t.expect(iter.next() == null);
try t.expect(iter.next() == null);
}
{
var iter = QueryIter.from("?");
try t.expect(iter.next() == null);
try t.expect(iter.next() == null);
}
{
var iter = QueryIter.from("?abc");
try t.expectEqual(QueryIter.Pair{
.key = "abc",
.value = null,
}, iter.next().?);
try t.expect(iter.next() == null);
try t.expect(iter.next() == null);
}
{
var iter = QueryIter.from("?abc=");
try t.expectEqual(QueryIter.Pair{
.key = "abc",
.value = "",
}, iter.next().?);
try t.expect(iter.next() == null);
try t.expect(iter.next() == null);
}
{
var iter = QueryIter.from("?abc=def");
try t.expectEqual(QueryIter.Pair{
.key = "abc",
.value = "def",
}, iter.next().?);
try t.expect(iter.next() == null);
try t.expect(iter.next() == null);
}
{
var iter = QueryIter.from("?abc=def&");
try t.expectEqual(QueryIter.Pair{
.key = "abc",
.value = "def",
}, iter.next().?);
try t.expect(iter.next() == null);
try t.expect(iter.next() == null);
}
{
var iter = QueryIter.from("?abc=def&foo&bar=baz&qux=");
try t.expectEqual(QueryIter.Pair{
.key = "abc",
.value = "def",
}, iter.next().?);
try t.expectEqual(QueryIter.Pair{
.key = "foo",
.value = null,
}, iter.next().?);
try t.expectEqual(QueryIter.Pair{
.key = "bar",
.value = "baz",
}, iter.next().?);
try t.expectEqual(QueryIter.Pair{
.key = "qux",
.value = "",
}, iter.next().?);
try t.expect(iter.next() == null);
try t.expect(iter.next() == null);
}
{
var iter = QueryIter.from("?=def&");
try t.expectEqual(QueryIter.Pair{
.key = "",
.value = "def",
}, iter.next().?);
try t.expect(iter.next() == null);
try t.expect(iter.next() == null);
}
}
test "PathIter /ab/cd/" {
const path = "/ab/cd/";
var it = PathIter.from(path);

View File

@ -5,7 +5,6 @@ pub const Uuid = @import("./Uuid.zig");
pub const DateTime = @import("./DateTime.zig");
pub const Url = @import("./Url.zig");
pub const PathIter = iters.PathIter;
pub const QueryIter = iters.QueryIter;
pub const SqlStmtIter = iters.Separator(';');
pub const serialize = @import("./serialize.zig");
pub const Deserializer = serialize.Deserializer;