1
0
Fork 0
mirror of https://github.com/zigzap/zap.git synced 2025-10-20 15:14:08 +00:00

Mega update! SimpleHttpRequest got Parameters!

parseBody() : parse form params
parseQuery() : parse query params
getParamCount() : returns number of parsed params
parametersToOwnedStrList() : get params as kv pairs of strings
parametersToOwnedList() : get params as kv list
getParamStr() : get parameter by name
This commit is contained in:
Rene Schallner 2023-05-06 02:38:35 +02:00
parent e679039bae
commit d0be271337
11 changed files with 647 additions and 31 deletions

View file

@ -26,5 +26,7 @@ jobs:
run: zig version
- name: Build simple endpoint example
run: zig build endpoint
- name: Build tests
run: zig build test
- name: Build authentication tests
run: zig build test-authentication
- name: Build http parameter tests
run: zig build test-httpparams

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ tmp/
**/target/*
**/__pycache__/*
wrk/go/main
scratch

View file

@ -41,7 +41,9 @@ Here's what works:
- **[mustache](examples/mustache/)**: a simple example using
[mustache](https://mustache.github.io/) templating.
- **[endpoint authentication](examples/endpoint_auth/)**: a simple authenticated
endpoint. Read more about authentication [here](./doc/authentication.md)
endpoint. Read more about authentication [here](./doc/authentication.md).
- **[http parameters](examples/http_parameters/)**: a simple example sending
itself query parameters of all supported types.
I'll continue wrapping more of facil.io's functionality and adding stuff to zap

View file

@ -50,6 +50,7 @@ pub fn build(b: *std.build.Builder) !void {
.{ .name = "wrk_zigstd", .src = "wrk/zigstd/main.zig" },
.{ .name = "mustache", .src = "examples/mustache/mustache.zig" },
.{ .name = "endpoint_auth", .src = "examples/endpoint_auth/endpoint_auth.zig" },
.{ .name = "http_params", .src = "examples/http_params/http_params.zig" },
}) |excfg| {
const ex_name = excfg.name;
const ex_src = excfg.src;
@ -93,11 +94,20 @@ pub fn build(b: *std.build.Builder) !void {
//
// TOOLS & TESTING
//
// n.b.: tests run in parallel, so we need all tests that use the network
// to run sequentially, since zap doesn't like to be started multiple
// times on different threads
//
// TODO: for some reason, tests aren't run more than once unless
// dependencies have changed.
// So, for now, we just force the exe to be built, so in order that
// we can call it again when needed.
// tests
// authentication tests
//
const auth_tests = b.addTest(.{
.root_source_file = .{ .path = "src/test_auth.zig" },
.name = "auth_tests",
.root_source_file = .{ .path = "src/tests/test_auth.zig" },
.target = target,
.optimize = optimize,
});
@ -105,15 +115,33 @@ pub fn build(b: *std.build.Builder) !void {
auth_tests.addModule("zap", zap_module);
const run_auth_tests = b.addRunArtifact(auth_tests);
const install_auth_tests = b.addInstallArtifact(auth_tests);
// http paramters (qyery, body) tests
const httpparams_tests = b.addTest(.{
.name = "http_params_tests",
.root_source_file = .{ .path = "src/tests/test_http_params.zig" },
.target = target,
.optimize = optimize,
});
httpparams_tests.linkLibrary(facil_dep.artifact("facil.io"));
httpparams_tests.addModule("zap", zap_module);
const run_httpparams_tests = b.addRunArtifact(httpparams_tests);
// TODO: for some reason, tests aren't run more than once unless
// dependencies have changed.
// So, for now, we just force the exe to be built, so in order that
// we can call it again when needed.
const install_auth_tests = b.addInstallArtifact(auth_tests);
const install_httpparams_tests = b.addInstallArtifact(httpparams_tests);
const test_step = b.step("test", "Run unit tests [REMOVE zig-cache!]");
test_step.dependOn(&run_auth_tests.step);
test_step.dependOn(&install_auth_tests.step);
// test commands
const run_auth_test_step = b.step("test-authentication", "Run auth unit tests [REMOVE zig-cache!]");
run_auth_test_step.dependOn(&run_auth_tests.step);
run_auth_test_step.dependOn(&install_auth_tests.step);
const run_httpparams_test_step = b.step("test-httpparams", "Run http param unit tests [REMOVE zig-cache!]");
run_httpparams_test_step.dependOn(&run_httpparams_tests.step);
run_httpparams_test_step.dependOn(&install_httpparams_tests.step);
// pkghash
//

View file

@ -1,6 +1,6 @@
.{
.name = "zap",
.version = "0.0.13",
.version = "0.0.14",
.dependencies = .{
.@"facil.io" = .{

View file

@ -0,0 +1,120 @@
const std = @import("std");
const zap = @import("zap");
// We send ourselves a request
fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
const uri = try std.Uri.parse(url);
var h = std.http.Headers{ .allocator = a };
defer h.deinit();
var http_client: std.http.Client = .{ .allocator = a };
defer http_client.deinit();
var req = try http_client.request(.GET, uri, h, .{});
defer req.deinit();
try req.start();
try req.wait();
}
fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread {
return try std.Thread.spawn(.{}, makeRequest, .{ a, url });
}
// here we go
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{
.thread_safe = true,
}){};
var allocator = gpa.allocator();
const Handler = struct {
var alloc: std.mem.Allocator = undefined;
pub fn on_request(r: zap.SimpleRequest) void {
std.debug.print("\n=====================================================\n", .{});
defer std.debug.print("=====================================================\n\n", .{});
// check for FORM parameters
r.parseBody() catch |err| {
std.log.err("Parse Body error: {any}. Expected if body is empty", .{err});
};
// check for query parameters
r.parseQuery();
var param_count = r.getParamCount();
std.log.info("param_count: {}", .{param_count});
// iterate over all params as strings
var strparams = r.parametersToOwnedStrList(alloc, false) catch unreachable;
defer strparams.deinit();
std.debug.print("\n", .{});
for (strparams.items) |kv| {
std.log.info("ParamStr `{s}` is `{s}`", .{ kv.key.str, kv.value.str });
}
std.debug.print("\n", .{});
// iterate over all params
const params = r.parametersToOwnedList(alloc, false) catch unreachable;
defer params.deinit();
for (params.items) |kv| {
std.log.info("Param `{s}` is {any}", .{ kv.key.str, kv.value });
}
// let's get param "one" by name
std.debug.print("\n", .{});
if (r.getParamStr("one", alloc, false)) |maybe_str| {
if (maybe_str) |*s| {
defer s.deinit();
std.log.info("Param one = {s}", .{s.str});
} else {
std.log.info("Param one not found!", .{});
}
}
// since we provided "false" for duplicating strings in the call
// to getParamStr(), there won't be an allocation error
else |err| {
std.log.err("cannot check for `one` param: {any}\n", .{err});
}
// check if we received a terminate=true parameter
if (r.getParamStr("terminate", alloc, false)) |maybe_str| {
if (maybe_str) |*s| {
defer s.deinit();
if (std.mem.eql(u8, s.str, "true")) {
zap.fio_stop();
}
}
} else |err| {
std.log.err("cannot check for terminate param: {any}\n", .{err});
}
}
};
Handler.alloc = allocator;
// setup listener
var listener = zap.SimpleHttpListener.init(
.{
.port = 3000,
.on_request = Handler.on_request,
.log = false,
.max_clients = 10,
.max_body_size = 1 * 1024,
},
);
zap.enableDebugLog();
try listener.listen();
std.log.info("\n\nTerminate with CTRL+C or by sending query param terminate=true\n", .{});
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3000/?one=1&two=2&string=hello+world&float=6.28&bool=true");
defer thread.join();
zap.start(.{
.threads = 1,
.workers = 0,
});
}

View file

@ -74,10 +74,22 @@ pub const struct_fio_str_info_s = extern struct {
};
pub const fio_str_info_s = struct_fio_str_info_s;
pub extern fn http_send_body(h: [*c]http_s, data: ?*anyopaque, length: usize) c_int;
pub fn fiobj_each1(arg_o: FIOBJ, arg_start_at: usize, arg_task: ?*const fn (FIOBJ, ?*anyopaque) callconv(.C) c_int, arg_arg: ?*anyopaque) callconv(.C) usize {
var o = arg_o;
var start_at = arg_start_at;
var task = arg_task;
var arg = arg_arg;
if ((((o != 0) and ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) == @bitCast(c_ulong, @as(c_long, @as(c_int, 0))))) and ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 6)))) != @bitCast(c_ulong, @as(c_long, @as(c_int, 6))))) and (fiobj_type_vtable(o).*.each != null)) return fiobj_type_vtable(o).*.each.?(o, start_at, task, arg);
return 0;
}
pub extern fn fiobj_hash_new() FIOBJ;
pub extern fn fiobj_hash_set(hash: FIOBJ, key: FIOBJ, obj: FIOBJ) c_int;
pub extern fn fiobj_hash_get(hash: FIOBJ, key: FIOBJ) FIOBJ;
pub extern fn fiobj_hash_pop(hash: FIOBJ, key: [*c]FIOBJ) FIOBJ;
pub extern fn fiobj_hash_count(hash: FIOBJ) usize;
pub extern fn fiobj_hash_key_in_loop() FIOBJ;
pub extern fn fiobj_hash_haskey(hash: FIOBJ, key: FIOBJ) c_int;
pub extern fn fiobj_ary_push(ary: FIOBJ, obj: FIOBJ) void;
pub extern fn fiobj_float_new(num: f64) FIOBJ;
pub extern fn fiobj_num_new_bignum(num: isize) FIOBJ;
@ -210,6 +222,22 @@ pub fn fiobj_type_vtable(arg_o: FIOBJ) callconv(.C) [*c]const fiobj_object_vtabl
}
return null;
}
pub fn fiobj_obj2num(o: FIOBJ) callconv(.C) isize {
if ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) != 0) {
const sign: usize = if ((o & ~(~@bitCast(usize, @as(c_long, @as(c_int, 0))) >> @intCast(@import("std").math.Log2Int(usize), 1))) != 0) ~(~@bitCast(usize, @as(c_long, @as(c_int, 0))) >> @intCast(@import("std").math.Log2Int(usize), 1)) | (~(~@bitCast(usize, @as(c_long, @as(c_int, 0))) >> @intCast(@import("std").math.Log2Int(usize), 1)) >> @intCast(@import("std").math.Log2Int(usize), 1)) else @bitCast(c_ulong, @as(c_long, @as(c_int, 0)));
return @bitCast(isize, ((o & (~@bitCast(usize, @as(c_long, @as(c_int, 0))) >> @intCast(@import("std").math.Log2Int(usize), 1))) >> @intCast(@import("std").math.Log2Int(c_ulong), 1)) | sign);
}
if (!(o != 0) or !(((o != 0) and ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) == @bitCast(c_ulong, @as(c_long, @as(c_int, 0))))) and ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 6)))) != @bitCast(c_ulong, @as(c_long, @as(c_int, 6)))))) return @bitCast(isize, @as(c_long, @boolToInt(o == @bitCast(c_ulong, @as(c_long, FIOBJ_T_TRUE)))));
return fiobj_type_vtable(o).*.to_i.?(o);
}
pub fn fiobj_obj2float(o: FIOBJ) callconv(.C) f64 {
if ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 1)))) != 0) return @intToFloat(f64, fiobj_obj2num(o));
// the below doesn't parse and we don't support ints here anyway
// if (!(o != 0) or ((o & @bitCast(c_ulong, @as(c_long, @as(c_int, 6)))) == @bitCast(c_ulong, @as(c_long, @as(c_int, 6))))) return @intToFloat(f64, o == @bitCast(c_ulong, @as(c_long, FIOBJ_T_TRUE)));
return fiobj_type_vtable(o).*.to_f.?(o);
}
pub extern fn fio_ltocstr(c_long) fio_str_info_s;
pub fn fiobj_obj2cstr(o: FIOBJ) callconv(.C) fio_str_info_s {
if (!(o != 0)) {

View file

@ -1,9 +1,13 @@
const std = @import("std");
const Authenticators = @import("http_auth.zig");
const zap = @import("zap.zig");
const Endpoints = @import("endpoint.zig");
const fio = @import("fio.zig");
const util = @import("util.zig");
const zap = @import("zap");
// const Authenticators = @import("http_auth.zig");
const Authenticators = zap;
const Endpoints = zap;
// const Endpoints = @import("endpoint.zig");
const fio = zap;
// const fio = @import("fio.zig");
const util = zap;
// const util = @import("util.zig");
test "BearerAuthSingle authenticate" {
const a = std.testing.allocator;

View file

@ -0,0 +1,166 @@
const std = @import("std");
const zap = @import("zap");
fn makeRequest(a: std.mem.Allocator, url: []const u8) !void {
const uri = try std.Uri.parse(url);
var h = std.http.Headers{ .allocator = a };
defer h.deinit();
var http_client: std.http.Client = .{ .allocator = a };
defer http_client.deinit();
var req = try http_client.request(.GET, uri, h, .{});
defer req.deinit();
try req.start();
try req.wait();
zap.fio_stop();
}
fn makeRequestThread(a: std.mem.Allocator, url: []const u8) !std.Thread {
return try std.Thread.spawn(.{}, makeRequest, .{ a, url });
}
test "http parameters" {
var allocator = std.testing.allocator;
const Handler = struct {
var alloc: std.mem.Allocator = undefined;
var ran: bool = false;
var param_count: isize = 0;
var strParams: ?zap.HttpParamStrKVList = null;
var params: ?zap.HttpParamKVList = null;
var paramOneStr: ?zap.FreeOrNot = null;
pub fn on_request(r: zap.SimpleRequest) void {
ran = true;
r.parseQuery();
param_count = r.getParamCount();
// true -> make copies of temp strings
strParams = r.parametersToOwnedStrList(alloc, true) catch unreachable;
// true -> make copies of temp strings
params = r.parametersToOwnedList(alloc, true) catch unreachable;
var maybe_str = r.getParamStr("one", alloc, true) catch unreachable;
if (maybe_str) |*s| {
paramOneStr = s.*;
}
}
};
Handler.alloc = allocator;
// setup listener
var listener = zap.SimpleHttpListener.init(
.{
.port = 3001,
.on_request = Handler.on_request,
.log = false,
.max_clients = 10,
.max_body_size = 1 * 1024,
},
);
zap.enableDebugLog();
try listener.listen();
const thread = try makeRequestThread(allocator, "http://127.0.0.1:3001/?one=1&two=2&string=hello+world&float=6.28&bool=true");
defer thread.join();
zap.start(.{
.threads = 1,
.workers = 0,
});
defer {
if (Handler.strParams) |*p| {
p.deinit();
}
if (Handler.params) |*p| {
p.deinit();
}
if (Handler.paramOneStr) |*p| {
// allocator.free(p);
p.deinit();
}
}
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.strParams != null);
for (Handler.strParams.?.items, 0..) |kv, i| {
switch (i) {
0 => {
try std.testing.expectEqualStrings(kv.key.str, "one");
try std.testing.expectEqualStrings(kv.value.str, "1");
},
1 => {
try std.testing.expectEqualStrings(kv.key.str, "two");
try std.testing.expectEqualStrings(kv.value.str, "2");
},
2 => {
try std.testing.expectEqualStrings(kv.key.str, "string");
try std.testing.expectEqualStrings(kv.value.str, "hello world");
},
3 => {
try std.testing.expectEqualStrings(kv.key.str, "float");
try std.testing.expectEqualStrings(kv.value.str, "6.28");
},
4 => {
try std.testing.expectEqualStrings(kv.key.str, "bool");
try std.testing.expectEqualStrings(kv.value.str, "true");
},
else => return error.TooManyArgs,
}
}
for (Handler.params.?.items, 0..) |kv, i| {
switch (i) {
0 => {
try std.testing.expectEqualStrings(kv.key.str, "one");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.Int => |n| try std.testing.expectEqual(n, 1),
else => return error.InvalidHttpParamType,
}
},
1 => {
try std.testing.expectEqualStrings(kv.key.str, "two");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.Int => |n| try std.testing.expectEqual(n, 2),
else => return error.InvalidHttpParamType,
}
},
2 => {
try std.testing.expectEqualStrings(kv.key.str, "string");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.String => |s| try std.testing.expectEqualStrings(s.str, "hello world"),
else => return error.InvalidHttpParamType,
}
},
3 => {
try std.testing.expectEqualStrings(kv.key.str, "float");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.Float => |f| try std.testing.expectEqual(f, 6.28),
else => return error.InvalidHttpParamType,
}
},
4 => {
try std.testing.expectEqualStrings(kv.key.str, "bool");
try std.testing.expect(kv.value != null);
switch (kv.value.?) {
.Bool => |b| try std.testing.expectEqual(b, true),
else => return error.InvalidHttpParamType,
}
},
else => return error.TooManyArgs,
}
}
}

View file

@ -1,6 +1,9 @@
const std = @import("std");
const fio = @import("fio.zig");
/// note: since this is called from within request functions, we don't make
/// copies. Also, we return temp memory from fio. -> don't hold on to it outside
/// of a request function
pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
if (o == 0) return null;
const x: fio.fio_str_info_s = fio.fiobj_obj2cstr(o);
@ -9,6 +12,32 @@ pub fn fio2str(o: fio.FIOBJ) ?[]const u8 {
return std.mem.span(x.data);
}
pub const FreeOrNot = struct {
str: []const u8,
freeme: bool,
allocator: ?std.mem.Allocator = null,
pub fn deinit(self: *const @This()) void {
if (self.freeme) {
self.allocator.?.free(self.str);
}
}
};
pub fn fio2strAllocOrNot(o: fio.FIOBJ, a: std.mem.Allocator, always_alloc: bool) !FreeOrNot {
if (o == 0) return .{ .str = "null", .freeme = false };
if (o == fio.FIOBJ_INVALID) return .{ .str = "null", .freeme = false };
return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_TRUE => .{ .str = "true", .freeme = false },
fio.FIOBJ_T_FALSE => .{ .str = "false", .freeme = false },
// according to fio2str above, the orelse should never happen
fio.FIOBJ_T_NUMBER => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a },
fio.FIOBJ_T_FLOAT => .{ .str = try a.dupe(u8, fio2str(o) orelse "null"), .freeme = true, .allocator = a },
// the string comes out of the request, so it is safe to not make a copy
fio.FIOBJ_T_STRING => .{ .str = if (always_alloc) try a.dupe(u8, fio2str(o) orelse "") else fio2str(o) orelse "", .freeme = if (always_alloc) true else false, .allocator = a },
else => .{ .str = "null", .freeme = false },
};
}
pub fn str2fio(s: []const u8) fio.fio_str_info_s {
return .{
.data = toCharPtr(s),

View file

@ -48,11 +48,8 @@ pub const HttpError = error{
HttpSendBody,
HttpSetContentType,
HttpSetHeader,
};
pub const HttpParam = struct {
key: []const u8,
value: []const u8,
HttpParseBody,
HttpIterParams,
};
pub const ContentType = enum {
@ -153,10 +150,6 @@ pub const SimpleRequest = struct {
}
debug("setHeader: ret = {}\n", .{ret});
// Note to self:
// const new_fiobj_str = fio.fiobj_str_new(name.ptr, name.len);
// fio.fiobj_free(new_fiobj_str);
if (ret == 0) return;
return error.HttpSetHeader;
}
@ -169,20 +162,263 @@ pub const SimpleRequest = struct {
self.h.*.status = @intCast(usize, @enumToInt(status));
}
pub fn nextParam(self: *const Self) ?HttpParam {
/// Attempts to decode the request's body.
/// This should be called BEFORE parseQuery
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
///
/// Supported body types:
/// - application/x-www-form-urlencoded
/// - application/json
/// - multipart/form-data
pub fn parseBody(self: *const Self) HttpError!void {
if (fio.http_parse_body(self.h) == -1) return error.HttpParseBody;
}
/// Parses the query part of an HTTP request
/// This should be called AFTER parseBody(), just in case the body is a JSON
/// object that doesn't have a hash map at its root.
///
/// Result is accessible via parametersToOwnedSlice(), parametersToOwnedStrSlice()
pub fn parseQuery(self: *const Self) void {
return fio.http_parse_query(self.h);
}
/// not implemented.
pub fn parseCookies() !void {}
/// not implemented.
pub fn getCookie(name: []const u8) ?[]const u8 {
_ = name;
}
/// Returns the number of parameters after parsing.
///
/// Parse with parseBody() and / or parseQuery()
pub fn getParamCount(self: *const Self) isize {
if (self.h.*.params == 0) return 0;
return fio.fiobj_obj2num(self.h.*.params);
}
/// Returns the query / body parameters as key/value pairs, as strings.
/// Supported param types that will be converted:
///
/// - Bool
/// - Int
/// - Float
/// - String
///
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
/// So, for JSON body payloads: parse the body instead.
///
/// Requires parseBody() and/or parseQuery() have been called.
/// Returned list needs to be deinited.
pub fn parametersToOwnedStrList(self: *const Self, a: std.mem.Allocator, always_alloc: bool) anyerror!HttpParamStrKVList {
var params = try std.ArrayList(HttpParamStrKV).initCapacity(a, @intCast(usize, self.getParamCount()));
var context: _parametersToOwnedStrSliceContext = .{
.params = &params,
.allocator = a,
.always_alloc = always_alloc,
};
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParamStr, &context);
if (howmany != self.getParamCount()) {
return error.HttpIterParams;
}
return .{ .items = try params.toOwnedSlice(), .allocator = a };
}
const _parametersToOwnedStrSliceContext = struct {
allocator: std.mem.Allocator,
params: *std.ArrayList(HttpParamStrKV),
last_error: ?anyerror = null,
always_alloc: bool,
};
fn _each_nextParamStr(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
const ctx: *_parametersToOwnedStrSliceContext = @ptrCast(*_parametersToOwnedStrSliceContext, @alignCast(@alignOf(*_parametersToOwnedStrSliceContext), context));
// this is thread-safe, guaranteed by fio
var fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{
.key = util.fio2strAllocOrNot(fiobj_key, ctx.allocator, ctx.always_alloc) catch |err| {
ctx.last_error = err;
return -1;
},
.value = util.fio2strAllocOrNot(fiobj_value, ctx.allocator, ctx.always_alloc) catch |err| {
ctx.last_error = err;
return -1;
},
}) catch |err| {
// what to do?
// signal the caller that an error occured by returning -1
// also, set the error
ctx.last_error = err;
return -1;
};
return 0;
}
/// Returns the query / body parameters as key/value pairs
/// Supported param types that will be converted:
///
/// - Bool
/// - Int
/// - Float
/// - String
///
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
/// So, for JSON body payloads: parse the body instead.
///
/// Requires parseBody() and/or parseQuery() have been called.
/// Returned slice needs to be freed.
pub fn parametersToOwnedList(self: *const Self, a: std.mem.Allocator, dupe_strings: bool) !HttpParamKVList {
var params = try std.ArrayList(HttpParamKV).initCapacity(a, @intCast(usize, self.getParamCount()));
var context: _parametersToOwnedSliceContext = .{ .params = &params, .allocator = a, .dupe_strings = dupe_strings };
const howmany = fio.fiobj_each1(self.h.*.params, 0, _each_nextParam, &context);
if (howmany != self.getParamCount()) {
return error.HttpIterParams;
}
return .{ .items = try params.toOwnedSlice(), .allocator = a };
}
const _parametersToOwnedSliceContext = struct {
params: *std.ArrayList(HttpParamKV),
last_error: ?anyerror = null,
allocator: std.mem.Allocator,
dupe_strings: bool,
};
fn _each_nextParam(fiobj_value: fio.FIOBJ, context: ?*anyopaque) callconv(.C) c_int {
const ctx: *_parametersToOwnedSliceContext = @ptrCast(*_parametersToOwnedSliceContext, @alignCast(@alignOf(*_parametersToOwnedSliceContext), context));
// this is thread-safe, guaranteed by fio
var fiobj_key: fio.FIOBJ = fio.fiobj_hash_key_in_loop();
ctx.params.append(.{
.key = util.fio2strAllocOrNot(fiobj_key, ctx.allocator, ctx.dupe_strings) catch |err| {
ctx.last_error = err;
return -1;
},
.value = Fiobj2HttpParam(fiobj_value, ctx.allocator, ctx.dupe_strings) catch |err| {
ctx.last_error = err;
return -1;
},
}) catch |err| {
// what to do?
// signal the caller that an error occured by returning -1
// also, set the error
ctx.last_error = err;
return -1;
};
return 0;
}
/// get named parameter as string
/// Supported param types that will be converted:
///
/// - Bool
/// - Int
/// - Float
/// - String
///
/// At the moment, no fio ARRAYs are supported as well as HASH maps.
/// So, for JSON body payloads: parse the body instead.
///
/// Requires parseBody() and/or parseQuery() have been called.
/// The returned string needs to be deinited with .deinit()
pub fn getParamStr(self: *const Self, name: []const u8, a: std.mem.Allocator, always_alloc: bool) !?util.FreeOrNot {
if (self.h.*.params == 0) return null;
var key: fio.FIOBJ = undefined;
const value = fio.fiobj_hash_pop(self.h.*.params, &key);
const key = fio.fiobj_str_new(name.ptr, name.len);
defer fio.fiobj_free_wrapped(key);
const value = fio.fiobj_hash_get(self.h.*.params, key);
if (value == fio.FIOBJ_INVALID) {
return null;
}
return HttpParam{
.key = util.fio2str(key).?,
.value = util.fio2str(value).?,
};
return try util.fio2strAllocOrNot(value, a, always_alloc);
}
};
/// Key value pair of strings from HTTP parameters
pub const HttpParamStrKV = struct {
key: util.FreeOrNot,
value: util.FreeOrNot,
pub fn deinit(self: *@This()) void {
self.key.deinit();
self.value.deinit();
}
};
pub const HttpParamStrKVList = struct {
items: []HttpParamStrKV,
allocator: std.mem.Allocator,
pub fn deinit(self: *@This()) void {
for (self.items) |*item| {
item.deinit();
}
self.allocator.free(self.items);
}
};
pub const HttpParamKVList = struct {
items: []HttpParamKV,
allocator: std.mem.Allocator,
pub fn deinit(self: *const @This()) void {
for (self.items) |*item| {
item.deinit();
}
self.allocator.free(self.items);
}
};
pub const HttpParamValueType = enum {
// Null,
Bool,
Int,
Float,
String,
Unsupported,
Unsupported_Hash,
Unsupported_Array,
};
pub const HttpParam = union(HttpParamValueType) {
Bool: bool,
Int: isize,
Float: f64,
/// we don't do writable strings here
String: util.FreeOrNot,
/// value will always be null
Unsupported: ?void,
/// value will always be null
Unsupported_Hash: ?void,
/// value will always be null
Unsupported_Array: ?void,
};
pub const HttpParamKV = struct {
key: util.FreeOrNot,
value: ?HttpParam,
pub fn deinit(self: *@This()) void {
self.key.deinit();
if (self.value) |p| {
switch (p) {
.String => |*s| s.deinit(),
else => {},
}
}
}
};
pub fn Fiobj2HttpParam(o: fio.FIOBJ, a: std.mem.Allocator, dupe_string: bool) !?HttpParam {
return switch (fio.fiobj_type(o)) {
fio.FIOBJ_T_NULL => null,
fio.FIOBJ_T_TRUE => .{ .Bool = true },
fio.FIOBJ_T_FALSE => .{ .Bool = false },
fio.FIOBJ_T_NUMBER => .{ .Int = fio.fiobj_obj2num(o) },
fio.FIOBJ_T_FLOAT => .{ .Float = fio.fiobj_obj2float(o) },
fio.FIOBJ_T_STRING => .{ .String = try util.fio2strAllocOrNot(o, a, dupe_string) },
fio.FIOBJ_T_ARRAY => .{ .Unsupported_Array = null },
fio.FIOBJ_T_HASH => .{ .Unsupported_Hash = null },
else => .{ .Unsupported = null },
};
}
pub const HttpRequestFn = *const fn (r: [*c]fio.http_s) callconv(.C) void;
pub const SimpleHttpRequestFn = *const fn (SimpleRequest) void;