aws-sdk-for-zig/src/url.zig
Emil Lerch dcfefa4072
address zig changes in awshttp. zig 15a030ef3 or newer needed
There are two recent changes in zig that effect awshttp.

1. cf65ab8 disallows unused variables. Fair enough and backward
compatible.
2. https://github.com/ziglang/zig/issues/2115#issuecomment-827968279

This comment resulted in a backward incompatible change to use the
underlying value from a C enum rather than its symbol. This reduces
edge cases in the compiler. Ultimately we may want awshttp to define
zig enums that mirror the C enums, but for now I've commented the
definitions of the C enums used.
2021-06-30 09:14:28 -07:00

178 lines
6.7 KiB
Zig

const std = @import("std");
fn defaultTransformer(field_name: []const u8, _: EncodingOptions) anyerror![]const u8 {
return field_name;
}
pub const FieldNameTransformer = fn ([]const u8, EncodingOptions) anyerror![]const u8;
pub const EncodingOptions = struct {
allocator: ?*std.mem.Allocator = null,
field_name_transformer: *const FieldNameTransformer = &defaultTransformer,
};
pub fn encode(obj: anytype, writer: anytype, options: EncodingOptions) !void {
_ = try encodeInternal("", "", true, obj, writer, options);
}
fn encodeStruct(parent: []const u8, first: bool, obj: anytype, writer: anytype, options: EncodingOptions) !bool {
var rc = first;
inline for (@typeInfo(@TypeOf(obj)).Struct.fields) |field| {
const field_name = try options.field_name_transformer.*(field.name, options);
defer if (options.field_name_transformer.* != defaultTransformer)
if (options.allocator) |a| a.free(field_name);
// @compileLog(@typeInfo(field.field_type).Pointer);
rc = try encodeInternal(parent, field_name, rc, @field(obj, field.name), writer, options);
}
return rc;
}
pub fn encodeInternal(parent: []const u8, field_name: []const u8, first: bool, obj: anytype, writer: anytype, options: EncodingOptions) !bool {
// @compileLog(@typeInfo(@TypeOf(obj)));
var rc = first;
switch (@typeInfo(@TypeOf(obj))) {
.Optional => if (obj) |o| {
rc = try encodeInternal(parent, field_name, first, o, writer, options);
},
.Pointer => |ti| if (ti.size == .One) {
rc = try encodeInternal(parent, field_name, first, obj.*, writer, options);
} else {
if (!first) _ = try writer.write("&");
try writer.print("{s}{s}={s}", .{ parent, field_name, obj });
rc = false;
},
.Struct => if (std.mem.eql(u8, "", field_name)) {
rc = try encodeStruct(parent, first, obj, writer, options);
} else {
// TODO: It would be lovely if we could concat at compile time or allocPrint at runtime
// XOR have compile time allocator support. Alas, neither are possible:
// https://github.com/ziglang/zig/issues/868: Comptime detection (feels like foot gun)
// https://github.com/ziglang/zig/issues/1291: Comptime allocator
const allocator = options.allocator orelse return error.AllocatorRequired;
const new_parent = try std.fmt.allocPrint(allocator, "{s}{s}.", .{ parent, field_name });
defer allocator.free(new_parent);
rc = try encodeStruct(new_parent, first, obj, writer, options);
// try encodeStruct(parent ++ field_name ++ ".", first, obj, writer, options);
},
.Array => {
if (!first) _ = try writer.write("&");
try writer.print("{s}{s}={s}", .{ parent, field_name, obj });
rc = false;
},
.Int, .ComptimeInt, .Float, .ComptimeFloat => {
if (!first) _ = try writer.write("&");
try writer.print("{s}{s}={d}", .{ parent, field_name, obj });
rc = false;
},
// BUGS! any doesn't work - a lot. Check this out:
// https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig#L424
else => {
if (!first) _ = try writer.write("&");
try writer.print("{s}{s}={any}", .{ parent, field_name, obj });
rc = false;
},
}
return rc;
}
fn testencode(expected: []const u8, value: anytype, options: EncodingOptions) !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}\n", .{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 urlencode an object" {
try testencode(
"Action=GetCallerIdentity&Version=2021-01-01",
.{ .Action = "GetCallerIdentity", .Version = "2021-01-01" },
.{},
);
}
test "can urlencode an object with integer" {
try testencode(
"Action=GetCallerIdentity&Duration=32",
.{ .Action = "GetCallerIdentity", .Duration = 32 },
.{},
);
}
const UnsetValues = struct {
action: ?[]const u8 = null,
duration: ?i64 = null,
val1: ?i64 = null,
val2: ?[]const u8 = null,
};
test "can urlencode an object with unset values" {
// var buffer = std.ArrayList(u8).init(std.testing.allocator);
// defer buffer.deinit();
// const writer = buffer.writer();
// try encode(
// UnsetValues{ .action = "GetCallerIdentity", .duration = 32 },
// writer,
// .{ .allocator = std.testing.allocator },
// );
// std.debug.print("{s}", .{buffer.items});
try testencode(
"action=GetCallerIdentity&duration=32",
UnsetValues{ .action = "GetCallerIdentity", .duration = 32 },
.{},
);
}
test "can urlencode a complex object" {
try testencode(
"Action=GetCallerIdentity&Version=2021-01-01&complex.innermember=foo",
.{ .Action = "GetCallerIdentity", .Version = "2021-01-01", .complex = .{ .innermember = "foo" } },
.{ .allocator = std.testing.allocator },
);
}