Compare commits
No commits in common. "bd3605e387acf18c2bb665cdc8cf2f5dd099dad8" and "ad5459683702f6fcd3bb3adc65483bad3e18464c" have entirely different histories.
bd3605e387
...
ad54596837
|
@ -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
|
||||
|
|
20
build.zig
20
build.zig
|
@ -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();
|
||||
|
|
57
src/aws.zig
57
src/aws.zig
|
@ -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);
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
40
src/case.zig
40
src/case.zig
|
@ -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);
|
||||
}
|
96
src/url.zig
96
src/url.zig
|
@ -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" } },
|
||||
.{},
|
||||
);
|
||||
}
|
Loading…
Reference in New Issue
Block a user