From 17d8cc65fe6396d505dda6bf162942cab9e00408 Mon Sep 17 00:00:00 2001 From: Martin Ashby Date: Sun, 6 Aug 2023 21:38:52 +0100 Subject: Add structure parsing to form/query handling --- src/zigwebserver.zig | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/zigwebserver.zig b/src/zigwebserver.zig index 42188f8..2e20382 100644 --- a/src/zigwebserver.zig +++ b/src/zigwebserver.zig @@ -394,6 +394,14 @@ pub const Path = struct { } return self.query_parsed.?.data.get(key); } + + pub fn query_to_struct(self: *Path, comptime T: type) !T { + if (self.query_parsed == null) { + self.query_parsed = try Form.parse(self.allocator, self.query); + } + return self.query_parsed.?.form_to_struct(T); + } + pub fn deinit(self: *Path) void { if (self.query_parsed != null) { self.query_parsed.?.deinit(); @@ -453,6 +461,21 @@ const PathTest = struct { try std.testing.expect(v2 != null); try std.testing.expectEqualSlices(u8, "bo om", v2.?); } + + test "query to struct" { + var p = try Path.parse(std.testing.allocator, "/foo?bar=ba+z&bam=55&zigzag#frag"); + defer p.deinit(); + const T = struct { + bar: []const u8, + bam: u64, + fn deinit(self: *@This()) void { + std.testing.allocator.free(self.bar); + } + }; + var t = try p.query_to_struct(T); + defer t.deinit(); + try std.testing.expectEqualDeep(T{ .bar = "ba z", .bam = 55 }, t); + } }; pub const Form = struct { @@ -482,6 +505,9 @@ pub const Form = struct { } return Form{ .allocator = allocator, .data = res }; } + pub fn form_to_struct(self: *Form, comptime T: type) !T { + return to_struct(self.allocator, T, self.data); + } pub fn deinit(self: *Form) void { var it = self.data.iterator(); var e = it.next(); @@ -524,8 +550,63 @@ const PercentEncodeTest = struct { } }; +/// Populate a struct from a hashmap +fn to_struct(allocator: std.mem.Allocator, comptime T: type, hm: std.StringHashMap([]const u8)) !T { + const ti = @typeInfo(T); + if (ti != .Struct) { + @compileError("to_struct T was not a struct type"); + } + var t: T = undefined; + inline for (ti.Struct.fields) |field| { + if (field.is_comptime) { + @compileError("can't dynamically set comptime field " ++ field.name); + } + const value: []const u8 = hm.get(field.name) orelse { + return error.FieldNotPresent; // TODO somehow be more useful. + }; + switch (@typeInfo(field.type)) { // TODO handle more types, default values etc etc. + .Int => { + @field(t, field.name) = try std.fmt.parseInt(field.type, value, 10); + }, + .Pointer => |ptrinfo| { + if (ptrinfo.size != .Slice) { + @compileError("field pointer size " ++ @tagName(ptrinfo.size) ++ " is not supported, only []u8 is supported right now"); + } + if (ptrinfo.child != u8) { + @compileError("field pointer type " ++ @tagName(@typeInfo(ptrinfo.child)) ++ " is not supported, only []u8 is supported right now"); + } + const dvalue = try allocator.dupe(u8, value); + errdefer allocator.free(dvalue); + @field(t, field.name) = dvalue; + }, + else => @compileError("field type " ++ @tagName(@typeInfo(field.type)) ++ " not supported on field " ++ field.name), + } + } + return t; +} + +const StructTest = struct { + test "to struct" { + const T = struct { + foo: i64, + bar: []const u8, + pub fn deinit(self: *@This()) void { + std.testing.allocator.free(self.bar); + } + }; + var hm = std.StringHashMap([]const u8).init(std.testing.allocator); + defer hm.deinit(); + try hm.put("foo", "42"); + try hm.put("bar", "oops"); + var t = try to_struct(std.testing.allocator, T, hm); + defer t.deinit(); + try std.testing.expectEqualDeep(T{ .foo = 42, .bar = "oops" }, t); + } +}; + test { _ = RouterTest; _ = PathTest; _ = PercentEncodeTest; + _ = StructTest; } -- cgit v1.2.3-ZIG