diff --git a/examples/http_params/http_params.zig b/examples/http_params/http_params.zig index 68e4040..29ed6d0 100644 --- a/examples/http_params/http_params.zig +++ b/examples/http_params/http_params.zig @@ -47,6 +47,27 @@ pub fn main() !void { var param_count = r.getParamCount(); std.log.info("param_count: {}", .{param_count}); + // ================================================================ + // Access RAW params from querystring + // ================================================================ + + // let's get param "one" by name + std.debug.print("\n", .{}); + if (r.getParamSlice("one")) |maybe_str| { + std.log.info("Param one = {s}", .{maybe_str}); + } else { + std.log.info("Param one not found!", .{}); + } + + var arg_it = r.getParamSlices(); + while (arg_it.next()) |param| { + std.log.info("ParamStr `{s}` is `{s}`", .{ param.name, param.value }); + } + + // ================================================================ + // Access DECODED and typed params + // ================================================================ + // iterate over all params as strings var strparams = r.parametersToOwnedStrList(alloc, false) catch unreachable; defer strparams.deinit(); @@ -82,15 +103,10 @@ pub fn main() !void { } // check if we received a terminate=true parameter - if (r.getParamStr(alloc, "terminate", false)) |maybe_str| { - if (maybe_str) |*s| { - defer s.deinit(); - if (std.mem.eql(u8, s.str, "true")) { - zap.stop(); - } + if (r.getParamSlice("terminate")) |maybe_str| { + if (std.mem.eql(u8, maybe_str, "true")) { + zap.stop(); } - } else |err| { - std.log.err("cannot check for terminate param: {any}\n", .{err}); } } }; diff --git a/src/request.zig b/src/request.zig index cd98ddd..d8c0cbb 100644 --- a/src/request.zig +++ b/src/request.zig @@ -737,3 +737,59 @@ pub fn getParamStr(self: *const Self, a: std.mem.Allocator, name: []const u8, al } return try util.fio2strAllocOrNot(a, value, always_alloc); } + +/// similar to getParamStr, except it will return the part of the querystring +/// after the equals sign, non-decoded, and always as character slice. +/// - no allocation! +/// - does not requre parseQuery() or anything to be called in advance +pub fn getParamSlice(self: *const Self, name: []const u8) ?[]const u8 { + if (self.query) |query| { + var amp_it = std.mem.tokenizeScalar(u8, query, '&'); + while (amp_it.next()) |maybe_pair| { + if (std.mem.indexOfScalar(u8, maybe_pair, '=')) |pos_of_eq| { + const pname = maybe_pair[0..pos_of_eq]; + if (std.mem.eql(u8, pname, name)) { + if (maybe_pair.len > pos_of_eq) { + const pval = maybe_pair[pos_of_eq + 1 ..]; + return pval; + } + } + } + } + } + return null; +} + +pub const ParameterSlices = struct { name: []const u8, value: []const u8 }; + +pub const ParamSliceIterator = struct { + amp_it: std.mem.TokenIterator(u8, .scalar), + + pub fn init(query: []const u8) @This() { + // const query = r.query orelse ""; + return .{ + .amp_it = std.mem.tokenizeScalar(u8, query, '&'), + }; + } + + pub fn next(self: *@This()) ?ParameterSlices { + while (self.amp_it.next()) |maybe_pair| { + if (std.mem.indexOfScalar(u8, maybe_pair, '=')) |pos_of_eq| { + const pname = maybe_pair[0..pos_of_eq]; + if (maybe_pair.len > pos_of_eq) { + const pval = maybe_pair[pos_of_eq + 1 ..]; + return .{ .name = pname, .value = pval }; + } + } + } + return null; + } +}; + +/// Returns an iterator that yields all query parameters on next() in the +/// form of a ParameterSlices struct { .name, .value } +/// As with getParamSlice(), the value is not decoded +pub fn getParamSlices(self: *const Self) ParamSliceIterator { + const query = self.query orelse ""; + return ParamSliceIterator.init(query); +} diff --git a/src/tests/test_http_params.zig b/src/tests/test_http_params.zig index c5e3387..718f65f 100644 --- a/src/tests/test_http_params.zig +++ b/src/tests/test_http_params.zig @@ -33,6 +33,8 @@ test "http parameters" { var strParams: ?zap.Request.HttpParamStrKVList = null; var params: ?zap.Request.HttpParamKVList = null; var paramOneStr: ?zap.FreeOrNot = null; + var paramOneSlice: ?[]const u8 = null; + var paramSlices: zap.Request.ParamSliceIterator = undefined; pub fn on_request(r: zap.Request) void { ran = true; @@ -49,6 +51,14 @@ test "http parameters" { if (maybe_str) |*s| { paramOneStr = s.*; } + + paramOneSlice = blk: { + if (r.getParamSlice("one")) |val| break :blk alloc.dupe(u8, val) catch unreachable; + break :blk null; + }; + + // paramSlices = zap.Request.ParamSliceIterator.init(r); + paramSlices = r.getParamSlices(); } }; @@ -85,12 +95,18 @@ test "http parameters" { // allocator.free(p); p.deinit(); } + + if (Handler.paramOneSlice) |p| { + Handler.alloc.free(p); + } } try std.testing.expectEqual(Handler.ran, true); try std.testing.expectEqual(Handler.param_count, 5); try std.testing.expect(Handler.paramOneStr != null); try std.testing.expectEqualStrings(Handler.paramOneStr.?.str, "1"); + try std.testing.expect(Handler.paramOneSlice != null); + try std.testing.expectEqualStrings(Handler.paramOneSlice.?, "1"); try std.testing.expect(Handler.strParams != null); for (Handler.strParams.?.items, 0..) |kv, i| { switch (i) { @@ -118,6 +134,34 @@ test "http parameters" { } } + var pindex: usize = 0; + while (Handler.paramSlices.next()) |param| { + switch (pindex) { + 0 => { + try std.testing.expectEqualStrings(param.name, "one"); + try std.testing.expectEqualStrings(param.value, "1"); + }, + 1 => { + try std.testing.expectEqualStrings(param.name, "two"); + try std.testing.expectEqualStrings(param.value, "2"); + }, + 2 => { + try std.testing.expectEqualStrings(param.name, "string"); + try std.testing.expectEqualStrings(param.value, "hello+world"); + }, + 3 => { + try std.testing.expectEqualStrings(param.name, "float"); + try std.testing.expectEqualStrings(param.value, "6.28"); + }, + 4 => { + try std.testing.expectEqualStrings(param.name, "bool"); + try std.testing.expectEqualStrings(param.value, "true"); + }, + else => return error.TooManyArgs, + } + pindex += 1; + } + for (Handler.params.?.items, 0..) |kv, i| { switch (i) { 0 => {