diff --git a/src/aws.zig b/src/aws.zig
index 9cb804e..8d0fe40 100644
--- a/src/aws.zig
+++ b/src/aws.zig
@@ -1,21 +1,19 @@
const builtin = @import("builtin");
-const case = @import("case");
const std = @import("std");
+
+const case = @import("case");
+const date = @import("date");
+const json = @import("json");
const zeit = @import("zeit");
const awshttp = @import("aws_http.zig");
-const json = @import("json");
const url = @import("url.zig");
-const date = @import("date");
const servicemodel = @import("servicemodel.zig");
const xml_shaper = @import("xml_shaper.zig");
const xml_serializer = @import("xml_serializer.zig");
const scoped_log = std.log.scoped(.aws);
-const Allocator = std.mem.Allocator;
-const ArenaAllocator = std.heap.ArenaAllocator;
-
/// control all logs directly/indirectly used by aws sdk. Not recommended for
/// use under normal circumstances, but helpful for times when the zig logging
/// controls are insufficient (e.g. use in build script)
@@ -95,7 +93,7 @@ pub const Options = struct {
pub const Diagnostics = struct {
http_code: i64,
response_body: []const u8,
- allocator: Allocator,
+ allocator: std.mem.Allocator,
pub fn deinit(self: *Diagnostics) void {
self.allocator.free(self.response_body);
@@ -117,12 +115,12 @@ pub const ClientOptions = struct {
proxy: ?std.http.Client.Proxy = null,
};
pub const Client = struct {
- allocator: Allocator,
+ allocator: std.mem.Allocator,
aws_http: awshttp.AwsHttp,
const Self = @This();
- pub fn init(allocator: Allocator, options: ClientOptions) Self {
+ pub fn init(allocator: std.mem.Allocator, options: ClientOptions) Self {
return Self{
.allocator = allocator,
.aws_http = awshttp.AwsHttp.init(allocator, options.proxy),
@@ -470,7 +468,7 @@ pub fn Request(comptime request_action: anytype) type {
}
fn setHeaderValue(
- allocator: Allocator,
+ allocator: std.mem.Allocator,
response: anytype,
comptime field_name: []const u8,
comptime field_type: type,
@@ -500,7 +498,7 @@ pub fn Request(comptime request_action: anytype) type {
var buf_request_id: [256]u8 = undefined;
const request_id = try requestIdFromHeaders(&buf_request_id, options.client.allocator, aws_request, response);
- const arena = ArenaAllocator.init(options.client.allocator);
+ const arena = std.heap.ArenaAllocator.init(options.client.allocator);
if (@hasDecl(action.Response, "http_payload")) {
var rc = try FullResponseType.init(.{
@@ -556,7 +554,7 @@ pub fn Request(comptime request_action: anytype) type {
}
fn jsonReturn(aws_request: awshttp.HttpRequest, options: Options, response: awshttp.HttpResult) !FullResponseType {
- var arena = ArenaAllocator.init(options.client.allocator);
+ var arena = std.heap.ArenaAllocator.init(options.client.allocator);
const parser_options = json.ParseOptions{
.allocator = arena.allocator(),
@@ -664,7 +662,7 @@ pub fn Request(comptime request_action: anytype) type {
// }
//
// Big thing is that requestid, which we'll need to fetch "manually"
- var arena = ArenaAllocator.init(options.client.allocator);
+ var arena = std.heap.ArenaAllocator.init(options.client.allocator);
const xml_options = xml_shaper.ParseOptions{
.allocator = arena.allocator(),
@@ -769,7 +767,7 @@ pub fn Request(comptime request_action: anytype) type {
fn ParsedJsonData(comptime T: type) type {
return struct {
parsed_response_ptr: *T,
- allocator: Allocator,
+ allocator: std.mem.Allocator,
const MySelf = @This();
@@ -890,7 +888,7 @@ fn parseInt(comptime T: type, val: []const u8) !T {
return rc;
}
-fn generalAllocPrint(allocator: Allocator, val: anytype) !?[]const u8 {
+fn generalAllocPrint(allocator: std.mem.Allocator, val: anytype) !?[]const u8 {
const T = @TypeOf(val);
switch (@typeInfo(T)) {
.optional => if (val) |v| return generalAllocPrint(allocator, v) else return null,
@@ -913,7 +911,7 @@ fn generalAllocPrint(allocator: Allocator, val: anytype) !?[]const u8 {
else => return try std.fmt.allocPrint(allocator, "{any}", .{val}),
}
}
-fn headersFor(allocator: Allocator, request: anytype) ![]awshttp.Header {
+fn headersFor(allocator: std.mem.Allocator, request: anytype) ![]awshttp.Header {
log.debug("Checking for headers to include for type {}", .{@TypeOf(request)});
if (!@hasDecl(@TypeOf(request), "http_header")) return &[_]awshttp.Header{};
const http_header = @TypeOf(request).http_header;
@@ -938,7 +936,7 @@ fn headersFor(allocator: Allocator, request: anytype) ![]awshttp.Header {
return headers.toOwnedSlice(allocator);
}
-fn freeHeadersFor(allocator: Allocator, request: anytype, headers: []const awshttp.Header) void {
+fn freeHeadersFor(allocator: std.mem.Allocator, request: anytype, headers: []const awshttp.Header) void {
if (!@hasDecl(@TypeOf(request), "http_header")) return;
const http_header = @TypeOf(request).http_header;
const fields = std.meta.fields(@TypeOf(http_header));
@@ -999,7 +997,7 @@ fn getContentType(headers: []const awshttp.Header) !ContentType {
}
/// Get request ID from headers.
/// Allocation is only used in case of an error. Caller does not need to free the returned buffer.
-fn requestIdFromHeaders(buf: []u8, allocator: Allocator, request: awshttp.HttpRequest, response: awshttp.HttpResult) ![]u8 {
+fn requestIdFromHeaders(buf: []u8, allocator: std.mem.Allocator, request: awshttp.HttpRequest, response: awshttp.HttpResult) ![]u8 {
var rid: ?[]const u8 = null;
// This "thing" is called:
// * Host ID
@@ -1093,13 +1091,13 @@ fn FullResponse(comptime action: anytype) type {
response: action.Response = undefined,
request_id: []const u8,
raw_parsed: RawParsed = .{ .raw = undefined },
- arena: ArenaAllocator,
+ arena: std.heap.ArenaAllocator,
};
response: action.Response = undefined,
raw_parsed: RawParsed = .{ .raw = undefined },
response_metadata: ResponseMetadata,
- arena: ArenaAllocator,
+ arena: std.heap.ArenaAllocator,
const Self = @This();
@@ -1122,14 +1120,14 @@ fn FullResponse(comptime action: anytype) type {
}
};
}
-fn safeFree(allocator: Allocator, obj: anytype) void {
+fn safeFree(allocator: std.mem.Allocator, obj: anytype) void {
switch (@typeInfo(@TypeOf(obj))) {
.pointer => allocator.free(obj),
.optional => if (obj) |o| safeFree(allocator, o),
else => {},
}
}
-fn queryFieldTransformer(allocator: Allocator, field_name: []const u8) anyerror![]const u8 {
+fn queryFieldTransformer(allocator: std.mem.Allocator, field_name: []const u8) anyerror![]const u8 {
var reader = std.Io.Reader.fixed(field_name);
var aw = try std.Io.Writer.Allocating.initCapacity(allocator, 100);
defer aw.deinit();
@@ -1140,7 +1138,7 @@ fn queryFieldTransformer(allocator: Allocator, field_name: []const u8) anyerror!
}
fn buildPath(
- allocator: Allocator,
+ allocator: std.mem.Allocator,
raw_uri: []const u8,
comptime ActionRequest: type,
request: anytype,
@@ -1228,7 +1226,7 @@ fn uriEncodeByte(char: u8, writer: *std.Io.Writer, encode_slash: bool) !void {
}
}
-fn buildQuery(allocator: Allocator, request: anytype) ![]const u8 {
+fn buildQuery(allocator: std.mem.Allocator, request: anytype) ![]const u8 {
// query should look something like this:
// pub const http_query = .{
// .master_region = "MasterRegion",
@@ -1358,7 +1356,7 @@ const IgnoringWriter = struct {
};
fn reportTraffic(
- allocator: Allocator,
+ allocator: std.mem.Allocator,
info: []const u8,
request: awshttp.HttpRequest,
response: awshttp.HttpResult,
@@ -1391,1399 +1389,9 @@ fn reportTraffic(
reporter("{s}\n", .{msg.written()});
}
-////////////////////////////////////////////////////////////////////////
-// All code below this line is for testing
-////////////////////////////////////////////////////////////////////////
-
-// TODO: Where does this belong really?
-fn typeForField(comptime T: type, comptime field_name: []const u8) !type {
- const ti = @typeInfo(T);
- switch (ti) {
- .@"struct" => {
- inline for (ti.@"struct".fields) |field| {
- if (std.mem.eql(u8, field.name, field_name))
- return field.type;
- }
- },
- else => return error.TypeIsNotAStruct, // should not hit this
- }
- return error.FieldNotFound;
-}
-
-test "custom serialization for map objects" {
- const allocator = std.testing.allocator;
- var buffer = std.Io.Writer.Allocating.init(allocator);
- defer buffer.deinit();
- var tags = try std.ArrayList(@typeInfo(try typeForField(services.lambda.tag_resource.Request, "tags")).pointer.child).initCapacity(allocator, 2);
- defer tags.deinit(allocator);
- tags.appendAssumeCapacity(.{ .key = "Foo", .value = "Bar" });
- tags.appendAssumeCapacity(.{ .key = "Baz", .value = "Qux" });
- const req = services.lambda.TagResourceRequest{ .resource = "hello", .tags = tags.items };
- try buffer.writer.print("{f}", .{std.json.fmt(req, .{ .whitespace = .indent_4 })});
-
- const parsed_body = try std.json.parseFromSlice(struct {
- Resource: []const u8,
- Tags: struct {
- Foo: []const u8,
- Baz: []const u8,
- },
- }, testing.allocator, buffer.written(), .{});
- defer parsed_body.deinit();
-
- try testing.expectEqualStrings("hello", parsed_body.value.Resource);
- try testing.expectEqualStrings("Bar", parsed_body.value.Tags.Foo);
- try testing.expectEqualStrings("Qux", parsed_body.value.Tags.Baz);
-}
-
-test "proper serialization for kms" {
- // Github issue #8
- // https://github.com/elerch/aws-sdk-for-zig/issues/8
- const allocator = std.testing.allocator;
- var buffer = std.Io.Writer.Allocating.init(allocator);
- defer buffer.deinit();
- const req = services.kms.encrypt.Request{
- .encryption_algorithm = "SYMMETRIC_DEFAULT",
- // Since encryption_context is not null, we expect "{}" to be the value
- // here, not "[]", because this is our special AWS map pattern
- .encryption_context = &.{},
- .key_id = "42",
- .plaintext = "foo",
- .dry_run = false,
- .grant_tokens = &[_][]const u8{},
- };
- try buffer.writer.print("{f}", .{std.json.fmt(req, .{ .whitespace = .indent_4 })});
-
- {
- const parsed_body = try std.json.parseFromSlice(struct {
- KeyId: []const u8,
- Plaintext: []const u8,
- EncryptionContext: struct {},
- GrantTokens: [][]const u8,
- EncryptionAlgorithm: []const u8,
- DryRun: bool,
- }, testing.allocator, buffer.written(), .{});
- defer parsed_body.deinit();
-
- try testing.expectEqualStrings("42", parsed_body.value.KeyId);
- try testing.expectEqualStrings("foo", parsed_body.value.Plaintext);
- try testing.expectEqual(0, parsed_body.value.GrantTokens.len);
- try testing.expectEqualStrings("SYMMETRIC_DEFAULT", parsed_body.value.EncryptionAlgorithm);
- try testing.expectEqual(false, parsed_body.value.DryRun);
- }
-
- var buffer_null = std.Io.Writer.Allocating.init(allocator);
- defer buffer_null.deinit();
- const req_null = services.kms.encrypt.Request{
- .encryption_algorithm = "SYMMETRIC_DEFAULT",
- // Since encryption_context here *IS* null, we expect simply "null" to be the value
- .encryption_context = null,
- .key_id = "42",
- .plaintext = "foo",
- .dry_run = false,
- .grant_tokens = &[_][]const u8{},
- };
-
- try buffer_null.writer.print("{f}", .{std.json.fmt(req_null, .{ .whitespace = .indent_4 })});
-
- {
- const parsed_body = try std.json.parseFromSlice(struct {
- KeyId: []const u8,
- Plaintext: []const u8,
- EncryptionContext: ?struct {},
- GrantTokens: [][]const u8,
- EncryptionAlgorithm: []const u8,
- DryRun: bool,
- }, testing.allocator, buffer_null.written(), .{});
- defer parsed_body.deinit();
-
- try testing.expectEqualStrings("42", parsed_body.value.KeyId);
- try testing.expectEqualStrings("foo", parsed_body.value.Plaintext);
- try testing.expectEqual(null, parsed_body.value.EncryptionContext);
- try testing.expectEqual(0, parsed_body.value.GrantTokens.len);
- try testing.expectEqualStrings("SYMMETRIC_DEFAULT", parsed_body.value.EncryptionAlgorithm);
- try testing.expectEqual(false, parsed_body.value.DryRun);
- }
-}
-
-test "REST Json v1 builds proper queries" {
- const allocator = std.testing.allocator;
- const svs = Services(.{.lambda}){};
- const request = svs.lambda.list_functions.Request{
- .max_items = 1,
- };
- const query = try buildQuery(allocator, request);
- defer allocator.free(query);
- try std.testing.expectEqualStrings("?MaxItems=1", query);
-}
-test "REST Json v1 handles reserved chars in queries" {
- const allocator = std.testing.allocator;
- const svs = Services(.{.lambda}){};
- var keys = [_][]const u8{"Foo?I'm a crazy%dude"}; // Would love to have a way to express this without burning a var here
- const request = svs.lambda.untag_resource.Request{
- .tag_keys = keys[0..],
- .resource = "hello",
- };
- const query = try buildQuery(allocator, request);
- defer allocator.free(query);
- try std.testing.expectEqualStrings("?tagKeys=Foo%3FI%27m a crazy%25dude", query);
-}
-test "REST Json v1 serializes lists in queries" {
- const allocator = std.testing.allocator;
- const svs = Services(.{.lambda}){};
- var keys = [_][]const u8{ "Foo", "Bar" }; // Would love to have a way to express this without burning a var here
- const request = svs.lambda.untag_resource.Request{
- .tag_keys = keys[0..],
- .resource = "hello",
- };
- const query = try buildQuery(allocator, request);
- defer allocator.free(query);
- try std.testing.expectEqualStrings("?tagKeys=Foo&tagKeys=Bar", query);
-}
-test "REST Json v1 buildpath substitutes" {
- const allocator = std.testing.allocator;
- var al = std.ArrayList([]const u8){};
- defer al.deinit(allocator);
- 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, &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){};
- defer al.deinit(allocator);
- 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, &al);
- defer allocator.free(output_path);
- try std.testing.expectEqualStrings("https://myhost/%3A/", output_path);
-}
-test "basic json request serialization" {
- const allocator = std.testing.allocator;
- const svs = Services(.{.dynamo_db}){};
- const request = svs.dynamo_db.list_tables.Request{
- .limit = 1,
- };
- var buffer = std.Io.Writer.Allocating.init(allocator);
- defer buffer.deinit();
-
- // The transformer needs to allocate stuff out of band, but we
- // can guarantee we don't need the memory after this call completes,
- // so we'll use an arena allocator to whack everything.
- // TODO: Determine if sending in null values is ok, or if we need another
- // tweak to the stringify function to exclude. According to the
- // smithy spec, "A null value MAY be provided or omitted
- // for a boxed member with no observable difference." But we're
- // seeing a lot of differences here between spec and reality
- //
- try buffer.writer.print("{f}", .{std.json.fmt(request, .{ .whitespace = .indent_4 })});
- try std.testing.expectEqualStrings(
- \\{
- \\ "ExclusiveStartTableName": null,
- \\ "Limit": 1
- \\}
- , buffer.written());
-}
-test "layer object only" {
- const TestResponse = struct {
- arn: ?[]const u8 = null,
- // uncompressed_code_size: ?i64 = null,
-
- pub fn jsonFieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 {
- const mappings = .{
- .arn = "Arn",
- };
- return @field(mappings, field_name);
- }
- };
- const response =
- \\ {
- \\ "UncompressedCodeSize": 2,
- \\ "Arn": "blah"
- \\ }
- ;
- // const response =
- // \\ {
- // \\ "UncompressedCodeSize": 22599541,
- // \\ "Arn": "arn:aws:lambda:us-west-2:123456789012:layer:PollyNotes-lib:4"
- // \\ }
- // ;
- const allocator = std.testing.allocator;
- var stream = json.TokenStream.init(response);
- const parser_options = json.ParseOptions{
- .allocator = allocator,
- .allow_camel_case_conversion = true, // new option
- .allow_snake_case_conversion = true, // new option
- .allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
- .allow_missing_fields = false, // new option. Cannot yet handle non-struct fields though
- };
- const r = try json.parse(TestResponse, &stream, parser_options);
- json.parseFree(TestResponse, r, parser_options);
-}
-
-// Use for debugging json responses of specific requests
-// test "dummy request" {
-// const allocator = std.testing.allocator;
-// const svs = Services(.{.sts}){};
-// const request = svs.sts.get_session_token.Request{
-// .duration_seconds = 900,
-// };
-// const FullR = FullResponse(request);
-// const response =
-// var stream = json.TokenStream.init(response);
-//
-// const parser_options = json.ParseOptions{
-// .allocator = allocator,
-// .allow_camel_case_conversion = true, // new option
-// .allow_snake_case_conversion = true, // new option
-// .allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
-// .allow_missing_fields = false, // new option. Cannot yet handle non-struct fields though
-// };
-// const SResponse = ServerResponse(request);
-// const r = try json.parse(SResponse, &stream, parser_options);
-// json.parseFree(SResponse, r, parser_options);
-
test {
- // To run nested container tests, either, call `refAllDecls` which will
- // reference all declarations located in the given argument.
- // `@This()` is a builtin function that returns the innermost container it is called from.
- // In this example, the innermost container is this file (implicitly a struct).
- std.testing.refAllDecls(@This());
- std.testing.refAllDecls(awshttp);
- std.testing.refAllDecls(json);
- std.testing.refAllDecls(url);
- std.testing.refAllDecls(case);
- std.testing.refAllDecls(date);
- std.testing.refAllDecls(servicemodel);
- std.testing.refAllDecls(xml_shaper);
-}
-const TestOptions = struct {
- allocator: Allocator,
- arena: ?*ArenaAllocator = null,
- server_port: ?u16 = null,
- server_remaining_requests: usize = 1,
- server_response: []const u8 = "unset",
- server_response_status: std.http.Status = .ok,
- server_response_headers: []const std.http.Header = &.{},
- server_response_transfer_encoding: ?std.http.TransferEncoding = null,
- request_body: []u8 = "",
- request_method: std.http.Method = undefined,
- request_target: []const u8 = undefined,
- request_headers: []std.http.Header = undefined,
- test_server_runtime_uri: ?[]u8 = null,
- server_ready: std.Thread.Semaphore = .{},
- requests_processed: usize = 0,
-
- const Self = @This();
-
- /// Builtin hashmap for strings as keys.
- /// Key memory is managed by the caller. Keys and values
- /// will not automatically be freed.
- pub fn StringCaseInsensitiveHashMap(comptime V: type) type {
- return std.HashMap([]const u8, V, StringInsensitiveContext, std.hash_map.default_max_load_percentage);
- }
-
- pub const StringInsensitiveContext = struct {
- pub fn hash(self: @This(), s: []const u8) u64 {
- _ = self;
- return hashString(s);
- }
- pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
- _ = self;
- return eqlString(a, b);
- }
+ _ = @import("aws_test.zig"){
+ .buildQuery = buildQuery,
+ .buildPath = buildPath,
};
-
- pub fn eqlString(a: []const u8, b: []const u8) bool {
- return std.ascii.eqlIgnoreCase(a, b);
- }
-
- pub fn hashString(s: []const u8) u64 {
- var buf: [1024]u8 = undefined;
- if (s.len > buf.len) unreachable; // tolower has a debug assert, but we want non-debug check too
- const lower_s = std.ascii.lowerString(buf[0..], s);
- return std.hash.Wyhash.hash(0, lower_s);
- }
-
- fn expectNoDuplicateHeaders(self: *Self) !void {
- // As header keys are
- var hm = StringCaseInsensitiveHashMap(void).init(self.allocator);
- try hm.ensureTotalCapacity(@intCast(self.request_headers.len));
- defer hm.deinit();
- for (self.request_headers) |h| {
- if (hm.getKey(h.name)) |_| {
- log.err("Duplicate key detected. Key name: {s}", .{h.name});
- return error.duplicateKeyDetected;
- }
- try hm.put(h.name, {});
- }
- }
-
- fn expectHeader(self: *Self, name: []const u8, value: []const u8) !void {
- for (self.request_headers) |h|
- if (std.ascii.eqlIgnoreCase(name, h.name) and
- std.mem.eql(u8, value, h.value)) return;
- return error.HeaderOrValueNotFound;
- }
- fn waitForReady(self: *Self) !void {
- // Set 10s timeout...this is way longer than necessary
- log.debug("waiting for ready", .{});
- try self.server_ready.timedWait(1000 * std.time.ns_per_ms);
- // var deadline = std.Thread.Futex.Deadline.init(1000 * std.time.ns_per_ms);
- // if (self.futex_word.load(.acquire) != 0) return;
- // log.debug("futex zero", .{});
- // // note that this seems backwards from the documentation...
- // deadline.wait(self.futex_word, 1) catch {
- // log.err("futex value {d}", .{self.futex_word.load(.acquire)});
- // return error.TestServerTimeoutWaitingForReady;
- // };
- log.debug("the wait is over!", .{});
- }
-};
-
-/// This starts a test server. We're not testing the server itself,
-/// so the main tests will start this thing up and create an arena around the
-/// whole thing so we can just deallocate everything at once at the end,
-/// leaks be damned
-fn threadMain(options: *TestOptions) !void {
- // https://github.com/ziglang/zig/blob/d2be725e4b14c33dbd39054e33d926913eee3cd4/lib/compiler/std-docs.zig#L22-L54
-
- options.arena = try options.allocator.create(ArenaAllocator);
- options.arena.?.* = ArenaAllocator.init(options.allocator);
- const allocator = options.arena.?.allocator();
- options.allocator = allocator;
-
- const address = try std.net.Address.parseIp("127.0.0.1", 0);
- var http_server = try address.listen(.{});
- options.server_port = http_server.listen_address.in.getPort();
- // TODO: remove
- options.test_server_runtime_uri = try std.fmt.allocPrint(options.allocator, "http://127.0.0.1:{d}", .{options.server_port.?});
- log.debug("server listening at {s}", .{options.test_server_runtime_uri.?});
- log.info("starting server thread, tid {d}", .{std.Thread.getCurrentId()});
- // var arena = ArenaAllocator.init(options.allocator);
- // defer arena.deinit();
- // var aa = arena.allocator();
- // We're in control of all requests/responses, so this flag will tell us
- // when it's time to shut down
- if (options.server_remaining_requests == 0)
- options.server_ready.post(); // This will cause the wait for server to return
- while (options.server_remaining_requests > 0) : (options.server_remaining_requests -= 1) {
- processRequest(options, &http_server) catch |e| {
- log.err("Unexpected error processing request: {any}", .{e});
- if (@errorReturnTrace()) |trace| {
- std.debug.dumpStackTrace(trace.*);
- }
- };
- }
-}
-
-fn processRequest(options: *TestOptions, net_server: *std.net.Server) !void {
- log.debug(
- "tid {d} (server): server waiting to accept. requests remaining: {d}",
- .{ std.Thread.getCurrentId(), options.server_remaining_requests },
- );
- // options.futex_word.store(1, .release);
- // errdefer options.futex_word.store(0, .release);
- options.server_ready.post();
- var connection = try net_server.accept();
- defer connection.stream.close();
- var read_buffer: [1024 * 16]u8 = undefined;
- var http_server = std.http.Server.init(connection, &read_buffer);
- while (http_server.state == .ready) {
- var request = http_server.receiveHead() catch |err| switch (err) {
- error.HttpConnectionClosing => return,
- else => {
- std.log.err("closing http connection: {s}", .{@errorName(err)});
- std.log.debug("Error occurred from this request: \n{s}", .{read_buffer[0..http_server.read_buffer_len]});
- return;
- },
- };
- try serveRequest(options, &request);
- }
-}
-
-fn serveRequest(options: *TestOptions, request: *std.http.Server.Request) !void {
- options.requests_processed += 1;
- options.request_body = try (try request.reader()).readAllAlloc(options.allocator, std.math.maxInt(usize));
- options.request_method = request.head.method;
- options.request_target = try options.allocator.dupe(u8, request.head.target);
- var req_headers = std.ArrayList(std.http.Header).init(options.allocator);
- defer req_headers.deinit();
- var it = request.iterateHeaders();
- while (it.next()) |f| {
- const h = try options.allocator.create(std.http.Header);
- h.* = .{ .name = try options.allocator.dupe(u8, f.name), .value = try options.allocator.dupe(u8, f.value) };
- try req_headers.append(h.*);
- }
- options.request_headers = try req_headers.toOwnedSlice();
- log.debug(
- "tid {d} (server): {d} bytes read from request",
- .{ std.Thread.getCurrentId(), options.request_body.len },
- );
-
- // try response.headers.append("content-type", "text/plain");
- try request.respond(options.server_response, .{
- .status = options.server_response_status,
- .extra_headers = options.server_response_headers,
- });
-
- log.debug(
- "tid {d} (server): sent response",
- .{std.Thread.getCurrentId()},
- );
-}
-
-////////////////////////////////////////////////////////////////////////
-// These will replicate the tests that were in src/main.zig
-// The server_response and server_response_headers come from logs of
-// a previous run of src/main.zig, with redactions
-////////////////////////////////////////////////////////////////////////
-
-const TestSetup = struct {
- allocator: Allocator,
- request_options: TestOptions,
- server_thread: std.Thread = undefined,
- creds: aws_auth.Credentials = undefined,
- client: Client = undefined,
- started: bool = false,
-
- const Self = @This();
-
- const aws_creds = @import("aws_credentials.zig");
- const aws_auth = @import("aws_authentication.zig");
- const signing_time =
- date.dateTimeToTimestamp(date.parseIso8601ToDateTime("20230908T170252Z") catch @compileError("Cannot parse date")) catch @compileError("Cannot parse date");
-
- fn init(options: TestOptions) Self {
- return .{
- .request_options = options,
- .allocator = options.allocator,
- };
- }
-
- fn start(self: *Self) !Options {
- self.server_thread = try std.Thread.spawn(
- .{},
- threadMain,
- .{&self.request_options},
- );
- self.started = true;
- try self.request_options.waitForReady();
- // Not sure why we're getting sprayed here, but we have an arena allocator, and this
- // is testing, so yolo
- awshttp.endpoint_override = self.request_options.test_server_runtime_uri;
- if (awshttp.endpoint_override == null) return error.TestSetupStartFailure;
- std.log.debug("endpoint override set to {?s}", .{awshttp.endpoint_override});
- self.creds = aws_auth.Credentials.init(
- self.allocator,
- try self.allocator.dupe(u8, "ACCESS"),
- try self.allocator.dupe(u8, "SECRET"),
- null,
- );
- aws_creds.static_credentials = self.creds;
- const client = Client.init(self.allocator, .{});
- self.client = client;
- return .{
- .region = "us-west-2",
- .client = client,
- .signing_time = signing_time,
- };
- }
-
- fn stop(self: *Self) void {
- if (self.request_options.server_remaining_requests > 0)
- if (test_error_log_enabled)
- std.log.err(
- "Test server has {d} request(s) remaining to issue! Draining",
- .{self.request_options.server_remaining_requests},
- )
- else
- std.log.info(
- "Test server has {d} request(s) remaining to issue! Draining",
- .{self.request_options.server_remaining_requests},
- );
-
- var rr = self.request_options.server_remaining_requests;
- while (rr > 0) : (rr -= 1) {
- std.log.debug("rr: {d}", .{self.request_options.server_remaining_requests});
- // We need to drain all remaining requests, otherwise the server
- // will hang indefinitely
- var client = std.http.Client{ .allocator = self.allocator };
- defer client.deinit();
- _ = client.fetch(.{ .location = .{ .url = self.request_options.test_server_runtime_uri.? } }) catch unreachable;
- }
- self.server_thread.join();
- }
-
- fn deinit(self: *Self) void {
- if (self.request_options.arena) |a| {
- a.deinit();
- self.allocator.destroy(a);
- }
- if (!self.started) return;
- awshttp.endpoint_override = null;
- // creds.deinit(); Creds will get deinited in the course of the call. We don't want to do it twice
- aws_creds.static_credentials = null; // we do need to reset the static creds for the next user though
- self.client.deinit();
- }
-};
-
-test "query_no_input: sts getCallerIdentity comptime" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{"GetCallerIdentityResponse":{"GetCallerIdentityResult":{"Account":"123456789012","Arn":"arn:aws:iam::123456789012:user/admin","UserId":"AIDAYAM4POHXHRVANDQBQ"},"ResponseMetadata":{"RequestId":"8f0d54da-1230-40f7-b4ac-95015c4b84cd"}}}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "8f0d54da-1230-40f7-b4ac-95015c4b84cd" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const sts = (Services(.{.sts}){}).sts;
- const call = try Request(sts.get_caller_identity).call(.{}, options);
- // const call = try client.call(services.sts.get_caller_identity.Request{}, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings(
- \\Action=GetCallerIdentity&Version=2011-06-15
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings(
- "arn:aws:iam::123456789012:user/admin",
- call.response.arn.?,
- );
- try std.testing.expectEqualStrings("AIDAYAM4POHXHRVANDQBQ", call.response.user_id.?);
- try std.testing.expectEqualStrings("123456789012", call.response.account.?);
- try std.testing.expectEqualStrings("8f0d54da-1230-40f7-b4ac-95015c4b84cd", call.response_metadata.request_id);
-}
-test "query_with_input: iam getRole runtime" {
- // sqs switched from query to json in aws sdk for go v2 commit f5a08768ef820ff5efd62a49ba50c61c9ca5dbcb
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\
- \\
- \\
- \\ /application_abc/component_xyz/
- \\ arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access
- \\ S3Access
- \\
- \\ {"Version":"2012-10-17","Statement":[{"Effect":"Allow",
- \\ "Principal":{"Service":["ec2.amazonaws.com"]},"Action":["sts:AssumeRole"]}]}
- \\
- \\ 2012-05-08T23:34:01Z
- \\ AROADBQP57FF2AEXAMPLE
- \\
- \\ 2019-11-20T17:09:20Z
- \\ us-east-1
- \\
- \\
- \\
- \\
- \\ df37e965-9967-11e1-a4c3-270EXAMPLE04
- \\
- \\
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "text/xml" },
- .{ .name = "x-amzn-RequestId", .value = "df37e965-9967-11e1-a4c3-270EXAMPLE04" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const iam = (Services(.{.iam}){}).iam;
- const call = try test_harness.client.call(iam.get_role.Request{
- .role_name = "S3Access",
- }, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings(
- \\Action=GetRole&Version=2010-05-08&RoleName=S3Access
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access", call.response.role.arn);
- try std.testing.expectEqualStrings("df37e965-9967-11e1-a4c3-270EXAMPLE04", call.response_metadata.request_id);
-}
-test "query_with_input: sts getAccessKeyInfo runtime" {
- // sqs switched from query to json in aws sdk for go v2 commit f5a08768ef820ff5efd62a49ba50c61c9ca5dbcb
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\
- \\
- \\ 123456789012
- \\
- \\
- \\ ec85bf29-1ef0-459a-930e-6446dd14a286
- \\
- \\
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "text/xml" },
- .{ .name = "x-amzn-RequestId", .value = "ec85bf29-1ef0-459a-930e-6446dd14a286" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const sts = (Services(.{.sts}){}).sts;
- const call = try test_harness.client.call(sts.get_access_key_info.Request{
- .access_key_id = "ASIAYAM4POHXJNKTYFUN",
- }, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings(
- \\Action=GetAccessKeyInfo&Version=2011-06-15&AccessKeyId=ASIAYAM4POHXJNKTYFUN
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expect(call.response.account != null);
- try std.testing.expectEqualStrings("123456789012", call.response.account.?);
- try std.testing.expectEqualStrings("ec85bf29-1ef0-459a-930e-6446dd14a286", call.response_metadata.request_id);
-}
-test "json_1_0_query_with_input: dynamodb listTables runtime" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{"LastEvaluatedTableName":"Customer","TableNames":["Customer"]}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const dynamo_db = services.dynamo_db;
- const call = try test_harness.client.call(dynamo_db.list_tables.Request{
- .limit = 1,
- }, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
- try test_harness.request_options.expectHeader("X-Amz-Target", "DynamoDB_20120810.ListTables");
-
- const parsed_body = try std.json.parseFromSlice(struct {
- ExclusiveStartTableName: ?[]const u8,
- Limit: u8,
- }, testing.allocator, test_harness.request_options.request_body, .{});
- defer parsed_body.deinit();
-
- try testing.expectEqual(null, parsed_body.value.ExclusiveStartTableName);
- try testing.expectEqual(1, parsed_body.value.Limit);
-
- // Response expectations
- try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 1), call.response.table_names.?.len);
- try std.testing.expectEqualStrings("Customer", call.response.table_names.?[0]);
-}
-
-test "json_1_0_query_no_input: dynamodb listTables runtime" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{"AccountMaxReadCapacityUnits":80000,"AccountMaxWriteCapacityUnits":80000,"TableMaxReadCapacityUnits":40000,"TableMaxWriteCapacityUnits":40000}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const dynamo_db = (Services(.{.dynamo_db}){}).dynamo_db;
- const call = try test_harness.client.call(dynamo_db.describe_limits.Request{}, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
- try test_harness.request_options.expectHeader("X-Amz-Target", "DynamoDB_20120810.DescribeLimits");
- try std.testing.expectEqualStrings(
- \\{}
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(i64, 80000), call.response.account_max_read_capacity_units.?);
-}
-test "json_1_1_query_with_input: ecs listClusters runtime" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{"clusterArns":["arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster"],"nextToken":"czE0Og=="}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "b2420066-ff67-4237-b782-721c4df60744" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const ecs = (Services(.{.ecs}){}).ecs;
- const call = try test_harness.client.call(ecs.list_clusters.Request{
- .max_results = 1,
- }, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
- try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters");
-
- const parsed_body = try std.json.parseFromSlice(struct {
- nextToken: ?[]const u8,
- maxResults: u8,
- }, testing.allocator, test_harness.request_options.request_body, .{});
- defer parsed_body.deinit();
-
- try testing.expectEqual(null, parsed_body.value.nextToken);
- try testing.expectEqual(1, parsed_body.value.maxResults);
-
- // Response expectations
- try std.testing.expectEqualStrings("b2420066-ff67-4237-b782-721c4df60744", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 1), call.response.cluster_arns.?.len);
- try std.testing.expectEqualStrings("arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster", call.response.cluster_arns.?[0]);
-}
-test "json_1_1_query_no_input: ecs listClusters runtime" {
- // const old = std.testing.log_level;
- // defer std.testing.log_level = old;
- // std.testing.log_level = .debug;
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{"clusterArns":["arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster"],"nextToken":"czE0Og=="}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "e65322b2-0065-45f2-ba37-f822bb5ce395" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const ecs = (Services(.{.ecs}){}).ecs;
- const call = try test_harness.client.call(ecs.list_clusters.Request{}, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
- try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters");
-
- const parsed_body = try std.json.parseFromSlice(struct {
- nextToken: ?[]const u8,
- maxResults: ?u8,
- }, testing.allocator, test_harness.request_options.request_body, .{});
- defer parsed_body.deinit();
-
- try testing.expectEqual(null, parsed_body.value.nextToken);
- try testing.expectEqual(null, parsed_body.value.maxResults);
-
- // Response expectations
- try std.testing.expectEqualStrings("e65322b2-0065-45f2-ba37-f822bb5ce395", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 1), call.response.cluster_arns.?.len);
- try std.testing.expectEqualStrings("arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster", call.response.cluster_arns.?[0]);
-}
-test "rest_json_1_query_with_input: lambda listFunctions runtime" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{"Functions":[{"Description":"AWS CDK resource provider framework - onEvent (DevelopmentFrontendStack-g650u/com.amazonaws.cdk.custom-resources.amplify-asset-deployment-provider/amplify-asset-deployment-handler-provider)","TracingConfig":{"Mode":"PassThrough"},"VpcConfig":null,"SigningJobArn":null,"SnapStart":{"OptimizationStatus":"Off","ApplyOn":"None"},"RevisionId":"0c62fc74-a692-403d-9206-5fcbad406424","LastModified":"2023-03-01T18:13:15.704+0000","FileSystemConfigs":null,"FunctionName":"DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU","Runtime":"nodejs14.x","Version":"$LATEST","PackageType":"Zip","LastUpdateStatus":null,"Layers":null,"FunctionArn":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU","KMSKeyArn":null,"MemorySize":128,"ImageConfigResponse":null,"LastUpdateStatusReason":null,"DeadLetterConfig":null,"Timeout":900,"Handler":"framework.onEvent","CodeSha256":"m4tt+M0l3p8bZvxIDj83dwGrwRW6atCfS/q8AiXCD3o=","Role":"arn:aws:iam::550620852718:role/DevelopmentFrontendStack-amplifyassetdeploymentha-1782JF7WAPXZ3","SigningProfileVersionArn":null,"MasterArn":null,"RuntimeVersionConfig":null,"CodeSize":4307,"State":null,"StateReason":null,"Environment":{"Variables":{"USER_ON_EVENT_FUNCTION_ARN":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymenton-X9iZJSCSPYDH","WAITER_STATE_MACHINE_ARN":"arn:aws:states:us-west-2:550620852718:stateMachine:amplifyassetdeploymenthandlerproviderwaiterstatemachineB3C2FCBE-Ltggp5wBcHWO","USER_IS_COMPLETE_FUNCTION_ARN":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymentis-jaHopLrSSARV"},"Error":null},"EphemeralStorage":{"Size":512},"StateReasonCode":null,"LastUpdateStatusReasonCode":null,"Architectures":["x86_64"]}],"NextMarker":"lslTXFcbLQKkb0vP9Kgh5hUL7C3VghELNGbWgZfxrRCk3eiDRMkct7D8EmptWfHSXssPdS7Bo66iQPTMpVOHZgANewpgGgFGGr4pVjd6VgLUO6qPe2EMAuNDBjUTxm8z6N28yhlUwEmKbrAV/m0k5qVzizwoxFwvyruMbuMx9kADFACSslcabxXl3/jDI4rfFnIsUVdzTLBgPF1hzwrE1f3lcdkBvUp+QgY+Pn3w5QuJmwsp/di8COzFemY89GgOHbLNqsrBsgR/ee2eXoJp0ZkKM4EcBK3HokqBzefLfgR02PnfNOdXwqTlhkSPW0TKiKGIYu3Bw7lSNrLd+q3+wEr7ZakqOQf0BVo3FMRhMHlVYgwUJzwi3ActyH2q6fuqGG1sS0B8Oa/prUpe5fmp3VaA3WpazioeHtrKF78JwCi6/nfQsrj/8ZtXGQOxlwEgvT1CIUaF+CdHY3biezrK0tRZNpkCtHnkPtF9lq2U7+UiKXSW9yzxT8P2b0M/Qh4IVdnw4rncQK/doYriAeOdrs1wjMEJnHWq9lAaEyipoxYcVr/z5+yaC6Gwxdg45p9X1vIAaYMf6IZxyFuua43SYi0Ls+IBk4VvpR2io7T0dCxHAr3WAo3D2dm0y8OsbM59"}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "c4025199-226f-4a16-bb1f-48618e9d2ea6" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const lambda = (Services(.{.lambda}){}).lambda;
- const call = try test_harness.client.call(lambda.list_functions.Request{
- .max_items = 1,
- }, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/2015-03-31/functions?MaxItems=1", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings(
- \\
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("c4025199-226f-4a16-bb1f-48618e9d2ea6", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 1), call.response.functions.?.len);
- try std.testing.expectEqualStrings(
- "DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU",
- call.response.functions.?[0].function_name.?,
- );
-}
-test "rest_json_1_query_no_input: lambda listFunctions runtime" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response = @embedFile("test_rest_json_1_query_no_input.response"),
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "b2aad11f-36fc-4d0d-ae92-fe0167fb0f40" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const lambda = (Services(.{.lambda}){}).lambda;
- const call = try test_harness.client.call(lambda.list_functions.Request{}, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/2015-03-31/functions", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings(
- \\
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("b2aad11f-36fc-4d0d-ae92-fe0167fb0f40", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 24), call.response.functions.?.len);
- try std.testing.expectEqualStrings(
- "DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU",
- call.response.functions.?[0].function_name.?,
- );
- try std.testing.expectEqualStrings(
- "amplify-login-create-auth-challenge-b4883e4c",
- call.response.functions.?[12].function_name.?,
- );
-}
-test "rest_json_1_work_with_lambda: lambda tagResource (only), to excercise zig issue 17015" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response = "",
- .server_response_status = .no_content,
- .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;
- var tags = try std.ArrayList(@typeInfo(try typeForField(lambda.tag_resource.Request, "tags")).pointer.child).initCapacity(allocator, 1);
- defer tags.deinit(allocator);
- tags.appendAssumeCapacity(.{ .key = "Foo", .value = "Bar" });
- const req = services.lambda.tag_resource.Request{ .resource = "arn:aws:lambda:us-west-2:550620852718:function:awsome-lambda-LambdaStackawsomeLambda", .tags = tags.items };
- const call = try Request(lambda.tag_resource).call(req, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
-
- const parsed_body = try std.json.parseFromSlice(struct {
- Tags: struct {
- Foo: []const u8,
- },
- }, testing.allocator, test_harness.request_options.request_body, .{ .ignore_unknown_fields = true });
- defer parsed_body.deinit();
-
- try testing.expectEqualStrings("Bar", parsed_body.value.Tags.Foo);
-
- // Due to 17015, we see %253A instead of %3A
- try std.testing.expectEqualStrings("/2017-03-31/tags/arn%3Aaws%3Alambda%3Aus-west-2%3A550620852718%3Afunction%3Aawsome-lambda-LambdaStackawsomeLambda", test_harness.request_options.request_target);
- // 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);
-
- const parsed_body = try std.json.parseFromSlice(struct {
- ZipFile: []const u8,
- Architectures: [][]const u8,
- }, testing.allocator, test_harness.request_options.request_body, .{
- .ignore_unknown_fields = true,
- });
- defer parsed_body.deinit();
-
- try testing.expectEqualStrings("zipfile", parsed_body.value.ZipFile);
- try testing.expectEqual(1, parsed_body.value.Architectures.len);
- try testing.expectEqualStrings("x86_64", parsed_body.value.Architectures[0]);
-
- // 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(.{
- .allocator = allocator,
- .server_response = @embedFile("test_ec2_query_no_input.response"),
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "text/xml;charset=UTF-8" },
- .{ .name = "x-amzn-RequestId", .value = "4cdbdd69-800c-49b5-8474-ae4c17709782" },
- },
- .server_response_transfer_encoding = .chunked,
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const ec2 = (Services(.{.ec2}){}).ec2;
- const call = try test_harness.client.call(ec2.describe_regions.Request{}, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/?Action=DescribeRegions&Version=2016-11-15", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings(
- \\Action=DescribeRegions&Version=2016-11-15
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("4cdbdd69-800c-49b5-8474-ae4c17709782", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 17), call.response.regions.?.len);
-}
-// LLVM hates this test. Depending on the platform, it will consume all memory
-// on the compilation host. Windows x86_64 and Linux riscv64 seem to be a problem so far
-// riscv64-linux also seems to have another problem with LLVM basically infinitely
-// doing something. My guess is the @embedFile is freaking out LLVM
-test "ec2_query_with_input: EC2 describe instances" {
- if (builtin.cpu.arch == .riscv64 and builtin.os.tag == .linux) return error.SkipZigTest;
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response = @embedFile("test_ec2_query_with_input.response"),
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "text/xml;charset=UTF-8" },
- .{ .name = "x-amzn-RequestId", .value = "150a14cc-785d-476f-a4c9-2aa4d03b14e2" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const ec2 = (Services(.{.ec2}){}).ec2;
- const call = try test_harness.client.call(ec2.describe_instances.Request{
- .max_results = 6,
- }, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/?Action=DescribeInstances&Version=2016-11-15", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings(
- \\Action=DescribeInstances&Version=2016-11-15&MaxResults=6
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("150a14cc-785d-476f-a4c9-2aa4d03b14e2", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 6), call.response.reservations.?.len);
- try std.testing.expectEqualStrings("i-0212d7d1f62b96676", call.response.reservations.?[1].instances.?[0].instance_id.?);
- try std.testing.expectEqualStrings("123456789012:found-me", call.response.reservations.?[1].instances.?[0].tags.?[0].value.?);
-}
-test "rest_xml_with_input_s3: S3 create bucket" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\
- ,
- .server_response_headers = &.{ // I don't see content type coming back in actual S3 requests
- .{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" },
- .{ .name = "x-amz-id-2", .value = "u7lzgW0tIyRP15vSUsVOXxJ37OfVCO8lZmLIVuqeq5EE4tNp9qebb5fy+/kendlZpR4YQE+y4Xg=" },
- },
- });
- defer test_harness.deinit();
- errdefer test_harness.creds.deinit();
- const options = try test_harness.start();
- const s3 = (Services(.{.s3}){}).s3;
- const call = try test_harness.client.call(s3.create_bucket.Request{
- .bucket = "",
- .create_bucket_configuration = .{
- .location_constraint = "us-west-2",
- },
- }, 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("/", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings(
- \\
- \\ us-west-2
- \\
- , test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings(
- "9PEYBAZ9J7TPRX43, host_id: u7lzgW0tIyRP15vSUsVOXxJ37OfVCO8lZmLIVuqeq5EE4tNp9qebb5fy+/kendlZpR4YQE+y4Xg=",
- call.response_metadata.request_id,
- );
-}
-test "rest_xml_no_input: S3 list buckets" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\3367189aa775bd98da38e55093705f2051443c1e775fc0971d6d77387a47c8d0emilerch+sub1550620852718-backup2020-06-17T16:26:51.000Zamplify-letmework-staging-185741-deployment2023-03-10T18:57:49.000Zaws-cloudtrail-logs-550620852718-224022a72021-06-21T18:32:44.000Zaws-sam-cli-managed-default-samclisourcebucket-1gy0z00mj47xe2021-10-05T16:38:07.000Zawsomeprojectstack-pipelineartifactsbucketaea9a05-1uzwo6c86ecr2021-10-05T22:55:09.000Zcdk-hnb659fds-assets-550620852718-us-west-22023-02-28T21:49:36.000Zcf-templates-12iy6putgdxtk-us-west-22020-06-26T02:31:59.000Zcodepipeline-us-west-2-467140836372021-09-14T18:43:07.000Zelasticbeanstalk-us-west-2-5506208527182022-04-15T16:22:42.000Zlobo-west2021-06-21T17:17:22.000Zlobo-west-22021-11-19T20:12:31.000Zlogging-backup-550620852718-us-east-22022-05-29T21:55:16.000Zmysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr02023-03-01T04:53:55.000Z
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/xml" },
- .{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const s3 = (Services(.{.s3}){}).s3;
- const call = try test_harness.client.call(s3.list_buckets.Request{}, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method);
- // This changed in rev 830202d722c904c7e3da40e8dde7b9338d08752c of the go sdk, and
- // contrary to the documentation, a query string argument was added. My guess is that
- // there is no functional reason, and that this is strictly for some AWS reporting function.
- // Alternatively, it could be to support some customization mechanism, as the commit
- // title of that commit is "Merge customizations for S3"
- try std.testing.expectEqualStrings("/?x-id=ListBuckets", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings("", test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("9PEYBAZ9J7TPRX43", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 13), call.response.buckets.?.len);
-}
-test "rest_xml_anything_but_s3: CloudFront list key groups" {
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{"Items":null,"MaxItems":100,"NextMarker":null,"Quantity":0}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "d3382082-5291-47a9-876b-8df3accbb7ea" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const cloudfront = (Services(.{.cloudfront}){}).cloudfront;
- const call = try test_harness.client.call(cloudfront.list_key_groups.Request{}, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/2020-05-31/key-group", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings("", test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("d3382082-5291-47a9-876b-8df3accbb7ea", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(i64, 100), call.response.key_group_list.?.max_items);
-}
-test "rest_xml_with_input: S3 put object" {
- // const old = std.testing.log_level;
- // defer std.testing.log_level = old;
- // std.testing.log_level = .debug;
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response = "",
- .server_response_headers = &.{
- // .{ "Content-Type", "application/xml" },
- .{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" },
- .{ .name = "x-amz-id-2", .value = "jdRDo30t7Ge9lf6F+4WYpg+YKui8z0mz2+rwinL38xDZzvloJqrmpCAiKG375OSvHA9OBykJS44=" },
- .{ .name = "x-amz-server-side-encryption", .value = "AES256" },
- .{ .name = "ETag", .value = "37b51d194a7513e45b56f6524f2d51f2" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const s3opts = Options{
- .region = "us-west-2",
- .client = options.client,
- .signing_time = TestSetup.signing_time,
- };
- const result = try Request(services.s3.put_object).call(.{
- .bucket = "mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0",
- .key = "i/am/a/teapot/foo",
- .content_type = "text/plain",
- .body = "bar",
- .storage_class = "STANDARD",
- }, s3opts);
- defer result.deinit();
- for (test_harness.request_options.request_headers) |header| {
- std.log.info("Request header: {s}: {s}", .{ header.name, header.value });
- }
- try test_harness.request_options.expectNoDuplicateHeaders();
- std.log.info("PutObject Request id: {s}", .{result.response_metadata.request_id});
- std.log.info("PutObject etag: {s}", .{result.response.e_tag.?});
- //mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0.s3.us-west-2.amazonaws.com
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.PUT, test_harness.request_options.request_method);
- // I don't think this will work since we're overriding the url
- // try test_harness.request_options.expectHeader("Host", "mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0.s3.us-west-2.amazonaws.com");
- try test_harness.request_options.expectHeader("x-amz-storage-class", "STANDARD");
- try std.testing.expectEqualStrings("/mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0/i/am/a/teapot/foo?x-id=PutObject", test_harness.request_options.request_target);
- try std.testing.expectEqualStrings("bar", test_harness.request_options.request_body);
- // Response expectations
- try std.testing.expectEqualStrings("9PEYBAZ9J7TPRX43, host_id: jdRDo30t7Ge9lf6F+4WYpg+YKui8z0mz2+rwinL38xDZzvloJqrmpCAiKG375OSvHA9OBykJS44=", result.response_metadata.request_id);
- try std.testing.expectEqualStrings("AES256", result.response.server_side_encryption.?);
- try std.testing.expectEqualStrings("37b51d194a7513e45b56f6524f2d51f2", result.response.e_tag.?);
-}
-test "raw ECR timestamps" {
- // This is a way to test the json parsing. Ultimately the more robust tests
- // should be preferred, but in this case we were tracking down an issue
- // for which the root cause was the incorrect type being passed to the parse
- // routine
- const allocator = std.testing.allocator;
- const ecr = (Services(.{.ecr}){}).ecr;
- const options = json.ParseOptions{
- .allocator = allocator,
- .allow_camel_case_conversion = true, // new option
- .allow_snake_case_conversion = true, // new option
- .allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
- .allow_missing_fields = false, // new option. Cannot yet handle non-struct fields though
- };
- var stream = json.TokenStream.init(
- \\{"authorizationData":[{"authorizationToken":"***","expiresAt":1.7385984915E9,"proxyEndpoint":"https://146325435496.dkr.ecr.us-west-2.amazonaws.com"}]}
- );
- const ptr = try json.parse(ecr.get_authorization_token.Response, &stream, options);
- defer json.parseFree(ecr.get_authorization_token.Response, ptr, options);
-}
-test "json_1_1: ECR timestamps" {
- // See: https://github.com/elerch/aws-sdk-for-zig/issues/5
- // const old = std.testing.log_level;
- // defer std.testing.log_level = old;
- // std.testing.log_level = .debug;
- const allocator = std.testing.allocator;
-
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{"authorizationData":[{"authorizationToken":"***","expiresAt":"2022-05-17T06:56:13.652000+00:00","proxyEndpoint":"https://146325435496.dkr.ecr.us-west-2.amazonaws.com"}]}
- // \\{"authorizationData":[{"authorizationToken":"***","expiresAt":1.738598491557E9,"proxyEndpoint":"https://146325435496.dkr.ecr.us-west-2.amazonaws.com"}]}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
- },
- });
- defer test_harness.deinit();
- const options = try test_harness.start();
- const ecr = (Services(.{.ecr}){}).ecr;
- std.log.debug("Typeof response {}", .{@TypeOf(ecr.get_authorization_token.Response{})});
- const call = try test_harness.client.call(ecr.get_authorization_token.Request{}, options);
- defer call.deinit();
- test_harness.stop();
- // Request expectations
- try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
- try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
- try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerRegistry_V20150921.GetAuthorizationToken");
- // Response expectations
- try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id);
- try std.testing.expectEqual(@as(usize, 1), call.response.authorization_data.?.len);
- try std.testing.expectEqualStrings("***", call.response.authorization_data.?[0].authorization_token.?);
- try std.testing.expectEqualStrings("https://146325435496.dkr.ecr.us-west-2.amazonaws.com", call.response.authorization_data.?[0].proxy_endpoint.?);
- // try std.testing.expectEqual(@as(i64, 1.73859841557E9), call.response.authorization_data.?[0].expires_at.?);
-
- const expected_ins = try zeit.instant(.{
- .source = .{ .iso8601 = "2022-05-17T06:56:13.652000+00:00" },
- });
- const expected_ts: date.Timestamp = @enumFromInt(expected_ins.timestamp);
-
- try std.testing.expectEqual(expected_ts, call.response.authorization_data.?[0].expires_at.?);
-}
-var test_error_log_enabled = true;
-test "test server timeout works" {
- // const old = std.testing.log_level;
- // defer std.testing.log_level = old;
- // std.testing.log_level = .debug;
- // defer std.testing.log_level = old;
- // std.testing.log_level = .debug;
- test_error_log_enabled = false;
- defer test_error_log_enabled = true;
- std.log.debug("test start", .{});
- const allocator = std.testing.allocator;
- var test_harness = TestSetup.init(.{
- .allocator = allocator,
- .server_response =
- \\{}
- ,
- .server_response_headers = &.{
- .{ .name = "Content-Type", .value = "application/json" },
- .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
- },
- });
- defer test_harness.deinit();
- defer test_harness.creds.deinit(); // Usually this gets done during the call,
- // but we're purposely not making a call
- // here, so we have to deinit() manually
- _ = try test_harness.start();
- std.log.debug("harness started", .{});
- test_harness.stop();
- std.log.debug("test complete", .{});
-}
-
-const testing = std.testing;
-
-test "jsonStringify: structure + enums" {
- const request = services.media_convert.PutPolicyRequest{
- .policy = .{
- .http_inputs = "foo",
- .https_inputs = "bar",
- .s3_inputs = "baz",
- },
- };
-
- var arena = std.heap.ArenaAllocator.init(testing.allocator);
- defer arena.deinit();
-
- const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
- defer std.testing.allocator.free(request_json);
-
- const parsed = try std.json.parseFromSlice(struct {
- policy: struct {
- httpInputs: []const u8,
- httpsInputs: []const u8,
- s3Inputs: []const u8,
- },
- }, testing.allocator, request_json, .{});
- defer parsed.deinit();
-
- try testing.expectEqualStrings("foo", parsed.value.policy.httpInputs);
- try testing.expectEqualStrings("bar", parsed.value.policy.httpsInputs);
- try testing.expectEqualStrings("baz", parsed.value.policy.s3Inputs);
-}
-
-test "jsonStringify: strings" {
- const request = services.media_convert.AssociateCertificateRequest{
- .arn = "1234",
- };
-
- var arena = std.heap.ArenaAllocator.init(testing.allocator);
- defer arena.deinit();
-
- const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
- defer std.testing.allocator.free(request_json);
-
- try testing.expectEqualStrings("{\"arn\":\"1234\"}", request_json);
-}
-
-test "jsonStringify" {
- var tags = [_]services.media_convert.MapOfStringKeyValue{
- .{
- .key = "foo",
- .value = "bar",
- },
- };
-
- const request = services.media_convert.TagResourceRequest{
- .arn = "1234",
- .tags = &tags,
- };
-
- var arena = std.heap.ArenaAllocator.init(testing.allocator);
- defer arena.deinit();
-
- const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
- defer std.testing.allocator.free(request_json);
-
- const json_parsed = try std.json.parseFromSlice(struct {
- arn: []const u8,
- tags: struct {
- foo: []const u8,
- },
- }, testing.allocator, request_json, .{});
- defer json_parsed.deinit();
-
- try testing.expectEqualStrings("1234", json_parsed.value.arn);
- try testing.expectEqualStrings("bar", json_parsed.value.tags.foo);
-}
-
-test "jsonStringify nullable object" {
- // structure is not null
- {
- const request = services.lambda.CreateAliasRequest{
- .function_name = "foo",
- .function_version = "bar",
- .name = "baz",
- .routing_config = services.lambda.AliasRoutingConfiguration{
- .additional_version_weights = null,
- },
- };
-
- const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
- defer std.testing.allocator.free(request_json);
-
- const json_parsed = try std.json.parseFromSlice(struct {
- FunctionName: []const u8,
- FunctionVersion: []const u8,
- Name: []const u8,
- RoutingConfig: struct {
- AdditionalVersionWeights: ?struct {},
- },
- }, testing.allocator, request_json, .{ .ignore_unknown_fields = true });
- defer json_parsed.deinit();
-
- try testing.expectEqualStrings("foo", json_parsed.value.FunctionName);
- try testing.expectEqualStrings("bar", json_parsed.value.FunctionVersion);
- try testing.expectEqualStrings("baz", json_parsed.value.Name);
- try testing.expectEqual(null, json_parsed.value.RoutingConfig.AdditionalVersionWeights);
- }
-
- // structure is null
- {
- const request = services.kms.DecryptRequest{
- .key_id = "foo",
- .ciphertext_blob = "bar",
- };
-
- const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
- defer std.testing.allocator.free(request_json);
-
- const json_parsed = try std.json.parseFromSlice(struct {
- KeyId: []const u8,
- CiphertextBlob: []const u8,
- }, testing.allocator, request_json, .{ .ignore_unknown_fields = true });
- defer json_parsed.deinit();
-
- try testing.expectEqualStrings("foo", json_parsed.value.KeyId);
- try testing.expectEqualStrings("bar", json_parsed.value.CiphertextBlob);
- }
}
diff --git a/src/aws_test.zig b/src/aws_test.zig
new file mode 100644
index 0000000..ad29522
--- /dev/null
+++ b/src/aws_test.zig
@@ -0,0 +1,1410 @@
+const builtin = @import("builtin");
+const std = @import("std");
+
+const date = @import("date");
+const json = @import("json");
+
+const aws = @import("aws.zig");
+const awshttp = @import("aws_http.zig");
+
+const services = aws.servicemodel.services;
+const Services = aws.servicemodel.Services;
+
+const log = std.log.scoped(.aws_test);
+
+pub var buildQuery: u8 = undefined;
+pub var buildPath: u8 = undefined;
+
+// TODO: Where does this belong really?
+fn typeForField(comptime T: type, comptime field_name: []const u8) !type {
+ const ti = @typeInfo(T);
+ switch (ti) {
+ .@"struct" => {
+ inline for (ti.@"struct".fields) |field| {
+ if (std.mem.eql(u8, field.name, field_name))
+ return field.type;
+ }
+ },
+ else => return error.TypeIsNotAStruct, // should not hit this
+ }
+ return error.FieldNotFound;
+}
+
+test "custom serialization for map objects" {
+ const allocator = std.testing.allocator;
+ var buffer = std.Io.Writer.Allocating.init(allocator);
+ defer buffer.deinit();
+ var tags = try std.ArrayList(@typeInfo(try typeForField(services.lambda.tag_resource.Request, "tags")).pointer.child).initCapacity(allocator, 2);
+ defer tags.deinit(allocator);
+ tags.appendAssumeCapacity(.{ .key = "Foo", .value = "Bar" });
+ tags.appendAssumeCapacity(.{ .key = "Baz", .value = "Qux" });
+ const req = services.lambda.TagResourceRequest{ .resource = "hello", .tags = tags.items };
+ try buffer.writer.print("{f}", .{std.json.fmt(req, .{ .whitespace = .indent_4 })});
+
+ const parsed_body = try std.json.parseFromSlice(struct {
+ Resource: []const u8,
+ Tags: struct {
+ Foo: []const u8,
+ Baz: []const u8,
+ },
+ }, testing.allocator, buffer.written(), .{});
+ defer parsed_body.deinit();
+
+ try testing.expectEqualStrings("hello", parsed_body.value.Resource);
+ try testing.expectEqualStrings("Bar", parsed_body.value.Tags.Foo);
+ try testing.expectEqualStrings("Qux", parsed_body.value.Tags.Baz);
+}
+
+test "proper serialization for kms" {
+ // Github issue #8
+ // https://github.com/elerch/aws-sdk-for-zig/issues/8
+ const allocator = std.testing.allocator;
+ var buffer = std.Io.Writer.Allocating.init(allocator);
+ defer buffer.deinit();
+ const req = services.kms.encrypt.Request{
+ .encryption_algorithm = "SYMMETRIC_DEFAULT",
+ // Since encryption_context is not null, we expect "{}" to be the value
+ // here, not "[]", because this is our special AWS map pattern
+ .encryption_context = &.{},
+ .key_id = "42",
+ .plaintext = "foo",
+ .dry_run = false,
+ .grant_tokens = &[_][]const u8{},
+ };
+ try buffer.writer.print("{f}", .{std.json.fmt(req, .{ .whitespace = .indent_4 })});
+
+ {
+ const parsed_body = try std.json.parseFromSlice(struct {
+ KeyId: []const u8,
+ Plaintext: []const u8,
+ EncryptionContext: struct {},
+ GrantTokens: [][]const u8,
+ EncryptionAlgorithm: []const u8,
+ DryRun: bool,
+ }, testing.allocator, buffer.written(), .{});
+ defer parsed_body.deinit();
+
+ try testing.expectEqualStrings("42", parsed_body.value.KeyId);
+ try testing.expectEqualStrings("foo", parsed_body.value.Plaintext);
+ try testing.expectEqual(0, parsed_body.value.GrantTokens.len);
+ try testing.expectEqualStrings("SYMMETRIC_DEFAULT", parsed_body.value.EncryptionAlgorithm);
+ try testing.expectEqual(false, parsed_body.value.DryRun);
+ }
+
+ var buffer_null = std.Io.Writer.Allocating.init(allocator);
+ defer buffer_null.deinit();
+ const req_null = services.kms.encrypt.Request{
+ .encryption_algorithm = "SYMMETRIC_DEFAULT",
+ // Since encryption_context here *IS* null, we expect simply "null" to be the value
+ .encryption_context = null,
+ .key_id = "42",
+ .plaintext = "foo",
+ .dry_run = false,
+ .grant_tokens = &[_][]const u8{},
+ };
+
+ try buffer_null.writer.print("{f}", .{std.json.fmt(req_null, .{ .whitespace = .indent_4 })});
+
+ {
+ const parsed_body = try std.json.parseFromSlice(struct {
+ KeyId: []const u8,
+ Plaintext: []const u8,
+ EncryptionContext: ?struct {},
+ GrantTokens: [][]const u8,
+ EncryptionAlgorithm: []const u8,
+ DryRun: bool,
+ }, testing.allocator, buffer_null.written(), .{});
+ defer parsed_body.deinit();
+
+ try testing.expectEqualStrings("42", parsed_body.value.KeyId);
+ try testing.expectEqualStrings("foo", parsed_body.value.Plaintext);
+ try testing.expectEqual(null, parsed_body.value.EncryptionContext);
+ try testing.expectEqual(0, parsed_body.value.GrantTokens.len);
+ try testing.expectEqualStrings("SYMMETRIC_DEFAULT", parsed_body.value.EncryptionAlgorithm);
+ try testing.expectEqual(false, parsed_body.value.DryRun);
+ }
+}
+
+test "REST Json v1 builds proper queries" {
+ const allocator = std.testing.allocator;
+ const svs = Services(.{.lambda}){};
+ const request = svs.lambda.list_functions.Request{
+ .max_items = 1,
+ };
+ const query = try buildQuery(allocator, request);
+ defer allocator.free(query);
+ try std.testing.expectEqualStrings("?MaxItems=1", query);
+}
+test "REST Json v1 handles reserved chars in queries" {
+ const allocator = std.testing.allocator;
+ const svs = Services(.{.lambda}){};
+ var keys = [_][]const u8{"Foo?I'm a crazy%dude"}; // Would love to have a way to express this without burning a var here
+ const request = svs.lambda.untag_resource.Request{
+ .tag_keys = keys[0..],
+ .resource = "hello",
+ };
+ const query = try buildQuery(allocator, request);
+ defer allocator.free(query);
+ try std.testing.expectEqualStrings("?tagKeys=Foo%3FI%27m a crazy%25dude", query);
+}
+test "REST Json v1 serializes lists in queries" {
+ const allocator = std.testing.allocator;
+ const svs = Services(.{.lambda}){};
+ var keys = [_][]const u8{ "Foo", "Bar" }; // Would love to have a way to express this without burning a var here
+ const request = svs.lambda.untag_resource.Request{
+ .tag_keys = keys[0..],
+ .resource = "hello",
+ };
+ const query = try buildQuery(allocator, request);
+ defer allocator.free(query);
+ try std.testing.expectEqualStrings("?tagKeys=Foo&tagKeys=Bar", query);
+}
+test "REST Json v1 buildpath substitutes" {
+ const allocator = std.testing.allocator;
+ var al = std.ArrayList([]const u8){};
+ defer al.deinit(allocator);
+ 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, &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){};
+ defer al.deinit(allocator);
+ 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, &al);
+ defer allocator.free(output_path);
+ try std.testing.expectEqualStrings("https://myhost/%3A/", output_path);
+}
+test "basic json request serialization" {
+ const allocator = std.testing.allocator;
+ const svs = Services(.{.dynamo_db}){};
+ const request = svs.dynamo_db.list_tables.Request{
+ .limit = 1,
+ };
+ var buffer = std.Io.Writer.Allocating.init(allocator);
+ defer buffer.deinit();
+
+ // The transformer needs to allocate stuff out of band, but we
+ // can guarantee we don't need the memory after this call completes,
+ // so we'll use an arena allocator to whack everything.
+ // TODO: Determine if sending in null values is ok, or if we need another
+ // tweak to the stringify function to exclude. According to the
+ // smithy spec, "A null value MAY be provided or omitted
+ // for a boxed member with no observable difference." But we're
+ // seeing a lot of differences here between spec and reality
+ //
+ try buffer.writer.print("{f}", .{std.json.fmt(request, .{ .whitespace = .indent_4 })});
+ try std.testing.expectEqualStrings(
+ \\{
+ \\ "ExclusiveStartTableName": null,
+ \\ "Limit": 1
+ \\}
+ , buffer.written());
+}
+test "layer object only" {
+ const TestResponse = struct {
+ arn: ?[]const u8 = null,
+ // uncompressed_code_size: ?i64 = null,
+
+ pub fn jsonFieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 {
+ const mappings = .{
+ .arn = "Arn",
+ };
+ return @field(mappings, field_name);
+ }
+ };
+ const response =
+ \\ {
+ \\ "UncompressedCodeSize": 2,
+ \\ "Arn": "blah"
+ \\ }
+ ;
+ // const response =
+ // \\ {
+ // \\ "UncompressedCodeSize": 22599541,
+ // \\ "Arn": "arn:aws:lambda:us-west-2:123456789012:layer:PollyNotes-lib:4"
+ // \\ }
+ // ;
+ const allocator = std.testing.allocator;
+ var stream = json.TokenStream.init(response);
+ const parser_options = json.ParseOptions{
+ .allocator = allocator,
+ .allow_camel_case_conversion = true, // new option
+ .allow_snake_case_conversion = true, // new option
+ .allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
+ .allow_missing_fields = false, // new option. Cannot yet handle non-struct fields though
+ };
+ const r = try json.parse(TestResponse, &stream, parser_options);
+ json.parseFree(TestResponse, r, parser_options);
+}
+
+// Use for debugging json responses of specific requests
+// test "dummy request" {
+// const allocator = std.testing.allocator;
+// const svs = Services(.{.sts}){};
+// const request = svs.sts.get_session_token.Request{
+// .duration_seconds = 900,
+// };
+// const FullR = FullResponse(request);
+// const response =
+// var stream = json.TokenStream.init(response);
+//
+// const parser_options = json.ParseOptions{
+// .allocator = allocator,
+// .allow_camel_case_conversion = true, // new option
+// .allow_snake_case_conversion = true, // new option
+// .allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
+// .allow_missing_fields = false, // new option. Cannot yet handle non-struct fields though
+// };
+// const SResponse = ServerResponse(request);
+// const r = try json.parse(SResponse, &stream, parser_options);
+// json.parseFree(SResponse, r, parser_options);
+
+test {
+ // To run nested container tests, either, call `refAllDecls` which will
+ // reference all declarations located in the given argument.
+ // `@This()` is a builtin function that returns the innermost container it is called from.
+ // In this example, the innermost container is this file (implicitly a struct).
+ std.testing.refAllDecls(@This());
+ std.testing.refAllDecls(awshttp);
+ std.testing.refAllDecls(json);
+ std.testing.refAllDecls(@import("url.zig"));
+ std.testing.refAllDecls(@import("case"));
+ std.testing.refAllDecls(date);
+ std.testing.refAllDecls(@import("servicemodel.zig"));
+ std.testing.refAllDecls(@import("xml_shaper.zig"));
+}
+const TestOptions = struct {
+ allocator: std.mem.Allocator,
+ arena: ?*std.mem.ArenaAllocator = null,
+ server_port: ?u16 = null,
+ server_remaining_requests: usize = 1,
+ server_response: []const u8 = "unset",
+ server_response_status: std.http.Status = .ok,
+ server_response_headers: []const std.http.Header = &.{},
+ server_response_transfer_encoding: ?std.http.TransferEncoding = null,
+ request_body: []u8 = "",
+ request_method: std.http.Method = undefined,
+ request_target: []const u8 = undefined,
+ request_headers: []std.http.Header = undefined,
+ test_server_runtime_uri: ?[]u8 = null,
+ server_ready: std.Thread.Semaphore = .{},
+ requests_processed: usize = 0,
+
+ const Self = @This();
+
+ /// Builtin hashmap for strings as keys.
+ /// Key memory is managed by the caller. Keys and values
+ /// will not automatically be freed.
+ pub fn StringCaseInsensitiveHashMap(comptime V: type) type {
+ return std.HashMap([]const u8, V, StringInsensitiveContext, std.hash_map.default_max_load_percentage);
+ }
+
+ pub const StringInsensitiveContext = struct {
+ pub fn hash(self: @This(), s: []const u8) u64 {
+ _ = self;
+ return hashString(s);
+ }
+ pub fn eql(self: @This(), a: []const u8, b: []const u8) bool {
+ _ = self;
+ return eqlString(a, b);
+ }
+ };
+
+ pub fn eqlString(a: []const u8, b: []const u8) bool {
+ return std.ascii.eqlIgnoreCase(a, b);
+ }
+
+ pub fn hashString(s: []const u8) u64 {
+ var buf: [1024]u8 = undefined;
+ if (s.len > buf.len) unreachable; // tolower has a debug assert, but we want non-debug check too
+ const lower_s = std.ascii.lowerString(buf[0..], s);
+ return std.hash.Wyhash.hash(0, lower_s);
+ }
+
+ fn expectNoDuplicateHeaders(self: *Self) !void {
+ // As header keys are
+ var hm = StringCaseInsensitiveHashMap(void).init(self.allocator);
+ try hm.ensureTotalCapacity(@intCast(self.request_headers.len));
+ defer hm.deinit();
+ for (self.request_headers) |h| {
+ if (hm.getKey(h.name)) |_| {
+ log.err("Duplicate key detected. Key name: {s}", .{h.name});
+ return error.duplicateKeyDetected;
+ }
+ try hm.put(h.name, {});
+ }
+ }
+
+ fn expectHeader(self: *Self, name: []const u8, value: []const u8) !void {
+ for (self.request_headers) |h|
+ if (std.ascii.eqlIgnoreCase(name, h.name) and
+ std.mem.eql(u8, value, h.value)) return;
+ return error.HeaderOrValueNotFound;
+ }
+ fn waitForReady(self: *Self) !void {
+ // Set 10s timeout...this is way longer than necessary
+ log.debug("waiting for ready", .{});
+ try self.server_ready.timedWait(1000 * std.time.ns_per_ms);
+ // var deadline = std.Thread.Futex.Deadline.init(1000 * std.time.ns_per_ms);
+ // if (self.futex_word.load(.acquire) != 0) return;
+ // log.debug("futex zero", .{});
+ // // note that this seems backwards from the documentation...
+ // deadline.wait(self.futex_word, 1) catch {
+ // log.err("futex value {d}", .{self.futex_word.load(.acquire)});
+ // return error.TestServerTimeoutWaitingForReady;
+ // };
+ log.debug("the wait is over!", .{});
+ }
+};
+
+/// This starts a test server. We're not testing the server itself,
+/// so the main tests will start this thing up and create an arena around the
+/// whole thing so we can just deallocate everything at once at the end,
+/// leaks be damned
+fn threadMain(options: *TestOptions) !void {
+ // https://github.com/ziglang/zig/blob/d2be725e4b14c33dbd39054e33d926913eee3cd4/lib/compiler/std-docs.zig#L22-L54
+
+ options.arena = try options.allocator.create(std.mem.ArenaAllocator);
+ options.arena.?.* = std.heap.ArenaAllocator.init(options.allocator);
+ const allocator = options.arena.?.allocator();
+ options.allocator = allocator;
+
+ const address = try std.net.Address.parseIp("127.0.0.1", 0);
+ var http_server = try address.listen(.{});
+ options.server_port = http_server.listen_address.in.getPort();
+ // TODO: remove
+ options.test_server_runtime_uri = try std.fmt.allocPrint(options.allocator, "http://127.0.0.1:{d}", .{options.server_port.?});
+ log.debug("server listening at {s}", .{options.test_server_runtime_uri.?});
+ log.info("starting server thread, tid {d}", .{std.Thread.getCurrentId()});
+ // var arena = std.heap.ArenaAllocator.init(options.allocator);
+ // defer arena.deinit();
+ // var aa = arena.allocator();
+ // We're in control of all requests/responses, so this flag will tell us
+ // when it's time to shut down
+ if (options.server_remaining_requests == 0)
+ options.server_ready.post(); // This will cause the wait for server to return
+ while (options.server_remaining_requests > 0) : (options.server_remaining_requests -= 1) {
+ processRequest(options, &http_server) catch |e| {
+ log.err("Unexpected error processing request: {any}", .{e});
+ if (@errorReturnTrace()) |trace| {
+ std.debug.dumpStackTrace(trace.*);
+ }
+ };
+ }
+}
+
+fn processRequest(options: *TestOptions, net_server: *std.net.Server) !void {
+ log.debug(
+ "tid {d} (server): server waiting to accept. requests remaining: {d}",
+ .{ std.Thread.getCurrentId(), options.server_remaining_requests },
+ );
+ // options.futex_word.store(1, .release);
+ // errdefer options.futex_word.store(0, .release);
+ options.server_ready.post();
+ var connection = try net_server.accept();
+ defer connection.stream.close();
+ var read_buffer: [1024 * 16]u8 = undefined;
+ var http_server = std.http.Server.init(connection, &read_buffer);
+ while (http_server.state == .ready) {
+ var request = http_server.receiveHead() catch |err| switch (err) {
+ error.HttpConnectionClosing => return,
+ else => {
+ std.log.err("closing http connection: {s}", .{@errorName(err)});
+ std.log.debug("Error occurred from this request: \n{s}", .{read_buffer[0..http_server.read_buffer_len]});
+ return;
+ },
+ };
+ try serveRequest(options, &request);
+ }
+}
+
+fn serveRequest(options: *TestOptions, request: *std.http.Server.Request) !void {
+ options.requests_processed += 1;
+ options.request_body = try (try request.reader()).readAllAlloc(options.allocator, std.math.maxInt(usize));
+ options.request_method = request.head.method;
+ options.request_target = try options.allocator.dupe(u8, request.head.target);
+ var req_headers = std.ArrayList(std.http.Header).init(options.allocator);
+ defer req_headers.deinit();
+ var it = request.iterateHeaders();
+ while (it.next()) |f| {
+ const h = try options.allocator.create(std.http.Header);
+ h.* = .{ .name = try options.allocator.dupe(u8, f.name), .value = try options.allocator.dupe(u8, f.value) };
+ try req_headers.append(h.*);
+ }
+ options.request_headers = try req_headers.toOwnedSlice();
+ log.debug(
+ "tid {d} (server): {d} bytes read from request",
+ .{ std.Thread.getCurrentId(), options.request_body.len },
+ );
+
+ // try response.headers.append("content-type", "text/plain");
+ try request.respond(options.server_response, .{
+ .status = options.server_response_status,
+ .extra_headers = options.server_response_headers,
+ });
+
+ log.debug(
+ "tid {d} (server): sent response",
+ .{std.Thread.getCurrentId()},
+ );
+}
+
+////////////////////////////////////////////////////////////////////////
+// These will replicate the tests that were in src/main.zig
+// The server_response and server_response_headers come from logs of
+// a previous run of src/main.zig, with redactions
+////////////////////////////////////////////////////////////////////////
+
+const TestSetup = struct {
+ allocator: std.mem.Allocator,
+ request_options: TestOptions,
+ server_thread: std.Thread = undefined,
+ creds: aws_auth.Credentials = undefined,
+ client: aws.Client = undefined,
+ started: bool = false,
+
+ const Self = @This();
+
+ const aws_creds = @import("aws_credentials.zig");
+ const aws_auth = @import("aws_authentication.zig");
+ const signing_time =
+ date.dateTimeToTimestamp(date.parseIso8601ToDateTime("20230908T170252Z") catch @compileError("Cannot parse date")) catch @compileError("Cannot parse date");
+
+ fn init(options: TestOptions) Self {
+ return .{
+ .request_options = options,
+ .allocator = options.allocator,
+ };
+ }
+
+ fn start(self: *Self) !aws.Options {
+ self.server_thread = try std.Thread.spawn(
+ .{},
+ threadMain,
+ .{&self.request_options},
+ );
+ self.started = true;
+ try self.request_options.waitForReady();
+ // Not sure why we're getting sprayed here, but we have an arena allocator, and this
+ // is testing, so yolo
+ awshttp.endpoint_override = self.request_options.test_server_runtime_uri;
+ if (awshttp.endpoint_override == null) return error.TestSetupStartFailure;
+ std.log.debug("endpoint override set to {?s}", .{awshttp.endpoint_override});
+ self.creds = aws_auth.Credentials.init(
+ self.allocator,
+ try self.allocator.dupe(u8, "ACCESS"),
+ try self.allocator.dupe(u8, "SECRET"),
+ null,
+ );
+ aws_creds.static_credentials = self.creds;
+ const client = aws.Client.init(self.allocator, .{});
+ self.client = client;
+ return .{
+ .region = "us-west-2",
+ .client = client,
+ .signing_time = signing_time,
+ };
+ }
+
+ fn stop(self: *Self) void {
+ if (self.request_options.server_remaining_requests > 0)
+ if (test_error_log_enabled)
+ std.log.err(
+ "Test server has {d} request(s) remaining to issue! Draining",
+ .{self.request_options.server_remaining_requests},
+ )
+ else
+ std.log.info(
+ "Test server has {d} request(s) remaining to issue! Draining",
+ .{self.request_options.server_remaining_requests},
+ );
+
+ var rr = self.request_options.server_remaining_requests;
+ while (rr > 0) : (rr -= 1) {
+ std.log.debug("rr: {d}", .{self.request_options.server_remaining_requests});
+ // We need to drain all remaining requests, otherwise the server
+ // will hang indefinitely
+ var client = std.http.Client{ .allocator = self.allocator };
+ defer client.deinit();
+ _ = client.fetch(.{ .location = .{ .url = self.request_options.test_server_runtime_uri.? } }) catch unreachable;
+ }
+ self.server_thread.join();
+ }
+
+ fn deinit(self: *Self) void {
+ if (self.request_options.arena) |a| {
+ a.deinit();
+ self.allocator.destroy(a);
+ }
+ if (!self.started) return;
+ awshttp.endpoint_override = null;
+ // creds.deinit(); Creds will get deinited in the course of the call. We don't want to do it twice
+ aws_creds.static_credentials = null; // we do need to reset the static creds for the next user though
+ self.client.deinit();
+ }
+};
+
+test "query_no_input: sts getCallerIdentity comptime" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{"GetCallerIdentityResponse":{"GetCallerIdentityResult":{"Account":"123456789012","Arn":"arn:aws:iam::123456789012:user/admin","UserId":"AIDAYAM4POHXHRVANDQBQ"},"ResponseMetadata":{"RequestId":"8f0d54da-1230-40f7-b4ac-95015c4b84cd"}}}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "8f0d54da-1230-40f7-b4ac-95015c4b84cd" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const sts = (Services(.{.sts}){}).sts;
+ const call = try aws.Request(sts.get_caller_identity).call(.{}, options);
+ // const call = try client.call(services.sts.get_caller_identity.Request{}, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings(
+ \\Action=GetCallerIdentity&Version=2011-06-15
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings(
+ "arn:aws:iam::123456789012:user/admin",
+ call.response.arn.?,
+ );
+ try std.testing.expectEqualStrings("AIDAYAM4POHXHRVANDQBQ", call.response.user_id.?);
+ try std.testing.expectEqualStrings("123456789012", call.response.account.?);
+ try std.testing.expectEqualStrings("8f0d54da-1230-40f7-b4ac-95015c4b84cd", call.response_metadata.request_id);
+}
+test "query_with_input: iam getRole runtime" {
+ // sqs switched from query to json in aws sdk for go v2 commit f5a08768ef820ff5efd62a49ba50c61c9ca5dbcb
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\
+ \\
+ \\
+ \\ /application_abc/component_xyz/
+ \\ arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access
+ \\ S3Access
+ \\
+ \\ {"Version":"2012-10-17","Statement":[{"Effect":"Allow",
+ \\ "Principal":{"Service":["ec2.amazonaws.com"]},"Action":["sts:AssumeRole"]}]}
+ \\
+ \\ 2012-05-08T23:34:01Z
+ \\ AROADBQP57FF2AEXAMPLE
+ \\
+ \\ 2019-11-20T17:09:20Z
+ \\ us-east-1
+ \\
+ \\
+ \\
+ \\
+ \\ df37e965-9967-11e1-a4c3-270EXAMPLE04
+ \\
+ \\
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "text/xml" },
+ .{ .name = "x-amzn-RequestId", .value = "df37e965-9967-11e1-a4c3-270EXAMPLE04" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const iam = (Services(.{.iam}){}).iam;
+ const call = try test_harness.client.call(iam.get_role.Request{
+ .role_name = "S3Access",
+ }, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings(
+ \\Action=GetRole&Version=2010-05-08&RoleName=S3Access
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("arn:aws:iam::123456789012:role/application_abc/component_xyz/S3Access", call.response.role.arn);
+ try std.testing.expectEqualStrings("df37e965-9967-11e1-a4c3-270EXAMPLE04", call.response_metadata.request_id);
+}
+test "query_with_input: sts getAccessKeyInfo runtime" {
+ // sqs switched from query to json in aws sdk for go v2 commit f5a08768ef820ff5efd62a49ba50c61c9ca5dbcb
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\
+ \\
+ \\ 123456789012
+ \\
+ \\
+ \\ ec85bf29-1ef0-459a-930e-6446dd14a286
+ \\
+ \\
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "text/xml" },
+ .{ .name = "x-amzn-RequestId", .value = "ec85bf29-1ef0-459a-930e-6446dd14a286" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const sts = (Services(.{.sts}){}).sts;
+ const call = try test_harness.client.call(sts.get_access_key_info.Request{
+ .access_key_id = "ASIAYAM4POHXJNKTYFUN",
+ }, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings(
+ \\Action=GetAccessKeyInfo&Version=2011-06-15&AccessKeyId=ASIAYAM4POHXJNKTYFUN
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expect(call.response.account != null);
+ try std.testing.expectEqualStrings("123456789012", call.response.account.?);
+ try std.testing.expectEqualStrings("ec85bf29-1ef0-459a-930e-6446dd14a286", call.response_metadata.request_id);
+}
+test "json_1_0_query_with_input: dynamodb listTables runtime" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{"LastEvaluatedTableName":"Customer","TableNames":["Customer"]}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const dynamo_db = services.dynamo_db;
+ const call = try test_harness.client.call(dynamo_db.list_tables.Request{
+ .limit = 1,
+ }, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
+ try test_harness.request_options.expectHeader("X-Amz-Target", "DynamoDB_20120810.ListTables");
+
+ const parsed_body = try std.json.parseFromSlice(struct {
+ ExclusiveStartTableName: ?[]const u8,
+ Limit: u8,
+ }, testing.allocator, test_harness.request_options.request_body, .{});
+ defer parsed_body.deinit();
+
+ try testing.expectEqual(null, parsed_body.value.ExclusiveStartTableName);
+ try testing.expectEqual(1, parsed_body.value.Limit);
+
+ // Response expectations
+ try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 1), call.response.table_names.?.len);
+ try std.testing.expectEqualStrings("Customer", call.response.table_names.?[0]);
+}
+
+test "json_1_0_query_no_input: dynamodb listTables runtime" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{"AccountMaxReadCapacityUnits":80000,"AccountMaxWriteCapacityUnits":80000,"TableMaxReadCapacityUnits":40000,"TableMaxWriteCapacityUnits":40000}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const dynamo_db = (Services(.{.dynamo_db}){}).dynamo_db;
+ const call = try test_harness.client.call(dynamo_db.describe_limits.Request{}, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
+ try test_harness.request_options.expectHeader("X-Amz-Target", "DynamoDB_20120810.DescribeLimits");
+ try std.testing.expectEqualStrings(
+ \\{}
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(i64, 80000), call.response.account_max_read_capacity_units.?);
+}
+test "json_1_1_query_with_input: ecs listClusters runtime" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{"clusterArns":["arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster"],"nextToken":"czE0Og=="}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "b2420066-ff67-4237-b782-721c4df60744" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const ecs = (Services(.{.ecs}){}).ecs;
+ const call = try test_harness.client.call(ecs.list_clusters.Request{
+ .max_results = 1,
+ }, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
+ try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters");
+
+ const parsed_body = try std.json.parseFromSlice(struct {
+ nextToken: ?[]const u8,
+ maxResults: u8,
+ }, testing.allocator, test_harness.request_options.request_body, .{});
+ defer parsed_body.deinit();
+
+ try testing.expectEqual(null, parsed_body.value.nextToken);
+ try testing.expectEqual(1, parsed_body.value.maxResults);
+
+ // Response expectations
+ try std.testing.expectEqualStrings("b2420066-ff67-4237-b782-721c4df60744", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 1), call.response.cluster_arns.?.len);
+ try std.testing.expectEqualStrings("arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster", call.response.cluster_arns.?[0]);
+}
+test "json_1_1_query_no_input: ecs listClusters runtime" {
+ // const old = std.testing.log_level;
+ // defer std.testing.log_level = old;
+ // std.testing.log_level = .debug;
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{"clusterArns":["arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster"],"nextToken":"czE0Og=="}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "e65322b2-0065-45f2-ba37-f822bb5ce395" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const ecs = (Services(.{.ecs}){}).ecs;
+ const call = try test_harness.client.call(ecs.list_clusters.Request{}, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
+ try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerServiceV20141113.ListClusters");
+
+ const parsed_body = try std.json.parseFromSlice(struct {
+ nextToken: ?[]const u8,
+ maxResults: ?u8,
+ }, testing.allocator, test_harness.request_options.request_body, .{});
+ defer parsed_body.deinit();
+
+ try testing.expectEqual(null, parsed_body.value.nextToken);
+ try testing.expectEqual(null, parsed_body.value.maxResults);
+
+ // Response expectations
+ try std.testing.expectEqualStrings("e65322b2-0065-45f2-ba37-f822bb5ce395", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 1), call.response.cluster_arns.?.len);
+ try std.testing.expectEqualStrings("arn:aws:ecs:us-west-2:550620852718:cluster/web-applicationehjaf-cluster", call.response.cluster_arns.?[0]);
+}
+test "rest_json_1_query_with_input: lambda listFunctions runtime" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{"Functions":[{"Description":"AWS CDK resource provider framework - onEvent (DevelopmentFrontendStack-g650u/com.amazonaws.cdk.custom-resources.amplify-asset-deployment-provider/amplify-asset-deployment-handler-provider)","TracingConfig":{"Mode":"PassThrough"},"VpcConfig":null,"SigningJobArn":null,"SnapStart":{"OptimizationStatus":"Off","ApplyOn":"None"},"RevisionId":"0c62fc74-a692-403d-9206-5fcbad406424","LastModified":"2023-03-01T18:13:15.704+0000","FileSystemConfigs":null,"FunctionName":"DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU","Runtime":"nodejs14.x","Version":"$LATEST","PackageType":"Zip","LastUpdateStatus":null,"Layers":null,"FunctionArn":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU","KMSKeyArn":null,"MemorySize":128,"ImageConfigResponse":null,"LastUpdateStatusReason":null,"DeadLetterConfig":null,"Timeout":900,"Handler":"framework.onEvent","CodeSha256":"m4tt+M0l3p8bZvxIDj83dwGrwRW6atCfS/q8AiXCD3o=","Role":"arn:aws:iam::550620852718:role/DevelopmentFrontendStack-amplifyassetdeploymentha-1782JF7WAPXZ3","SigningProfileVersionArn":null,"MasterArn":null,"RuntimeVersionConfig":null,"CodeSize":4307,"State":null,"StateReason":null,"Environment":{"Variables":{"USER_ON_EVENT_FUNCTION_ARN":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymenton-X9iZJSCSPYDH","WAITER_STATE_MACHINE_ARN":"arn:aws:states:us-west-2:550620852718:stateMachine:amplifyassetdeploymenthandlerproviderwaiterstatemachineB3C2FCBE-Ltggp5wBcHWO","USER_IS_COMPLETE_FUNCTION_ARN":"arn:aws:lambda:us-west-2:550620852718:function:DevelopmentFrontendStack--amplifyassetdeploymentis-jaHopLrSSARV"},"Error":null},"EphemeralStorage":{"Size":512},"StateReasonCode":null,"LastUpdateStatusReasonCode":null,"Architectures":["x86_64"]}],"NextMarker":"lslTXFcbLQKkb0vP9Kgh5hUL7C3VghELNGbWgZfxrRCk3eiDRMkct7D8EmptWfHSXssPdS7Bo66iQPTMpVOHZgANewpgGgFGGr4pVjd6VgLUO6qPe2EMAuNDBjUTxm8z6N28yhlUwEmKbrAV/m0k5qVzizwoxFwvyruMbuMx9kADFACSslcabxXl3/jDI4rfFnIsUVdzTLBgPF1hzwrE1f3lcdkBvUp+QgY+Pn3w5QuJmwsp/di8COzFemY89GgOHbLNqsrBsgR/ee2eXoJp0ZkKM4EcBK3HokqBzefLfgR02PnfNOdXwqTlhkSPW0TKiKGIYu3Bw7lSNrLd+q3+wEr7ZakqOQf0BVo3FMRhMHlVYgwUJzwi3ActyH2q6fuqGG1sS0B8Oa/prUpe5fmp3VaA3WpazioeHtrKF78JwCi6/nfQsrj/8ZtXGQOxlwEgvT1CIUaF+CdHY3biezrK0tRZNpkCtHnkPtF9lq2U7+UiKXSW9yzxT8P2b0M/Qh4IVdnw4rncQK/doYriAeOdrs1wjMEJnHWq9lAaEyipoxYcVr/z5+yaC6Gwxdg45p9X1vIAaYMf6IZxyFuua43SYi0Ls+IBk4VvpR2io7T0dCxHAr3WAo3D2dm0y8OsbM59"}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "c4025199-226f-4a16-bb1f-48618e9d2ea6" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const lambda = (Services(.{.lambda}){}).lambda;
+ const call = try test_harness.client.call(lambda.list_functions.Request{
+ .max_items = 1,
+ }, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/2015-03-31/functions?MaxItems=1", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings(
+ \\
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("c4025199-226f-4a16-bb1f-48618e9d2ea6", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 1), call.response.functions.?.len);
+ try std.testing.expectEqualStrings(
+ "DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU",
+ call.response.functions.?[0].function_name.?,
+ );
+}
+test "rest_json_1_query_no_input: lambda listFunctions runtime" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response = @embedFile("test_rest_json_1_query_no_input.response"),
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "b2aad11f-36fc-4d0d-ae92-fe0167fb0f40" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const lambda = (Services(.{.lambda}){}).lambda;
+ const call = try test_harness.client.call(lambda.list_functions.Request{}, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/2015-03-31/functions", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings(
+ \\
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("b2aad11f-36fc-4d0d-ae92-fe0167fb0f40", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 24), call.response.functions.?.len);
+ try std.testing.expectEqualStrings(
+ "DevelopmentFrontendStack--amplifyassetdeploymentha-aZqB9IbZLIKU",
+ call.response.functions.?[0].function_name.?,
+ );
+ try std.testing.expectEqualStrings(
+ "amplify-login-create-auth-challenge-b4883e4c",
+ call.response.functions.?[12].function_name.?,
+ );
+}
+test "rest_json_1_work_with_lambda: lambda tagResource (only), to excercise zig issue 17015" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response = "",
+ .server_response_status = .no_content,
+ .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;
+ var tags = try std.ArrayList(@typeInfo(try typeForField(lambda.tag_resource.Request, "tags")).pointer.child).initCapacity(allocator, 1);
+ defer tags.deinit(allocator);
+ tags.appendAssumeCapacity(.{ .key = "Foo", .value = "Bar" });
+ const req = services.lambda.tag_resource.Request{ .resource = "arn:aws:lambda:us-west-2:550620852718:function:awsome-lambda-LambdaStackawsomeLambda", .tags = tags.items };
+ const call = try aws.Request(lambda.tag_resource).call(req, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+
+ const parsed_body = try std.json.parseFromSlice(struct {
+ Tags: struct {
+ Foo: []const u8,
+ },
+ }, testing.allocator, test_harness.request_options.request_body, .{ .ignore_unknown_fields = true });
+ defer parsed_body.deinit();
+
+ try testing.expectEqualStrings("Bar", parsed_body.value.Tags.Foo);
+
+ // Due to 17015, we see %253A instead of %3A
+ try std.testing.expectEqualStrings("/2017-03-31/tags/arn%3Aaws%3Alambda%3Aus-west-2%3A550620852718%3Afunction%3Aawsome-lambda-LambdaStackawsomeLambda", test_harness.request_options.request_target);
+ // 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 aws.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);
+
+ const parsed_body = try std.json.parseFromSlice(struct {
+ ZipFile: []const u8,
+ Architectures: [][]const u8,
+ }, testing.allocator, test_harness.request_options.request_body, .{
+ .ignore_unknown_fields = true,
+ });
+ defer parsed_body.deinit();
+
+ try testing.expectEqualStrings("zipfile", parsed_body.value.ZipFile);
+ try testing.expectEqual(1, parsed_body.value.Architectures.len);
+ try testing.expectEqualStrings("x86_64", parsed_body.value.Architectures[0]);
+
+ // 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(.{
+ .allocator = allocator,
+ .server_response = @embedFile("test_ec2_query_no_input.response"),
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "text/xml;charset=UTF-8" },
+ .{ .name = "x-amzn-RequestId", .value = "4cdbdd69-800c-49b5-8474-ae4c17709782" },
+ },
+ .server_response_transfer_encoding = .chunked,
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const ec2 = (Services(.{.ec2}){}).ec2;
+ const call = try test_harness.client.call(ec2.describe_regions.Request{}, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/?Action=DescribeRegions&Version=2016-11-15", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings(
+ \\Action=DescribeRegions&Version=2016-11-15
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("4cdbdd69-800c-49b5-8474-ae4c17709782", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 17), call.response.regions.?.len);
+}
+// LLVM hates this test. Depending on the platform, it will consume all memory
+// on the compilation host. Windows x86_64 and Linux riscv64 seem to be a problem so far
+// riscv64-linux also seems to have another problem with LLVM basically infinitely
+// doing something. My guess is the @embedFile is freaking out LLVM
+test "ec2_query_with_input: EC2 describe instances" {
+ if (builtin.cpu.arch == .riscv64 and builtin.os.tag == .linux) return error.SkipZigTest;
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response = @embedFile("test_ec2_query_with_input.response"),
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "text/xml;charset=UTF-8" },
+ .{ .name = "x-amzn-RequestId", .value = "150a14cc-785d-476f-a4c9-2aa4d03b14e2" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const ec2 = (Services(.{.ec2}){}).ec2;
+ const call = try test_harness.client.call(ec2.describe_instances.Request{
+ .max_results = 6,
+ }, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/?Action=DescribeInstances&Version=2016-11-15", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings(
+ \\Action=DescribeInstances&Version=2016-11-15&MaxResults=6
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("150a14cc-785d-476f-a4c9-2aa4d03b14e2", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 6), call.response.reservations.?.len);
+ try std.testing.expectEqualStrings("i-0212d7d1f62b96676", call.response.reservations.?[1].instances.?[0].instance_id.?);
+ try std.testing.expectEqualStrings("123456789012:found-me", call.response.reservations.?[1].instances.?[0].tags.?[0].value.?);
+}
+test "rest_xml_with_input_s3: S3 create bucket" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\
+ ,
+ .server_response_headers = &.{ // I don't see content type coming back in actual S3 requests
+ .{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" },
+ .{ .name = "x-amz-id-2", .value = "u7lzgW0tIyRP15vSUsVOXxJ37OfVCO8lZmLIVuqeq5EE4tNp9qebb5fy+/kendlZpR4YQE+y4Xg=" },
+ },
+ });
+ defer test_harness.deinit();
+ errdefer test_harness.creds.deinit();
+ const options = try test_harness.start();
+ const s3 = (Services(.{.s3}){}).s3;
+ const call = try test_harness.client.call(s3.create_bucket.Request{
+ .bucket = "",
+ .create_bucket_configuration = .{
+ .location_constraint = "us-west-2",
+ },
+ }, 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("/", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings(
+ \\
+ \\ us-west-2
+ \\
+ , test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings(
+ "9PEYBAZ9J7TPRX43, host_id: u7lzgW0tIyRP15vSUsVOXxJ37OfVCO8lZmLIVuqeq5EE4tNp9qebb5fy+/kendlZpR4YQE+y4Xg=",
+ call.response_metadata.request_id,
+ );
+}
+test "rest_xml_no_input: S3 list buckets" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\3367189aa775bd98da38e55093705f2051443c1e775fc0971d6d77387a47c8d0emilerch+sub1550620852718-backup2020-06-17T16:26:51.000Zamplify-letmework-staging-185741-deployment2023-03-10T18:57:49.000Zaws-cloudtrail-logs-550620852718-224022a72021-06-21T18:32:44.000Zaws-sam-cli-managed-default-samclisourcebucket-1gy0z00mj47xe2021-10-05T16:38:07.000Zawsomeprojectstack-pipelineartifactsbucketaea9a05-1uzwo6c86ecr2021-10-05T22:55:09.000Zcdk-hnb659fds-assets-550620852718-us-west-22023-02-28T21:49:36.000Zcf-templates-12iy6putgdxtk-us-west-22020-06-26T02:31:59.000Zcodepipeline-us-west-2-467140836372021-09-14T18:43:07.000Zelasticbeanstalk-us-west-2-5506208527182022-04-15T16:22:42.000Zlobo-west2021-06-21T17:17:22.000Zlobo-west-22021-11-19T20:12:31.000Zlogging-backup-550620852718-us-east-22022-05-29T21:55:16.000Zmysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr02023-03-01T04:53:55.000Z
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/xml" },
+ .{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const s3 = (Services(.{.s3}){}).s3;
+ const call = try test_harness.client.call(s3.list_buckets.Request{}, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method);
+ // This changed in rev 830202d722c904c7e3da40e8dde7b9338d08752c of the go sdk, and
+ // contrary to the documentation, a query string argument was added. My guess is that
+ // there is no functional reason, and that this is strictly for some AWS reporting function.
+ // Alternatively, it could be to support some customization mechanism, as the commit
+ // title of that commit is "Merge customizations for S3"
+ try std.testing.expectEqualStrings("/?x-id=ListBuckets", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings("", test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("9PEYBAZ9J7TPRX43", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 13), call.response.buckets.?.len);
+}
+test "rest_xml_anything_but_s3: CloudFront list key groups" {
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{"Items":null,"MaxItems":100,"NextMarker":null,"Quantity":0}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "d3382082-5291-47a9-876b-8df3accbb7ea" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const cloudfront = (Services(.{.cloudfront}){}).cloudfront;
+ const call = try test_harness.client.call(cloudfront.list_key_groups.Request{}, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.GET, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/2020-05-31/key-group", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings("", test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("d3382082-5291-47a9-876b-8df3accbb7ea", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(i64, 100), call.response.key_group_list.?.max_items);
+}
+test "rest_xml_with_input: S3 put object" {
+ // const old = std.testing.log_level;
+ // defer std.testing.log_level = old;
+ // std.testing.log_level = .debug;
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response = "",
+ .server_response_headers = &.{
+ // .{ "Content-Type", "application/xml" },
+ .{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" },
+ .{ .name = "x-amz-id-2", .value = "jdRDo30t7Ge9lf6F+4WYpg+YKui8z0mz2+rwinL38xDZzvloJqrmpCAiKG375OSvHA9OBykJS44=" },
+ .{ .name = "x-amz-server-side-encryption", .value = "AES256" },
+ .{ .name = "ETag", .value = "37b51d194a7513e45b56f6524f2d51f2" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const s3opts = aws.Options{
+ .region = "us-west-2",
+ .client = options.client,
+ .signing_time = TestSetup.signing_time,
+ };
+ const result = try aws.Request(services.s3.put_object).call(.{
+ .bucket = "mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0",
+ .key = "i/am/a/teapot/foo",
+ .content_type = "text/plain",
+ .body = "bar",
+ .storage_class = "STANDARD",
+ }, s3opts);
+ defer result.deinit();
+ for (test_harness.request_options.request_headers) |header| {
+ std.log.info("Request header: {s}: {s}", .{ header.name, header.value });
+ }
+ try test_harness.request_options.expectNoDuplicateHeaders();
+ std.log.info("PutObject Request id: {s}", .{result.response_metadata.request_id});
+ std.log.info("PutObject etag: {s}", .{result.response.e_tag.?});
+ //mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0.s3.us-west-2.amazonaws.com
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.PUT, test_harness.request_options.request_method);
+ // I don't think this will work since we're overriding the url
+ // try test_harness.request_options.expectHeader("Host", "mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0.s3.us-west-2.amazonaws.com");
+ try test_harness.request_options.expectHeader("x-amz-storage-class", "STANDARD");
+ try std.testing.expectEqualStrings("/mysfitszj3t6webstack-hostingbucketa91a61fe-1ep3ezkgwpxr0/i/am/a/teapot/foo?x-id=PutObject", test_harness.request_options.request_target);
+ try std.testing.expectEqualStrings("bar", test_harness.request_options.request_body);
+ // Response expectations
+ try std.testing.expectEqualStrings("9PEYBAZ9J7TPRX43, host_id: jdRDo30t7Ge9lf6F+4WYpg+YKui8z0mz2+rwinL38xDZzvloJqrmpCAiKG375OSvHA9OBykJS44=", result.response_metadata.request_id);
+ try std.testing.expectEqualStrings("AES256", result.response.server_side_encryption.?);
+ try std.testing.expectEqualStrings("37b51d194a7513e45b56f6524f2d51f2", result.response.e_tag.?);
+}
+test "raw ECR timestamps" {
+ // This is a way to test the json parsing. Ultimately the more robust tests
+ // should be preferred, but in this case we were tracking down an issue
+ // for which the root cause was the incorrect type being passed to the parse
+ // routine
+ const allocator = std.testing.allocator;
+ const ecr = (Services(.{.ecr}){}).ecr;
+ const options = json.ParseOptions{
+ .allocator = allocator,
+ .allow_camel_case_conversion = true, // new option
+ .allow_snake_case_conversion = true, // new option
+ .allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
+ .allow_missing_fields = false, // new option. Cannot yet handle non-struct fields though
+ };
+ var stream = json.TokenStream.init(
+ \\{"authorizationData":[{"authorizationToken":"***","expiresAt":1.7385984915E9,"proxyEndpoint":"https://146325435496.dkr.ecr.us-west-2.amazonaws.com"}]}
+ );
+ const ptr = try json.parse(ecr.get_authorization_token.Response, &stream, options);
+ defer json.parseFree(ecr.get_authorization_token.Response, ptr, options);
+}
+test "json_1_1: ECR timestamps" {
+ // See: https://github.com/elerch/aws-sdk-for-zig/issues/5
+ // const old = std.testing.log_level;
+ // defer std.testing.log_level = old;
+ // std.testing.log_level = .debug;
+ const allocator = std.testing.allocator;
+
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{"authorizationData":[{"authorizationToken":"***","expiresAt":"2022-05-17T06:56:13.652000+00:00","proxyEndpoint":"https://146325435496.dkr.ecr.us-west-2.amazonaws.com"}]}
+ // \\{"authorizationData":[{"authorizationToken":"***","expiresAt":1.738598491557E9,"proxyEndpoint":"https://146325435496.dkr.ecr.us-west-2.amazonaws.com"}]}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
+ },
+ });
+ defer test_harness.deinit();
+ const options = try test_harness.start();
+ const ecr = (Services(.{.ecr}){}).ecr;
+ std.log.debug("Typeof response {}", .{@TypeOf(ecr.get_authorization_token.Response{})});
+ const call = try test_harness.client.call(ecr.get_authorization_token.Request{}, options);
+ defer call.deinit();
+ test_harness.stop();
+ // Request expectations
+ try std.testing.expectEqual(std.http.Method.POST, test_harness.request_options.request_method);
+ try std.testing.expectEqualStrings("/", test_harness.request_options.request_target);
+ try test_harness.request_options.expectHeader("X-Amz-Target", "AmazonEC2ContainerRegistry_V20150921.GetAuthorizationToken");
+ // Response expectations
+ try std.testing.expectEqualStrings("QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG", call.response_metadata.request_id);
+ try std.testing.expectEqual(@as(usize, 1), call.response.authorization_data.?.len);
+ try std.testing.expectEqualStrings("***", call.response.authorization_data.?[0].authorization_token.?);
+ try std.testing.expectEqualStrings("https://146325435496.dkr.ecr.us-west-2.amazonaws.com", call.response.authorization_data.?[0].proxy_endpoint.?);
+ // try std.testing.expectEqual(@as(i64, 1.73859841557E9), call.response.authorization_data.?[0].expires_at.?);
+
+ const zeit = @import("zeit");
+ const expected_ins = try zeit.instant(.{
+ .source = .{ .iso8601 = "2022-05-17T06:56:13.652000+00:00" },
+ });
+ const expected_ts: date.Timestamp = @enumFromInt(expected_ins.timestamp);
+
+ try std.testing.expectEqual(expected_ts, call.response.authorization_data.?[0].expires_at.?);
+}
+var test_error_log_enabled = true;
+test "test server timeout works" {
+ // const old = std.testing.log_level;
+ // defer std.testing.log_level = old;
+ // std.testing.log_level = .debug;
+ // defer std.testing.log_level = old;
+ // std.testing.log_level = .debug;
+ test_error_log_enabled = false;
+ defer test_error_log_enabled = true;
+ std.log.debug("test start", .{});
+ const allocator = std.testing.allocator;
+ var test_harness = TestSetup.init(.{
+ .allocator = allocator,
+ .server_response =
+ \\{}
+ ,
+ .server_response_headers = &.{
+ .{ .name = "Content-Type", .value = "application/json" },
+ .{ .name = "x-amzn-RequestId", .value = "QBI72OUIN8U9M9AG6PCSADJL4JVV4KQNSO5AEMVJF66Q9ASUAAJG" },
+ },
+ });
+ defer test_harness.deinit();
+ defer test_harness.creds.deinit(); // Usually this gets done during the call,
+ // but we're purposely not making a call
+ // here, so we have to deinit() manually
+ _ = try test_harness.start();
+ std.log.debug("harness started", .{});
+ test_harness.stop();
+ std.log.debug("test complete", .{});
+}
+
+const testing = std.testing;
+
+test "jsonStringify: structure + enums" {
+ const request = services.media_convert.PutPolicyRequest{
+ .policy = .{
+ .http_inputs = "foo",
+ .https_inputs = "bar",
+ .s3_inputs = "baz",
+ },
+ };
+
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+
+ const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
+ defer std.testing.allocator.free(request_json);
+
+ const parsed = try std.json.parseFromSlice(struct {
+ policy: struct {
+ httpInputs: []const u8,
+ httpsInputs: []const u8,
+ s3Inputs: []const u8,
+ },
+ }, testing.allocator, request_json, .{});
+ defer parsed.deinit();
+
+ try testing.expectEqualStrings("foo", parsed.value.policy.httpInputs);
+ try testing.expectEqualStrings("bar", parsed.value.policy.httpsInputs);
+ try testing.expectEqualStrings("baz", parsed.value.policy.s3Inputs);
+}
+
+test "jsonStringify: strings" {
+ const request = services.media_convert.AssociateCertificateRequest{
+ .arn = "1234",
+ };
+
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+
+ const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
+ defer std.testing.allocator.free(request_json);
+
+ try testing.expectEqualStrings("{\"arn\":\"1234\"}", request_json);
+}
+
+test "jsonStringify" {
+ var tags = [_]services.media_convert.MapOfStringKeyValue{
+ .{
+ .key = "foo",
+ .value = "bar",
+ },
+ };
+
+ const request = services.media_convert.TagResourceRequest{
+ .arn = "1234",
+ .tags = &tags,
+ };
+
+ var arena = std.heap.ArenaAllocator.init(testing.allocator);
+ defer arena.deinit();
+
+ const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
+ defer std.testing.allocator.free(request_json);
+
+ const json_parsed = try std.json.parseFromSlice(struct {
+ arn: []const u8,
+ tags: struct {
+ foo: []const u8,
+ },
+ }, testing.allocator, request_json, .{});
+ defer json_parsed.deinit();
+
+ try testing.expectEqualStrings("1234", json_parsed.value.arn);
+ try testing.expectEqualStrings("bar", json_parsed.value.tags.foo);
+}
+
+test "jsonStringify nullable object" {
+ // structure is not null
+ {
+ const request = services.lambda.CreateAliasRequest{
+ .function_name = "foo",
+ .function_version = "bar",
+ .name = "baz",
+ .routing_config = services.lambda.AliasRoutingConfiguration{
+ .additional_version_weights = null,
+ },
+ };
+
+ const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
+ defer std.testing.allocator.free(request_json);
+
+ const json_parsed = try std.json.parseFromSlice(struct {
+ FunctionName: []const u8,
+ FunctionVersion: []const u8,
+ Name: []const u8,
+ RoutingConfig: struct {
+ AdditionalVersionWeights: ?struct {},
+ },
+ }, testing.allocator, request_json, .{ .ignore_unknown_fields = true });
+ defer json_parsed.deinit();
+
+ try testing.expectEqualStrings("foo", json_parsed.value.FunctionName);
+ try testing.expectEqualStrings("bar", json_parsed.value.FunctionVersion);
+ try testing.expectEqualStrings("baz", json_parsed.value.Name);
+ try testing.expectEqual(null, json_parsed.value.RoutingConfig.AdditionalVersionWeights);
+ }
+
+ // structure is null
+ {
+ const request = services.kms.DecryptRequest{
+ .key_id = "foo",
+ .ciphertext_blob = "bar",
+ };
+
+ const request_json = try std.fmt.allocPrint(std.testing.allocator, "{f}", .{std.json.fmt(request, .{})});
+ defer std.testing.allocator.free(request_json);
+
+ const json_parsed = try std.json.parseFromSlice(struct {
+ KeyId: []const u8,
+ CiphertextBlob: []const u8,
+ }, testing.allocator, request_json, .{ .ignore_unknown_fields = true });
+ defer json_parsed.deinit();
+
+ try testing.expectEqualStrings("foo", json_parsed.value.KeyId);
+ try testing.expectEqualStrings("bar", json_parsed.value.CiphertextBlob);
+ }
+}