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
|
FROM alpine:3.13 as final
|
||||||
COPY --from=auth /aws-c-auth-clang.tgz /
|
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* && \
|
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
|
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.linkSystemLibrary("c");
|
||||||
exe.setTarget(target);
|
exe.setTarget(target);
|
||||||
exe.setBuildMode(mode);
|
exe.setBuildMode(mode);
|
||||||
|
exe.override_dest_dir = .{ .Custom = ".." };
|
||||||
|
|
||||||
// This line works as of c5d412268
|
// TODO: Figure out -static
|
||||||
// Earliest nightly is 05b5e49bc on 2021-06-12
|
// Neither of these two work
|
||||||
// https://ziglang.org/builds/zig-linux-x86_64-0.9.0-dev.113+05b5e49bc.tar.xz
|
// exe.addCompileFlags([][]const u8{
|
||||||
// exe.override_dest_dir = .{ .Custom = ".." };
|
// "-static",
|
||||||
exe.override_dest_dir = .{ .custom = ".." };
|
// "--strip",
|
||||||
exe.linkage = .static;
|
// });
|
||||||
|
//
|
||||||
|
// 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.strip = true;
|
||||||
exe.install();
|
exe.install();
|
||||||
|
|
57
src/aws.zig
57
src/aws.zig
|
@ -2,8 +2,6 @@ const std = @import("std");
|
||||||
|
|
||||||
const awshttp = @import("awshttp.zig");
|
const awshttp = @import("awshttp.zig");
|
||||||
const json = @import("json.zig");
|
const json = @import("json.zig");
|
||||||
const url = @import("url.zig");
|
|
||||||
const case = @import("case.zig");
|
|
||||||
const servicemodel = @import("servicemodel.zig");
|
const servicemodel = @import("servicemodel.zig");
|
||||||
|
|
||||||
const log = std.log.scoped(.aws);
|
const log = std.log.scoped(.aws);
|
||||||
|
@ -46,52 +44,16 @@ pub const Aws = struct {
|
||||||
const service = meta_info.service;
|
const service = meta_info.service;
|
||||||
const action = meta_info.action;
|
const action = meta_info.action;
|
||||||
const R = Response(request);
|
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);
|
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(
|
const response = try self.aws_http.callApi(
|
||||||
service.endpoint_prefix,
|
service.endpoint_prefix,
|
||||||
body,
|
service.version,
|
||||||
|
action.action_name,
|
||||||
.{
|
.{
|
||||||
.region = options.region,
|
.region = options.region,
|
||||||
.dualstack = options.dualstack,
|
.dualstack = options.dualstack,
|
||||||
|
@ -99,11 +61,6 @@ pub const Aws = struct {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
defer response.deinit();
|
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
|
// TODO: Check status code for badness
|
||||||
var stream = json.TokenStream.init(response.body);
|
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
|
/// It will calculate the appropriate endpoint and action parameters for the
|
||||||
/// service called, and will set up the signing options. The return
|
/// service called, and will set up the signing options. The return
|
||||||
/// value is simply a raw HttpResult
|
/// 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);
|
const endpoint = try regionSubDomain(self.allocator, service, options.region, options.dualstack);
|
||||||
defer endpoint.deinit();
|
defer endpoint.deinit();
|
||||||
httplog.debug("Calling endpoint {s}", .{endpoint.uri});
|
const body = try std.fmt.allocPrint(self.allocator, "Action={s}&Version={s}\n", .{ action, version });
|
||||||
httplog.debug("Body\n====\n{s}\n====", .{body});
|
defer self.allocator.free(body);
|
||||||
|
httplog.debug("Calling {s}.{s}, endpoint {s}", .{ service, action, endpoint.uri });
|
||||||
const signing_options: SigningOptions = .{
|
const signing_options: SigningOptions = .{
|
||||||
.region = options.region,
|
.region = options.region,
|
||||||
.service = if (options.sigv4_service_name) |name| name else service,
|
.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