Remove extra fields in request (AWS is sensitive to them)

This commit is contained in:
Emil Lerch 2024-08-27 10:40:52 -07:00
parent f5663fd84d
commit c5cb3dde29
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 93 additions and 30 deletions

View File

@ -126,12 +126,15 @@ pub fn Request(comptime request_action: anytype) type {
log.debug("Rest method: '{s}'", .{aws_request.method});
log.debug("Rest success code: '{d}'", .{Action.http_config.success_code});
log.debug("Rest raw uri: '{s}'", .{Action.http_config.uri});
var al = std.ArrayList([]const u8).init(options.client.allocator);
defer al.deinit();
aws_request.path = try buildPath(
options.client.allocator,
Action.http_config.uri,
ActionRequest,
request,
!std.mem.eql(u8, Self.service_meta.sdk_id, "S3"),
&al,
);
defer options.client.allocator.free(aws_request.path);
log.debug("Rest processed uri: '{s}'", .{aws_request.path});
@ -163,7 +166,7 @@ pub fn Request(comptime request_action: anytype) type {
defer nameAllocator.deinit();
if (Self.service_meta.aws_protocol == .rest_json_1) {
if (std.mem.eql(u8, "PUT", aws_request.method) or std.mem.eql(u8, "POST", aws_request.method)) {
try json.stringify(request, .{ .whitespace = .{} }, buffer.writer());
try json.stringify(request, .{ .whitespace = .{}, .emit_null = false, .exclude_fields = al.items }, buffer.writer());
}
}
aws_request.body = buffer.items;
@ -944,6 +947,7 @@ fn buildPath(
comptime ActionRequest: type,
request: anytype,
encode_slash: bool,
replaced_fields: *std.ArrayList([]const u8),
) ![]const u8 {
var buffer = try std.ArrayList(u8).initCapacity(allocator, raw_uri.len);
// const writer = buffer.writer();
@ -965,6 +969,7 @@ fn buildPath(
const replacement_label = raw_uri[start..end];
inline for (std.meta.fields(ActionRequest)) |field| {
if (std.mem.eql(u8, request.fieldNameFor(field.name), replacement_label)) {
try replaced_fields.append(replacement_label);
var replacement_buffer = try std.ArrayList(u8).initCapacity(allocator, raw_uri.len);
defer replacement_buffer.deinit();
var encoded_buffer = try std.ArrayList(u8).initCapacity(allocator, raw_uri.len);
@ -1254,23 +1259,27 @@ test "REST Json v1 serializes lists in queries" {
}
test "REST Json v1 buildpath substitutes" {
const allocator = std.testing.allocator;
var al = std.ArrayList([]const u8).init(allocator);
defer al.deinit();
const svs = Services(.{.lambda}){};
const request = svs.lambda.list_functions.Request{
.max_items = 1,
};
const input_path = "https://myhost/{MaxItems}/";
const output_path = try buildPath(allocator, input_path, @TypeOf(request), request, true);
const output_path = try buildPath(allocator, input_path, @TypeOf(request), request, true, &al);
defer allocator.free(output_path);
try std.testing.expectEqualStrings("https://myhost/1/", output_path);
}
test "REST Json v1 buildpath handles restricted characters" {
const allocator = std.testing.allocator;
var al = std.ArrayList([]const u8).init(allocator);
defer al.deinit();
const svs = Services(.{.lambda}){};
const request = svs.lambda.list_functions.Request{
.marker = ":",
};
const input_path = "https://myhost/{Marker}/";
const output_path = try buildPath(allocator, input_path, @TypeOf(request), request, true);
const output_path = try buildPath(allocator, input_path, @TypeOf(request), request, true, &al);
defer allocator.free(output_path);
try std.testing.expectEqualStrings("https://myhost/%3A/", output_path);
}
@ -1970,7 +1979,6 @@ test "rest_json_1_work_with_lambda: lambda tagResource (only), to excercise zig
try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
try std.testing.expectEqualStrings(
\\{
\\ "Resource": "arn:aws:lambda:us-west-2:550620852718:function:awsome-lambda-LambdaStackawsomeLambda",
\\ "Tags": {
\\ "Foo": "Bar"
\\ }
@ -1981,6 +1989,45 @@ test "rest_json_1_work_with_lambda: lambda tagResource (only), to excercise zig
// Response expectations
try std.testing.expectEqualStrings("a521e152-6e32-4e67-9fb3-abc94e34551b", call.response_metadata.request_id);
}
test "rest_json_1_url_parameters_not_in_request: lambda update_function_code" {
const allocator = std.testing.allocator;
var test_harness = TestSetup.init(.{
.allocator = allocator,
.server_response = "{\"CodeSize\": 42}",
.server_response_status = .ok,
.server_response_headers = &.{
.{ .name = "Content-Type", .value = "application/json" },
.{ .name = "x-amzn-RequestId", .value = "a521e152-6e32-4e67-9fb3-abc94e34551b" },
},
});
defer test_harness.deinit();
const options = try test_harness.start();
const lambda = (Services(.{.lambda}){}).lambda;
const architectures = [_][]const u8{"x86_64"};
const arches: [][]const u8 = @constCast(architectures[0..]);
const req = services.lambda.update_function_code.Request{
.function_name = "functionname",
.architectures = arches,
.zip_file = "zipfile",
};
const call = try Request(lambda.update_function_code).call(req, options);
defer call.deinit();
test_harness.stop();
// Request expectations
try std.testing.expectEqual(std.http.Method.PUT, test_harness.request_options.request_method);
try std.testing.expectEqualStrings(
\\{
\\ "ZipFile": "zipfile",
\\ "Architectures": [
\\ "x86_64"
\\ ]
\\}
, test_harness.request_options.request_body);
// Due to 17015, we see %253A instead of %3A
try std.testing.expectEqualStrings("/2015-03-31/functions/functionname/code", test_harness.request_options.request_target);
// Response expectations
try std.testing.expectEqualStrings("a521e152-6e32-4e67-9fb3-abc94e34551b", call.response_metadata.request_id);
}
test "ec2_query_no_input: EC2 describe regions" {
const allocator = std.testing.allocator;
var test_harness = TestSetup.init(.{

View File

@ -2756,6 +2756,10 @@ pub const StringifyOptions = struct {
}
};
emit_null: bool = true,
exclude_fields: ?[][]const u8 = null,
/// Controls the whitespace emitted
whitespace: ?Whitespace = null,
@ -2855,7 +2859,7 @@ pub fn stringify(
}
try out_stream.writeByte('{');
comptime var field_output = false;
var field_output = false;
var child_options = options;
if (child_options.whitespace) |*child_whitespace| {
child_whitespace.indent_level += 1;
@ -2864,34 +2868,46 @@ pub fn stringify(
// don't include void fields
if (Field.type == void) continue;
if (!field_output) {
field_output = true;
} else {
try out_stream.writeByte(',');
}
if (child_options.whitespace) |child_whitespace| {
try out_stream.writeByte('\n');
try child_whitespace.outputIndent(out_stream);
}
var field_written = false;
if (comptime std.meta.hasFn(T, "jsonStringifyField"))
field_written = try value.jsonStringifyField(Field.name, child_options, out_stream);
var output_this_field = true;
if (!options.emit_null and @typeInfo(Field.type) == .Optional and @field(value, Field.name) == null) output_this_field = false;
if (!field_written) {
if (comptime std.meta.hasFn(T, "fieldNameFor")) {
const name = value.fieldNameFor(Field.name);
try stringify(name, options, out_stream);
} else {
try stringify(Field.name, options, out_stream);
}
try out_stream.writeByte(':');
if (child_options.whitespace) |child_whitespace| {
if (child_whitespace.separator) {
try out_stream.writeByte(' ');
const final_name = if (comptime std.meta.hasFn(T, "fieldNameFor"))
value.fieldNameFor(Field.name)
else
Field.name;
if (options.exclude_fields) |exclude_fields| {
for (exclude_fields) |exclude_field| {
if (std.mem.eql(u8, final_name, exclude_field)) {
output_this_field = false;
}
}
try stringify(@field(value, Field.name), child_options, out_stream);
}
if (!field_output) {
field_output = output_this_field;
} else {
if (output_this_field) try out_stream.writeByte(',');
}
if (child_options.whitespace) |child_whitespace| {
if (output_this_field) try out_stream.writeByte('\n');
if (output_this_field) try child_whitespace.outputIndent(out_stream);
}
var field_written = false;
if (comptime std.meta.hasFn(T, "jsonStringifyField")) {
if (output_this_field) field_written = try value.jsonStringifyField(Field.name, child_options, out_stream);
}
if (!field_written) {
if (output_this_field) {
try stringify(final_name, options, out_stream);
try out_stream.writeByte(':');
}
if (child_options.whitespace) |child_whitespace| {
if (child_whitespace.separator) {
if (output_this_field) try out_stream.writeByte(' ');
}
}
if (output_this_field) try stringify(@field(value, Field.name), child_options, out_stream);
}
}
if (field_output) {