Compare commits

..

No commits in common. "bd3605e387acf18c2bb665cdc8cf2f5dd099dad8" and "ad5459683702f6fcd3bb3adc65483bad3e18464c" have entirely different histories.

6 changed files with 26 additions and 196 deletions

View File

@ -105,6 +105,6 @@ RUN tar -czf aws-c-auth-clang.tgz /usr/local/*
FROM alpine:3.13 as final
COPY --from=auth /aws-c-auth-clang.tgz /
ADD https://ziglang.org/download/0.8.0/zig-linux-x86_64-0.8.0.tar.xz /
ADD https://ziglang.org/download/0.7.1/zig-linux-x86_64-0.7.1.tar.xz /
RUN tar -xzf /aws-c-auth-clang.tgz && mkdir /src && tar -C /usr/local -xf zig-linux* && \
ln -s /usr/local/zig-linux*/zig /usr/local/bin/zig

View File

@ -30,13 +30,21 @@ pub fn build(b: *Builder) void {
exe.linkSystemLibrary("c");
exe.setTarget(target);
exe.setBuildMode(mode);
exe.override_dest_dir = .{ .Custom = ".." };
// This line works as of c5d412268
// Earliest nightly is 05b5e49bc on 2021-06-12
// https://ziglang.org/builds/zig-linux-x86_64-0.9.0-dev.113+05b5e49bc.tar.xz
// exe.override_dest_dir = .{ .Custom = ".." };
exe.override_dest_dir = .{ .custom = ".." };
exe.linkage = .static;
// TODO: Figure out -static
// Neither of these two work
// exe.addCompileFlags([][]const u8{
// "-static",
// "--strip",
// });
//
// To compile on stock 0.8.0, comment this line of code, or use the Makefile
// See https://github.com/ziglang/zig/pull/8248
//
// On a musl-based x86_64 system, this pre-compiled zig can be used:
// https://github.com/elerch/zig/releases/download/0.8.0/zig-0.8.0-static-support-musl-libz.tgz
exe.is_static = true;
exe.strip = true;
exe.install();

View File

@ -2,8 +2,6 @@ const std = @import("std");
const awshttp = @import("awshttp.zig");
const json = @import("json.zig");
const url = @import("url.zig");
const case = @import("case.zig");
const servicemodel = @import("servicemodel.zig");
const log = std.log.scoped(.aws);
@ -46,52 +44,16 @@ pub const Aws = struct {
const service = meta_info.service;
const action = meta_info.action;
const R = Response(request);
log.debug("call: prefix {s}, sigv4 {s}, version {s}, action {s}", .{
service.endpoint_prefix,
service.sigv4_name,
service.version,
action.action_name,
});
log.debug("proto: {s}", .{service.aws_protocol});
switch (service.aws_protocol) {
.query => return self.callQuery(request, service, action, options),
.ec2_query => @compileError("EC2 Query protocol not yet supported"),
.rest_json_1 => @compileError("REST Json 1 protocol not yet supported"),
.json_1_0 => @compileError("Json 1.0 protocol not yet supported"),
.json_1_1 => @compileError("Json 1.1 protocol not yet supported"),
.rest_xml => @compileError("REST XML protocol not yet supported"),
}
}
// Call using query protocol. This is documented as an XML protocol, but
// throwing a JSON accept header seems to work
fn callQuery(self: Self, comptime request: anytype, service: anytype, action: anytype, options: Options) !FullResponse(request) {
var buffer = std.ArrayList(u8).init(self.allocator);
defer buffer.deinit();
const writer = buffer.writer();
const transformer = struct {
allocator: *std.mem.Allocator,
const This = @This();
pub fn transform(this: This, name: []const u8) ![]const u8 {
return try case.snakeToPascal(this.allocator, name);
}
pub fn transform_deinit(this: This, name: []const u8) void {
this.allocator.free(name);
}
}{ .allocator = self.allocator };
try url.encode(request, writer, .{ .field_name_transformer = transformer });
const continuation = if (buffer.items.len > 0) "&" else "";
const body = try std.fmt.allocPrint(self.allocator, "Action={s}&Version={s}{s}{s}\n", .{ action.action_name, service.version, continuation, buffer.items });
defer self.allocator.free(body);
const FullR = FullResponse(request);
log.debug("service endpoint {s}", .{service.endpoint_prefix});
log.debug("service sigv4 name {s}", .{service.sigv4_name});
log.debug("version {s}", .{service.version});
log.debug("action {s}", .{action.action_name});
const response = try self.aws_http.callApi(
service.endpoint_prefix,
body,
service.version,
action.action_name,
.{
.region = options.region,
.dualstack = options.dualstack,
@ -99,11 +61,6 @@ pub const Aws = struct {
},
);
defer response.deinit();
if (response.response_code != 200) {
log.err("call failed! return status: {d}", .{response.response_code});
log.err("Request:\n |{s}\nResponse:\n |{s}", .{ body, response.body });
return error.HttpFailure;
}
// TODO: Check status code for badness
var stream = json.TokenStream.init(response.body);

View File

@ -232,11 +232,12 @@ pub const AwsHttp = struct {
/// It will calculate the appropriate endpoint and action parameters for the
/// service called, and will set up the signing options. The return
/// value is simply a raw HttpResult
pub fn callApi(self: Self, service: []const u8, body: []const u8, options: Options) !HttpResult {
pub fn callApi(self: Self, service: []const u8, version: []const u8, action: []const u8, options: Options) !HttpResult {
const endpoint = try regionSubDomain(self.allocator, service, options.region, options.dualstack);
defer endpoint.deinit();
httplog.debug("Calling endpoint {s}", .{endpoint.uri});
httplog.debug("Body\n====\n{s}\n====", .{body});
const body = try std.fmt.allocPrint(self.allocator, "Action={s}&Version={s}\n", .{ action, version });
defer self.allocator.free(body);
httplog.debug("Calling {s}.{s}, endpoint {s}", .{ service, action, endpoint.uri });
const signing_options: SigningOptions = .{
.region = options.region,
.service = if (options.sigv4_service_name) |name| name else service,

View File

@ -1,40 +0,0 @@
const std = @import("std");
const expectEqualStrings = std.testing.expectEqualStrings;
pub fn snakeToCamel(allocator: *std.mem.Allocator, name: []const u8) ![]u8 {
var utf8_name = (std.unicode.Utf8View.init(name) catch unreachable).iterator();
var target_inx: u64 = 0;
var previous_ascii: u8 = 0;
const rc = try allocator.alloc(u8, name.len); // This is slightly overkill, will need <= number of input chars
while (utf8_name.nextCodepoint()) |cp| {
if (cp > 0xff) return error.UnicodeNotSupported;
const ascii_char = @truncate(u8, cp);
if (ascii_char != '_') {
if (previous_ascii == '_' and ascii_char >= 'a' and ascii_char <= 'z') {
const uppercase_char = ascii_char - ('a' - 'A');
rc[target_inx] = uppercase_char;
} else {
rc[target_inx] = ascii_char;
}
target_inx = target_inx + 1;
}
previous_ascii = ascii_char;
}
rc[target_inx] = 0; // add zero sentinel
return rc[0..target_inx];
}
pub fn snakeToPascal(allocator: *std.mem.Allocator, name: []const u8) ![]u8 {
const rc = try snakeToCamel(allocator, name);
if (rc[0] >= 'a' and rc[0] <= 'z') {
const uppercase_char = rc[0] - ('a' - 'A');
rc[0] = uppercase_char;
}
return rc;
}
test "converts from snake to camelCase" {
const allocator = std.testing.allocator;
const camel = try snakeToCamel(allocator, "access_key_id");
defer allocator.free(camel);
try expectEqualStrings("accessKeyId", camel);
}

View File

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