97 lines
3.4 KiB
Zig
97 lines
3.4 KiB
Zig
|
const std = @import("std");
|
||
|
|
||
|
pub fn encode(obj: anytype, writer: anytype, options: anytype) !void {
|
||
|
try encodeStruct("", obj, writer, options);
|
||
|
}
|
||
|
|
||
|
fn encodeStruct(parent: []const u8, obj: anytype, writer: anytype, options: anytype) !void {
|
||
|
var first = true;
|
||
|
inline for (@typeInfo(@TypeOf(obj)).Struct.fields) |field| {
|
||
|
const field_name = if (@hasField(@TypeOf(options), "field_name_transformer")) try options.field_name_transformer.transform(field.name) else field.name;
|
||
|
defer {
|
||
|
if (@hasField(@TypeOf(options), "field_name_transformer"))
|
||
|
options.field_name_transformer.transform_deinit(field_name);
|
||
|
}
|
||
|
if (!first) _ = try writer.write("&");
|
||
|
switch (@typeInfo(field.field_type)) {
|
||
|
.Struct => {
|
||
|
try encodeStruct(field_name ++ ".", @field(obj, field.name), writer);
|
||
|
},
|
||
|
else => try writer.print("{s}{s}={s}", .{ parent, field_name, @field(obj, field.name) }),
|
||
|
}
|
||
|
first = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn testencode(expected: []const u8, value: anytype, options: anytype) !void {
|
||
|
const ValidationWriter = struct {
|
||
|
const Self = @This();
|
||
|
pub const Writer = std.io.Writer(*Self, Error, write);
|
||
|
pub const Error = error{
|
||
|
TooMuchData,
|
||
|
DifferentData,
|
||
|
};
|
||
|
|
||
|
expected_remaining: []const u8,
|
||
|
|
||
|
fn init(exp: []const u8) Self {
|
||
|
return .{ .expected_remaining = exp };
|
||
|
}
|
||
|
|
||
|
pub fn writer(self: *Self) Writer {
|
||
|
return .{ .context = self };
|
||
|
}
|
||
|
|
||
|
fn write(self: *Self, bytes: []const u8) Error!usize {
|
||
|
// std.debug.print("{s}", .{bytes});
|
||
|
if (self.expected_remaining.len < bytes.len) {
|
||
|
std.debug.warn(
|
||
|
\\====== expected this output: =========
|
||
|
\\{s}
|
||
|
\\======== instead found this: =========
|
||
|
\\{s}
|
||
|
\\======================================
|
||
|
, .{
|
||
|
self.expected_remaining,
|
||
|
bytes,
|
||
|
});
|
||
|
return error.TooMuchData;
|
||
|
}
|
||
|
if (!std.mem.eql(u8, self.expected_remaining[0..bytes.len], bytes)) {
|
||
|
std.debug.warn(
|
||
|
\\====== expected this output: =========
|
||
|
\\{s}
|
||
|
\\======== instead found this: =========
|
||
|
\\{s}
|
||
|
\\======================================
|
||
|
, .{
|
||
|
self.expected_remaining[0..bytes.len],
|
||
|
bytes,
|
||
|
});
|
||
|
return error.DifferentData;
|
||
|
}
|
||
|
self.expected_remaining = self.expected_remaining[bytes.len..];
|
||
|
return bytes.len;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var vos = ValidationWriter.init(expected);
|
||
|
try encode(value, vos.writer(), options);
|
||
|
if (vos.expected_remaining.len > 0) return error.NotEnoughData;
|
||
|
}
|
||
|
|
||
|
test "can url encode an object" {
|
||
|
try testencode(
|
||
|
"Action=GetCallerIdentity&Version=2021-01-01",
|
||
|
.{ .Action = "GetCallerIdentity", .Version = "2021-01-01" },
|
||
|
.{},
|
||
|
);
|
||
|
}
|
||
|
test "can url encode a complex object" {
|
||
|
try testencode(
|
||
|
"Action=GetCallerIdentity&Version=2021-01-01&complex.innermember=foo",
|
||
|
.{ .Action = "GetCallerIdentity", .Version = "2021-01-01", .complex = .{ .innermember = "foo" } },
|
||
|
.{},
|
||
|
);
|
||
|
}
|