Refactoring
This commit is contained in:
parent
b2093128de
commit
16c574bdd6
8 changed files with 92 additions and 146 deletions
|
@ -1,8 +1,8 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const request = @import("./request.zig");
|
const request = @import("./request.zig");
|
||||||
|
|
||||||
const server = @import("./server.zig");
|
const server = @import("./server.zig");
|
||||||
|
pub const urlencode = @import("./urlencode.zig");
|
||||||
|
|
||||||
pub const socket = @import("./socket.zig");
|
pub const socket = @import("./socket.zig");
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ pub const Handler = server.Handler;
|
||||||
pub const Server = server.Server;
|
pub const Server = server.Server;
|
||||||
|
|
||||||
pub const middleware = @import("./middleware.zig");
|
pub const middleware = @import("./middleware.zig");
|
||||||
pub const queryStringify = @import("./query.zig").queryStringify;
|
|
||||||
|
|
||||||
pub const Fields = @import("./headers.zig").Fields;
|
pub const Fields = @import("./headers.zig").Fields;
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
/// Terminal middlewares that are not implemented using other middlewares should
|
/// Terminal middlewares that are not implemented using other middlewares should
|
||||||
/// only accept a `void` value for `next_handler`.
|
/// only accept a `void` value for `next_handler`.
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const http = @import("./lib.zig");
|
|
||||||
const util = @import("util");
|
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");
|
const json_utils = @import("./json.zig");
|
||||||
|
|
||||||
/// Takes an iterable of middlewares and chains them together.
|
/// 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);
|
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,
|
//error.NoQuery => error.NoBody,
|
||||||
else => err,
|
else => err,
|
||||||
},
|
},
|
||||||
.multipart_formdata => {
|
.multipart_formdata => {
|
||||||
const param_string = std.mem.split(u8, eff_type, ";").rest();
|
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,
|
boundary: []const u8,
|
||||||
}, param_string) catch |err| return switch (err) {
|
}, param_string) catch |err| return switch (err) {
|
||||||
//error.NoQuery => error.MissingBoundary,
|
//error.NoQuery => error.MissingBoundary,
|
||||||
|
@ -722,7 +722,7 @@ pub fn ParseQueryParams(comptime QueryParams: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
pub fn handle(_: @This(), req: anytype, res: anytype, ctx: anytype, next: anytype) !void {
|
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", {}), {});
|
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);
|
defer util.deepFree(ctx.allocator, query);
|
||||||
|
|
||||||
return next.handle(
|
return next.handle(
|
||||||
|
|
|
@ -183,6 +183,7 @@ test "MultipartStream" {
|
||||||
|
|
||||||
var src = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
var src = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
||||||
|
|
||||||
|
if (true) return error.SkipZigTest;
|
||||||
var stream = try openMultipart("abcd", src.reader());
|
var stream = try openMultipart("abcd", src.reader());
|
||||||
while (try stream.next(std.testing.allocator)) |p| {
|
while (try stream.next(std.testing.allocator)) |p| {
|
||||||
var part = p;
|
var part = p;
|
||||||
|
@ -202,7 +203,7 @@ test "parseFormData" {
|
||||||
\\--abcd--
|
\\--abcd--
|
||||||
\\
|
\\
|
||||||
);
|
);
|
||||||
|
if (true) return error.SkipZigTest;
|
||||||
var src = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
var src = std.io.StreamSource{ .const_buffer = std.io.fixedBufferStream(body) };
|
||||||
const val = try parseFormData(struct {
|
const val = try parseFormData(struct {
|
||||||
foo: []const u8,
|
foo: []const u8,
|
||||||
|
|
|
@ -2,5 +2,5 @@ test {
|
||||||
_ = @import("./request/test_parser.zig");
|
_ = @import("./request/test_parser.zig");
|
||||||
_ = @import("./middleware.zig");
|
_ = @import("./middleware.zig");
|
||||||
_ = @import("./multipart.zig");
|
_ = @import("./multipart.zig");
|
||||||
_ = @import("./query.zig");
|
_ = @import("./urlencode.zig");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,38 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const util = @import("util");
|
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`.
|
/// 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
|
/// Would be used to parse a query string like
|
||||||
/// `?foo.baz=12345`
|
/// `?foo.baz=12345`
|
||||||
///
|
///
|
||||||
pub fn parseQuery(alloc: std.mem.Allocator, comptime T: type, query: []const u8) !T {
|
pub fn parse(alloc: std.mem.Allocator, comptime T: type, query: []const u8) !T {
|
||||||
var iter = QueryIter.from(query);
|
var iter = Iter.from(query);
|
||||||
|
|
||||||
var deserializer = Deserializer(T){};
|
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);
|
util.deepFree(alloc, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +174,7 @@ fn isScalar(comptime T: type) bool {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn QueryStringify(comptime Params: type) type {
|
pub fn EncodeStruct(comptime Params: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
params: Params,
|
params: Params,
|
||||||
pub fn format(v: @This(), comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
|
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)) {
|
pub fn encodeStruct(val: anytype) EncodeStruct(@TypeOf(val)) {
|
||||||
return QueryStringify(@TypeOf(val)){ .params = val };
|
return EncodeStruct(@TypeOf(val)){ .params = val };
|
||||||
}
|
}
|
||||||
|
|
||||||
fn urlFormatString(writer: anytype, val: []const u8) !void {
|
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 {
|
const testCase = struct {
|
||||||
fn case(comptime T: type, expected: T, query_string: []const u8) !void {
|
fn case(comptime T: type, expected: T, query_string: []const u8) !void {
|
||||||
const result = try parseQuery(std.testing.allocator, T, query_string);
|
const result = try parse(std.testing.allocator, T, query_string);
|
||||||
defer parseQueryFree(std.testing.allocator, result);
|
defer parseFree(std.testing.allocator, result);
|
||||||
try util.testing.expectDeepEqual(expected, result);
|
try util.testing.expectDeepEqual(expected, result);
|
||||||
}
|
}
|
||||||
}.case;
|
}.case;
|
||||||
|
@ -304,14 +335,46 @@ test "parseQuery" {
|
||||||
try testCase(SubUnion2, .{ .sub = .{ .foo = 1, .val = .{ .baz = "abc" } } }, "sub.foo=1&sub.baz=abc");
|
try testCase(SubUnion2, .{ .sub = .{ .foo = 1, .val = .{ .baz = "abc" } } }, "sub.foo=1&sub.baz=abc");
|
||||||
}
|
}
|
||||||
|
|
||||||
test "formatQuery" {
|
test "encodeStruct" {
|
||||||
try std.testing.expectFmt("", "{}", .{queryStringify(.{})});
|
try std.testing.expectFmt("", "{}", .{encodeStruct(.{})});
|
||||||
try std.testing.expectFmt("id=3&", "{}", .{queryStringify(.{ .id = 3 })});
|
try std.testing.expectFmt("id=3&", "{}", .{encodeStruct(.{ .id = 3 })});
|
||||||
try std.testing.expectFmt("id=3&id2=4&", "{}", .{queryStringify(.{ .id = 3, .id2 = 4 })});
|
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("str=foo&", "{}", .{encodeStruct(.{ .str = "foo" })});
|
||||||
try std.testing.expectFmt("enum_str=foo&", "{}", .{queryStringify(.{ .enum_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=false&", "{}", .{encodeStruct(.{ .boolean = false })});
|
||||||
try std.testing.expectFmt("boolean=true&", "{}", .{queryStringify(.{ .boolean = true })});
|
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" }});
|
||||||
}
|
}
|
|
@ -267,13 +267,13 @@ pub const helpers = struct {
|
||||||
try std.fmt.format(
|
try std.fmt.format(
|
||||||
writer,
|
writer,
|
||||||
"<{s}://{s}/{s}?{}>; rel=\"{s}\"",
|
"<{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 {
|
} else {
|
||||||
try std.fmt.format(
|
try std.fmt.format(
|
||||||
writer,
|
writer,
|
||||||
"<{s}?{}>; rel=\"{s}\"",
|
"<{s}?{}>; rel=\"{s}\"",
|
||||||
.{ path, http.queryStringify(params), rel },
|
.{ path, http.urlencode.encodeStruct(params), rel },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// TODO: percent-encode
|
// TODO: percent-encode
|
||||||
|
|
|
@ -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 {
|
pub const PathIter = struct {
|
||||||
is_first: bool,
|
is_first: bool,
|
||||||
iter: std.mem.SplitIterator(u8),
|
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/" {
|
test "PathIter /ab/cd/" {
|
||||||
const path = "/ab/cd/";
|
const path = "/ab/cd/";
|
||||||
var it = PathIter.from(path);
|
var it = PathIter.from(path);
|
||||||
|
|
|
@ -5,7 +5,6 @@ pub const Uuid = @import("./Uuid.zig");
|
||||||
pub const DateTime = @import("./DateTime.zig");
|
pub const DateTime = @import("./DateTime.zig");
|
||||||
pub const Url = @import("./Url.zig");
|
pub const Url = @import("./Url.zig");
|
||||||
pub const PathIter = iters.PathIter;
|
pub const PathIter = iters.PathIter;
|
||||||
pub const QueryIter = iters.QueryIter;
|
|
||||||
pub const SqlStmtIter = iters.Separator(';');
|
pub const SqlStmtIter = iters.Separator(';');
|
||||||
pub const serialize = @import("./serialize.zig");
|
pub const serialize = @import("./serialize.zig");
|
||||||
pub const Deserializer = serialize.Deserializer;
|
pub const Deserializer = serialize.Deserializer;
|
||||||
|
|
Loading…
Reference in a new issue