add header parsing support

This commit is contained in:
Emil Lerch 2022-06-05 18:26:42 -07:00
parent 723c483544
commit 21b04317bd
Signed by: lobo
GPG Key ID: A7B62D657EF764F8

View File

@ -4,6 +4,7 @@ const awshttp = @import("aws_http.zig");
const json = @import("json.zig"); const json = @import("json.zig");
const url = @import("url.zig"); const url = @import("url.zig");
const case = @import("case.zig"); const case = @import("case.zig");
const date = @import("date.zig");
const servicemodel = @import("servicemodel.zig"); const servicemodel = @import("servicemodel.zig");
const xml_shaper = @import("xml_shaper.zig"); const xml_shaper = @import("xml_shaper.zig");
@ -263,31 +264,78 @@ pub fn Request(comptime action: anytype) type {
return error.HttpFailure; return error.HttpFailure;
} }
var fullResponse = try getFullResponseFromBody(aws_request, response, options); var full_response = try getFullResponseFromBody(aws_request, response, options);
errdefer full_response.deinit();
// Fill in any fields that require a header. Note doing it post-facto // Fill in any fields that require a header. Note doing it post-facto
// assumes all response header fields are optional, which may be incorrect // assumes all response header fields are optional, which may be incorrect
if (@hasDecl(action.Response, "http_header")) { if (@hasDecl(action.Response, "http_header")) {
inline for (std.meta.fields(@TypeOf(action.Response.http_header))) |f| { log.debug("Checking headers based on type: {s}", .{@typeName(action.Response)});
const header_name = @field(action.Response.http_header, f.name); const HeaderInfo = struct {
for (response.headers) |h| { name: []const u8,
if (std.ascii.eqlIgnoreCase(h.name, header_name)) { T: type,
log.debug("Response header {s} configured for field. Setting {s} = {s}", .{ h.name, f.name, h.value }); header_name: []const u8,
const field_type = @TypeOf(@field(fullResponse.response, f.name)); };
// TODO: Fix this. We need to make this much more robust comptime var fields = [_]?HeaderInfo{null} ** std.meta.fields(@TypeOf(action.Response.http_header)).len;
// The deal is we have to do the dupe though inline for (std.meta.fields(@TypeOf(action.Response.http_header))) |f, inx| {
// Also, this is a memory leak atm fields[inx] = HeaderInfo{
if (field_type == ?[]const u8) { .name = f.name,
@field(fullResponse.response, f.name) = try options.client.allocator.dupe(u8, (try coerceFromString(field_type, h.value)).?); .T = @TypeOf(@field(full_response.response, f.name)),
} else { .header_name = @field(action.Response.http_header, f.name),
@field(fullResponse.response, f.name) = try coerceFromString(field_type, h.value); };
} }
inline for (fields) |f| {
for (response.headers) |header| {
if (std.mem.eql(u8, header.name, f.?.header_name)) {
log.debug("Response header {s} configured for field. Setting {s} = {s}", .{ header.name, f.?.name, header.value });
// TODO: Revisit return for this function. At the moment, there
// is something in the compiler that is causing the inline for
// surrounding this to start repeating elements
//
// https://github.com/ziglang/zig/issues/10507
//
// This bug is also relevant to some of the many,
// many different methods used to try to work around:
// https://github.com/ziglang/zig/issues/10029
//
// Note: issues found on zig 0.9.0
setHeaderValue(
options.client.allocator,
&full_response.response,
f.?.name,
f.?.T,
header.value,
) catch |e| {
log.err("Could not set header value: Response header {s}. Field {s}. Value {s}", .{ header.name, f.?.name, header.value });
log.err("Error: {}", .{e});
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
};
break; break;
} }
} }
} }
} }
return fullResponse; return full_response;
}
fn setHeaderValue(
allocator: std.mem.Allocator,
response: anytype,
comptime field_name: []const u8,
comptime field_type: type,
value: []const u8,
) !void {
// TODO: Fix this. We need to make this much more robust
// The deal is we have to do the dupe though
// Also, this is a memory leak atm
if (field_type == ?[]const u8) {
@field(response, field_name) = try allocator.dupe(u8, value);
} else {
@field(response, field_name) = try coerceFromString(field_type, value);
}
} }
fn getFullResponseFromBody(aws_request: awshttp.HttpRequest, response: awshttp.HttpResult, options: Options) !FullResponseType { fn getFullResponseFromBody(aws_request: awshttp.HttpRequest, response: awshttp.HttpResult, options: Options) !FullResponseType {
@ -568,15 +616,31 @@ pub fn Request(comptime action: anytype) type {
}; };
} }
fn coerceFromString(comptime T: type, val: []const u8) !T { fn coerceFromString(comptime T: type, val: []const u8) anyerror!T {
if (@typeInfo(T) == .Optional) return try coerceFromString(@typeInfo(T).Optional.child, val); if (@typeInfo(T) == .Optional) return try coerceFromString(@typeInfo(T).Optional.child, val);
// TODO: This is terrible...fix it // TODO: This is terrible...fix it
switch (T) { switch (T) {
bool => return std.ascii.eqlIgnoreCase(val, "true"), bool => return std.ascii.eqlIgnoreCase(val, "true"),
i64 => return try std.fmt.parseInt(T, val, 10), i64 => return parseInt(T, val) catch |e| {
log.err("Invalid string representing i64: {s}", .{val});
return e;
},
else => return val, else => return val,
} }
} }
fn parseInt(comptime T: type, val: []const u8) !T {
const rc = std.fmt.parseInt(T, val, 10);
if (!std.meta.isError(rc)) return rc;
if (T == i64) {
return date.parseEnglishToTimestamp(val) catch |e| {
log.err("Error coercing date string '{s}' to timestamp value", .{val});
return e;
};
}
log.err("Error parsing string '{s}' to integer", .{val});
return rc;
}
fn generalAllocPrint(allocator: std.mem.Allocator, val: anytype) !?[]const u8 { fn generalAllocPrint(allocator: std.mem.Allocator, val: anytype) !?[]const u8 {
switch (@typeInfo(@TypeOf(val))) { switch (@typeInfo(@TypeOf(val))) {