diff --git a/src/http/headers.zig b/src/http/fields.zig similarity index 76% rename from src/http/headers.zig rename to src/http/fields.zig index 1b91865..d6319da 100644 --- a/src/http/headers.zig +++ b/src/http/fields.zig @@ -1,5 +1,60 @@ const std = @import("std"); +pub const ParamIter = struct { + str: []const u8, + index: usize = 0, + + const Param = struct { + name: []const u8, + value: []const u8, + }; + + pub fn from(str: []const u8) ParamIter { + return .{ .str = str, .index = std.mem.indexOfScalar(u8, str, ';') orelse str.len }; + } + + pub fn fieldValue(self: *ParamIter) []const u8 { + return std.mem.sliceTo(self.str, ';'); + } + + pub fn next(self: *ParamIter) ?Param { + if (self.index >= self.str.len) return null; + + const start = self.index + 1; + const new_start = std.mem.indexOfScalarPos(u8, self.str, start, ';') orelse self.str.len; + self.index = new_start; + + const param = std.mem.trim(u8, self.str[start..new_start], " \t"); + var split = std.mem.split(u8, param, "="); + const name = split.first(); + const value = std.mem.trimLeft(u8, split.rest(), " \t"); + // TODO: handle quoted values + // TODO: handle parse errors + + return Param{ + .name = name, + .value = value, + }; + } +}; + +pub fn getParam(field: []const u8, name: ?[]const u8) ?[]const u8 { + var iter = ParamIter.from(field); + + if (name) |param| { + while (iter.next()) |p| { + if (std.ascii.eqlIgnoreCase(param, p.name)) { + const trimmed = std.mem.trim(u8, p.value, " \t"); + if (trimmed.len >= 2 and trimmed[0] == '"' and trimmed[trimmed.len - 1] == '"') { + return trimmed[1 .. trimmed.len - 1]; + } + return trimmed; + } + } + return null; + } else return iter.fieldValue(); +} + pub const Fields = struct { const HashContext = struct { const hash_seed = 1; diff --git a/src/http/lib.zig b/src/http/lib.zig index abde83e..3cb0f56 100644 --- a/src/http/lib.zig +++ b/src/http/lib.zig @@ -5,6 +5,7 @@ const server = @import("./server.zig"); pub const urlencode = @import("./urlencode.zig"); pub const socket = @import("./socket.zig"); const json = @import("./json.zig"); +pub const fields = @import("./fields.zig"); pub const Method = std.http.Method; pub const Status = std.http.Status; @@ -16,7 +17,7 @@ pub const Server = server.Server; pub const middleware = @import("./middleware.zig"); -pub const Fields = @import("./headers.zig").Fields; +pub const Fields = fields.Fields; pub const Protocol = enum { http_1_0, diff --git a/src/http/test.zig b/src/http/test.zig deleted file mode 100644 index 63bae92..0000000 --- a/src/http/test.zig +++ /dev/null @@ -1,6 +0,0 @@ -test { - _ = @import("./request/test_parser.zig"); - _ = @import("./middleware.zig"); - _ = @import("./multipart.zig"); - _ = @import("./urlencode.zig"); -} diff --git a/src/sql/lib.zig b/src/sql/lib.zig index 358a2d3..2ad2f1a 100644 --- a/src/sql/lib.zig +++ b/src/sql/lib.zig @@ -144,42 +144,16 @@ fn fieldPtr(ptr: anytype, comptime names: []const []const u8) FieldPtr(@TypeOf(p return fieldPtr(&@field(ptr.*, names[0]), names[1..]); } -fn isScalar(comptime T: type) bool { - if (comptime std.meta.trait.isZigString(T)) return true; - if (comptime std.meta.trait.isIntegral(T)) return true; - if (comptime std.meta.trait.isFloat(T)) return true; - if (comptime std.meta.trait.is(.Enum)(T)) return true; - if (T == bool) return true; - if (comptime std.meta.trait.hasFn("parse")(T)) return true; - - if (comptime std.meta.trait.is(.Optional)(T) and isScalar(std.meta.Child(T))) return true; - - return false; -} - -fn recursiveFieldPaths(comptime T: type, comptime prefix: []const []const u8) []const []const []const u8 { - comptime { - var fields: []const []const []const u8 = &.{}; - - for (std.meta.fields(T)) |f| { - const full_name = prefix ++ [_][]const u8{f.name}; - if (isScalar(f.field_type)) { - fields = fields ++ [_][]const []const u8{full_name}; - } else { - fields = fields ++ recursiveFieldPaths(f.field_type, full_name); - } - } - - return fields; - } -} - // Represents a set of results. // row() must be called until it returns null, or the query may not complete // Must be deallocated by a call to finish() pub fn Results(comptime T: type) type { // would normally make this a declaration of the struct, but it causes the compiler to crash - const fields = if (T == void) .{} else recursiveFieldPaths(T, &.{}); + const fields = if (T == void) .{} else util.serialize.getRecursiveFieldList( + T, + &.{}, + util.serialize.default_options, + ); return struct { const Self = @This(); diff --git a/src/template/lib.zig b/src/template/lib.zig index 8c6c8e9..f1e141d 100644 --- a/src/template/lib.zig +++ b/src/template/lib.zig @@ -601,3 +601,20 @@ const ControlTokenIter = struct { self.peeked_token = token; } }; + +test "template" { + const testCase = struct { + fn case(comptime tmpl: []const u8, args: anytype, expected: []const u8) !void { + var stream = std.io.changeDetectionStream(expected, std.io.null_writer); + try execute(stream.writer(), tmpl, args); + try std.testing.expect(!stream.changeDetected()); + } + }.case; + + try testCase("", .{}, ""); + try testCase("abcd", .{}, "abcd"); + try testCase("{.val}", .{ .val = 3 }, "3"); + try testCase("{#if .val}1{/if}", .{ .val = true }, "1"); + try testCase("{#for .vals |$v|}{$v}{/for}", .{ .vals = [_]u8{ 1, 2, 3 } }, "123"); + try testCase("{#for .vals |$v|=} {$v} {=/for}", .{ .vals = [_]u8{ 1, 2, 3 } }, "123"); +} diff --git a/src/util/lib.zig b/src/util/lib.zig index 6d69c47..fb2bee0 100644 --- a/src/util/lib.zig +++ b/src/util/lib.zig @@ -201,7 +201,7 @@ pub fn seedThreadPrng() !void { pub fn comptimeToCrlf(comptime str: []const u8) []const u8 { comptime { - @setEvalBranchQuota(str.len * 6); + @setEvalBranchQuota(str.len * 10); const size = std.mem.replacementSize(u8, str, "\n", "\r\n"); var buf: [size]u8 = undefined; _ = std.mem.replace(u8, str, "\n", "\r\n", &buf); diff --git a/src/util/serialize.zig b/src/util/serialize.zig index 0fd7594..cf4a4c5 100644 --- a/src/util/serialize.zig +++ b/src/util/serialize.zig @@ -38,7 +38,7 @@ pub fn deserializeString(allocator: std.mem.Allocator, comptime T: type, value: @compileError("Invalid type " ++ @typeName(T)); } -fn getRecursiveFieldList(comptime T: type, comptime prefix: FieldRef, comptime options: SerializationOptions) []const FieldRef { +pub fn getRecursiveFieldList(comptime T: type, comptime prefix: FieldRef, comptime options: SerializationOptions) []const FieldRef { comptime { if (std.meta.trait.is(.Union)(T) and prefix.len == 0 and options.embed_unions) { @compileError("Cannot embed a union into nothing"); @@ -113,7 +113,7 @@ pub fn DeserializerContext(comptime Result: type, comptime From: type, comptime context: Context = .{}, pub fn setSerializedField(self: *@This(), key: []const u8, value: From) !void { - const field = std.meta.stringToEnum(std.meta.FieldEnum(Data), key); + const field = std.meta.stringToEnum(std.meta.FieldEnum(Data), key) orelse return error.UnknownField; inline for (comptime std.meta.fieldNames(Data)) |field_name| { @setEvalBranchQuota(10000); const f = comptime std.meta.stringToEnum(std.meta.FieldEnum(Data), field_name); @@ -123,7 +123,36 @@ pub fn DeserializerContext(comptime Result: type, comptime From: type, comptime } } - return error.UnknownField; + unreachable; + } + + pub const Iter = struct { + data: *const Data, + field_index: usize, + + const Item = struct { + key: []const u8, + value: From, + }; + + pub fn next(self: *Iter) ?Item { + while (self.field_index < std.meta.fields(Data).len) { + const idx = self.field_index; + self.field_index += 1; + inline for (comptime std.meta.fieldNames(Data)) |field, i| { + if (i == idx) { + const maybe_value = @field(self.data.*, field); + if (maybe_value) |value| return Item{ .key = field, .value = value }; + } + } + } + + return null; + } + }; + + pub fn iterator(self: *const @This()) Iter { + return .{ .data = &self.data, .field_index = 0 }; } pub fn finishFree(_: *@This(), allocator: std.mem.Allocator, val: anytype) void {