Compare commits
No commits in common. "1b831cd91df2d280cee89929880c33a6e624b460" and "a278f6fd73a0712c4488777226c238de6aa2fa73" have entirely different histories.
1b831cd91d
...
a278f6fd73
204
src/aws.zig
204
src/aws.zig
|
@ -1,5 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const json = @import("json.zig");
|
const xml = @import("xml.zig");
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
@cInclude("bitfield-workaround.h");
|
@cInclude("bitfield-workaround.h");
|
||||||
@cInclude("aws/common/allocator.h");
|
@cInclude("aws/common/allocator.h");
|
||||||
|
@ -67,6 +67,17 @@ fn ServiceActionResponse(comptime service: []const u8, comptime action: []const
|
||||||
arn: []const u8,
|
arn: []const u8,
|
||||||
user_id: []const u8,
|
user_id: []const u8,
|
||||||
account: []const u8,
|
account: []const u8,
|
||||||
|
response_metadata: ResponseMetadata,
|
||||||
|
|
||||||
|
allocator: *std.mem.Allocator,
|
||||||
|
raw_response: xml.Document,
|
||||||
|
|
||||||
|
// If this is purely generic we won't be able to do it here as
|
||||||
|
// declarations aren't supported yet
|
||||||
|
// pub fn deinit(self: *const GetCallerIdentityResponse) void {
|
||||||
|
// self.responseMetadata.deinit();
|
||||||
|
// self.rawResponse.deinit();
|
||||||
|
// }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
unreachable;
|
unreachable;
|
||||||
|
@ -154,6 +165,13 @@ pub const Aws = struct {
|
||||||
var tls_ctx_options: ?*c.aws_tls_ctx_options = null;
|
var tls_ctx_options: ?*c.aws_tls_ctx_options = null;
|
||||||
var tls_ctx: ?*c.aws_tls_ctx = null;
|
var tls_ctx: ?*c.aws_tls_ctx = null;
|
||||||
|
|
||||||
|
pub fn responseDeinit(raw_response: xml.Document, response_metadata: ?ResponseMetadata) void {
|
||||||
|
raw_response.deinit();
|
||||||
|
if (response_metadata) |meta| {
|
||||||
|
meta.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn AsyncResult(comptime T: type) type {
|
fn AsyncResult(comptime T: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
result: *T,
|
result: *T,
|
||||||
|
@ -228,14 +246,13 @@ pub const Aws = struct {
|
||||||
log.debug("Deinit complete", .{});
|
log.debug("Deinit complete", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn call(self: Self, comptime request: anytype, options: Options) !FullResponse(request) {
|
pub fn call(self: Self, comptime request: anytype, options: Options) !Response(request) {
|
||||||
const action_info = actionForRequest(request);
|
const action_info = actionForRequest(request);
|
||||||
// This is true weirdness, but we are running into compiler bugs. Touch only if
|
// This is true weirdness, but we are running into compiler bugs. Touch only if
|
||||||
// prepared...
|
// prepared...
|
||||||
const service = @field(services, action_info.service);
|
const service = @field(services, action_info.service);
|
||||||
const action = @field(service, action_info.action);
|
const action = @field(service, action_info.action);
|
||||||
const R = Response(request);
|
const R = Response(request);
|
||||||
const FullR = FullResponse(request);
|
|
||||||
|
|
||||||
log.debug("service {s}", .{action_info.service});
|
log.debug("service {s}", .{action_info.service});
|
||||||
log.debug("version {s}", .{service.version});
|
log.debug("version {s}", .{service.version});
|
||||||
|
@ -243,37 +260,52 @@ pub const Aws = struct {
|
||||||
const response = try self.callApi(action_info.service, service.version, action.action_name, options);
|
const response = try self.callApi(action_info.service, service.version, action.action_name, options);
|
||||||
defer response.deinit();
|
defer response.deinit();
|
||||||
// TODO: Check status code for badness
|
// TODO: Check status code for badness
|
||||||
var stream = json.TokenStream.init(response.body);
|
const doc = try xml.parse(self.allocator, response.body);
|
||||||
|
const result = doc.root.findChildByTag("GetCallerIdentityResult");
|
||||||
const parser_options = json.ParseOptions{
|
return R{
|
||||||
|
.arn = result.?.getCharData("Arn").?,
|
||||||
|
.user_id = result.?.getCharData("UserId").?,
|
||||||
|
.account = result.?.getCharData("Account").?,
|
||||||
.allocator = self.allocator,
|
.allocator = self.allocator,
|
||||||
.allow_camel_case_conversion = true, // new option
|
.raw_response = doc,
|
||||||
.allow_snake_case_conversion = true, // new option
|
.response_metadata = try metadataFromResponse(self.allocator, response.body),
|
||||||
.allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
|
|
||||||
};
|
};
|
||||||
const SResponse = ServerResponse(request);
|
}
|
||||||
const parsed_response = try json.parse(SResponse, &stream, parser_options);
|
fn actionForRequest(comptime request: anytype) struct { service: []const u8, action: []const u8, service_obj: anytype } {
|
||||||
|
const type_name = @typeName(@TypeOf(request));
|
||||||
// Grab the first (and only) object from the server. Server shape expected to be:
|
var service_start: usize = 0;
|
||||||
// { ActionResponse: {ActionResult: {...}, ResponseMetadata: {...} } }
|
var service_end: usize = 0;
|
||||||
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
var action_start: usize = 0;
|
||||||
// Next line of code pulls this portion
|
var action_end: usize = 0;
|
||||||
//
|
for (type_name) |ch, i| {
|
||||||
//
|
switch (ch) {
|
||||||
// And the response property below will pull whatever is the ActionResult object
|
'(' => service_start = i + 2,
|
||||||
// We can grab index [0] as structs are guaranteed by zig to be returned in the order
|
')' => action_end = i - 1,
|
||||||
// declared, and we're declaring in that order in ServerResponse().
|
',' => {
|
||||||
const real_response = @field(parsed_response, @typeInfo(SResponse).Struct.fields[0].name);
|
service_end = i - 1;
|
||||||
return FullR{
|
action_start = i + 2;
|
||||||
.response = @field(real_response, @typeInfo(@TypeOf(real_response)).Struct.fields[0].name),
|
},
|
||||||
.response_metadata = .{
|
else => continue,
|
||||||
.request_id = real_response.ResponseMetadata.RequestId,
|
}
|
||||||
},
|
}
|
||||||
.parser_options = parser_options,
|
// const zero: usize = 0;
|
||||||
// .ParsedType = ServerResponse,
|
// TODO: Figure out why if statement isn't working
|
||||||
.raw_parsed = parsed_response,
|
// if (serviceStart == zero or serviceEnd == zero or actionStart == zero or actionEnd == zero) {
|
||||||
|
// @compileLog("Type must be a function with two parameters \"service\" and \"action\". Found: " ++ type_name);
|
||||||
|
// // @compileError("Type must be a function with two parameters \"service\" and \"action\". Found: " ++ type_name);
|
||||||
|
// }
|
||||||
|
return .{
|
||||||
|
.service = type_name[service_start..service_end],
|
||||||
|
.action = type_name[action_start..action_end],
|
||||||
|
.service_obj = @field(services, type_name[service_start..service_end]),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
fn Response(comptime request: anytype) type {
|
||||||
|
const action_info = actionForRequest(request);
|
||||||
|
const service = @field(services, action_info.service);
|
||||||
|
const action = @field(service, action_info.action);
|
||||||
|
return action.Response;
|
||||||
|
}
|
||||||
fn callApi(self: Self, service: []const u8, version: []const u8, action: []const u8, options: Options) !HttpResult {
|
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();
|
||||||
|
@ -586,7 +618,7 @@ pub const Aws = struct {
|
||||||
fn addHeaders(self: Self, request: *c.aws_http_message, host: []const u8, body: []const u8) !void {
|
fn addHeaders(self: Self, request: *c.aws_http_message, host: []const u8, body: []const u8) !void {
|
||||||
const accept_header = c.aws_http_header{
|
const accept_header = c.aws_http_header{
|
||||||
.name = c.aws_byte_cursor_from_c_str("Accept"),
|
.name = c.aws_byte_cursor_from_c_str("Accept"),
|
||||||
.value = c.aws_byte_cursor_from_c_str("application/json"),
|
.value = c.aws_byte_cursor_from_c_str("*/*"),
|
||||||
.compression = .AWS_HTTP_HEADER_COMPRESSION_USE_CACHE,
|
.compression = .AWS_HTTP_HEADER_COMPRESSION_USE_CACHE,
|
||||||
};
|
};
|
||||||
if (c.aws_http_message_add_header(request, accept_header) != c.AWS_OP_SUCCESS)
|
if (c.aws_http_message_add_header(request, accept_header) != c.AWS_OP_SUCCESS)
|
||||||
|
@ -930,6 +962,16 @@ fn cDeinit() void { // probably the wrong name
|
||||||
log.debug("auth clean up complete", .{});
|
log.debug("auth clean up complete", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const ResponseMetadata = struct {
|
||||||
|
request_id: ?[]const u8,
|
||||||
|
allocator: *std.mem.Allocator,
|
||||||
|
pub fn deinit(self: *const ResponseMetadata) void {
|
||||||
|
if (self.request_id) |id| {
|
||||||
|
self.allocator.free(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
region: []const u8 = "aws-global",
|
region: []const u8 = "aws-global",
|
||||||
dualstack: bool = false,
|
dualstack: bool = false,
|
||||||
|
@ -1101,103 +1143,3 @@ const RequestContext = struct {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fn actionForRequest(comptime request: anytype) struct { service: []const u8, action: []const u8, service_obj: anytype } {
|
|
||||||
const type_name = @typeName(@TypeOf(request));
|
|
||||||
var service_start: usize = 0;
|
|
||||||
var service_end: usize = 0;
|
|
||||||
var action_start: usize = 0;
|
|
||||||
var action_end: usize = 0;
|
|
||||||
for (type_name) |ch, i| {
|
|
||||||
switch (ch) {
|
|
||||||
'(' => service_start = i + 2,
|
|
||||||
')' => action_end = i - 1,
|
|
||||||
',' => {
|
|
||||||
service_end = i - 1;
|
|
||||||
action_start = i + 2;
|
|
||||||
},
|
|
||||||
else => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// const zero: usize = 0;
|
|
||||||
// TODO: Figure out why if statement isn't working
|
|
||||||
// if (serviceStart == zero or serviceEnd == zero or actionStart == zero or actionEnd == zero) {
|
|
||||||
// @compileLog("Type must be a function with two parameters \"service\" and \"action\". Found: " ++ type_name);
|
|
||||||
// // @compileError("Type must be a function with two parameters \"service\" and \"action\". Found: " ++ type_name);
|
|
||||||
// }
|
|
||||||
return .{
|
|
||||||
.service = type_name[service_start..service_end],
|
|
||||||
.action = type_name[action_start..action_end],
|
|
||||||
.service_obj = @field(services, type_name[service_start..service_end]),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn ServerResponse(comptime request: anytype) type {
|
|
||||||
const T = Response(request);
|
|
||||||
const action_info = actionForRequest(request);
|
|
||||||
const service = @field(services, action_info.service);
|
|
||||||
const action = @field(service, action_info.action);
|
|
||||||
// NOTE: This is weird capitalization as a performance enhancement and to reduce
|
|
||||||
// allocations in json.zig
|
|
||||||
const ResponseMetadata = struct {
|
|
||||||
RequestId: []u8,
|
|
||||||
};
|
|
||||||
const Result = @Type(.{
|
|
||||||
.Struct = .{
|
|
||||||
.layout = .Auto,
|
|
||||||
.fields = &[_]std.builtin.TypeInfo.StructField{
|
|
||||||
.{
|
|
||||||
.name = action.action_name ++ "Result",
|
|
||||||
.field_type = T,
|
|
||||||
.default_value = null,
|
|
||||||
.is_comptime = false,
|
|
||||||
.alignment = 0,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
.name = "ResponseMetadata",
|
|
||||||
.field_type = ResponseMetadata,
|
|
||||||
.default_value = null,
|
|
||||||
.is_comptime = false,
|
|
||||||
.alignment = 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.decls = &[_]std.builtin.TypeInfo.Declaration{},
|
|
||||||
.is_tuple = false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return @Type(.{
|
|
||||||
.Struct = .{
|
|
||||||
.layout = .Auto,
|
|
||||||
.fields = &[_]std.builtin.TypeInfo.StructField{
|
|
||||||
.{
|
|
||||||
.name = action.action_name ++ "Response",
|
|
||||||
.field_type = Result,
|
|
||||||
.default_value = null,
|
|
||||||
.is_comptime = false,
|
|
||||||
.alignment = 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
.decls = &[_]std.builtin.TypeInfo.Declaration{},
|
|
||||||
.is_tuple = false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn FullResponse(comptime request: anytype) type {
|
|
||||||
return struct {
|
|
||||||
response: Response(request),
|
|
||||||
response_metadata: struct {
|
|
||||||
request_id: []u8,
|
|
||||||
},
|
|
||||||
parser_options: json.ParseOptions,
|
|
||||||
raw_parsed: ServerResponse(request),
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
pub fn deinit(self: Self) void {
|
|
||||||
json.parseFree(ServerResponse(request), self.raw_parsed, self.parser_options);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
fn Response(comptime request: anytype) type {
|
|
||||||
const action_info = actionForRequest(request);
|
|
||||||
const service = @field(services, action_info.service);
|
|
||||||
const action = @field(service, action_info.action);
|
|
||||||
return action.Response;
|
|
||||||
}
|
|
||||||
|
|
3038
src/json.zig
3038
src/json.zig
File diff suppressed because it is too large
Load Diff
43
src/main.zig
43
src/main.zig
|
@ -1,6 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const aws = @import("aws.zig");
|
const aws = @import("aws.zig");
|
||||||
const json = @import("json.zig");
|
|
||||||
|
|
||||||
pub fn log(
|
pub fn log(
|
||||||
comptime level: std.log.Level,
|
comptime level: std.log.Level,
|
||||||
|
@ -11,6 +10,7 @@ pub fn log(
|
||||||
// Ignore awshttp messages
|
// Ignore awshttp messages
|
||||||
if (scope == .awshttp and @enumToInt(level) >= @enumToInt(std.log.Level.debug))
|
if (scope == .awshttp and @enumToInt(level) >= @enumToInt(std.log.Level.debug))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const scope_prefix = "(" ++ @tagName(scope) ++ "): ";
|
const scope_prefix = "(" ++ @tagName(scope) ++ "): ";
|
||||||
const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
|
const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
|
||||||
|
|
||||||
|
@ -27,15 +27,6 @@ pub fn main() anyerror!void {
|
||||||
// defer file.close();
|
// defer file.close();
|
||||||
// var child_allocator = std.heap.c_allocator;
|
// var child_allocator = std.heap.c_allocator;
|
||||||
// const allocator = &std.heap.loggingAllocator(child_allocator, file.writer()).allocator;
|
// const allocator = &std.heap.loggingAllocator(child_allocator, file.writer()).allocator;
|
||||||
|
|
||||||
// Flip to true to run a second time. This will help debug
|
|
||||||
// allocation/deallocation issues
|
|
||||||
const test_twice = false;
|
|
||||||
|
|
||||||
// Flip to true to run through the json parsing changes made to stdlib
|
|
||||||
const test_json = false;
|
|
||||||
if (test_json) try jsonFun();
|
|
||||||
|
|
||||||
const allocator = std.heap.c_allocator;
|
const allocator = std.heap.c_allocator;
|
||||||
|
|
||||||
const options = aws.Options{
|
const options = aws.Options{
|
||||||
|
@ -48,8 +39,11 @@ pub fn main() anyerror!void {
|
||||||
const resp = try client.call(aws.services.sts.get_caller_identity.Request{}, options);
|
const resp = try client.call(aws.services.sts.get_caller_identity.Request{}, options);
|
||||||
// TODO: This is a bit wonky. Root cause is lack of declarations in
|
// TODO: This is a bit wonky. Root cause is lack of declarations in
|
||||||
// comptime-generated types
|
// comptime-generated types
|
||||||
defer resp.deinit();
|
defer aws.Aws.responseDeinit(resp.raw_response, resp.response_metadata);
|
||||||
|
|
||||||
|
// Flip to true to run a second time. This will help debug
|
||||||
|
// allocation/deallocation issues
|
||||||
|
const test_twice = false;
|
||||||
if (test_twice) {
|
if (test_twice) {
|
||||||
std.time.sleep(1000 * std.time.ns_per_ms);
|
std.time.sleep(1000 * std.time.ns_per_ms);
|
||||||
std.log.info("second request", .{});
|
std.log.info("second request", .{});
|
||||||
|
@ -57,32 +51,13 @@ pub fn main() anyerror!void {
|
||||||
var client2 = aws.Aws.init(allocator);
|
var client2 = aws.Aws.init(allocator);
|
||||||
defer client2.deinit();
|
defer client2.deinit();
|
||||||
const resp2 = try client2.call(aws.services.sts.get_caller_identity.Request{}, options); // catch here and try alloc?
|
const resp2 = try client2.call(aws.services.sts.get_caller_identity.Request{}, options); // catch here and try alloc?
|
||||||
defer resp2.deinit();
|
defer aws.Aws.responseDeinit(resp2.raw_response, resp2.response_metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
std.log.info("arn: {s}", .{resp.response.arn});
|
std.log.info("arn: {s}", .{resp.arn});
|
||||||
std.log.info("id: {s}", .{resp.response.user_id});
|
std.log.info("id: {s}", .{resp.user_id});
|
||||||
std.log.info("account: {s}", .{resp.response.account});
|
std.log.info("account: {s}", .{resp.account});
|
||||||
std.log.info("requestId: {s}", .{resp.response_metadata.request_id});
|
std.log.info("requestId: {s}", .{resp.response_metadata.request_id});
|
||||||
|
|
||||||
std.log.info("Departing main", .{});
|
std.log.info("Departing main", .{});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jsonFun() !void {
|
|
||||||
// Standard behavior
|
|
||||||
const payload =
|
|
||||||
\\{"GetCallerIdentityResponse":{"GetCallerIdentityResult":{"Account":"0123456789","Arn":"arn:aws:iam::0123456789:user/test","UserId":"MYUSERID"},"ResponseMetadata":{"RequestId":"3b80a99b-7df8-4bcb-96ee-b2759878a5f2"}}}
|
|
||||||
;
|
|
||||||
const Ret3 = struct {
|
|
||||||
getCallerIdentityResponse: struct { getCallerIdentityResult: struct { account: []u8, arn: []u8, user_id: []u8 }, responseMetadata: struct { requestId: []u8 } },
|
|
||||||
};
|
|
||||||
var stream3 = json.TokenStream.init(payload);
|
|
||||||
const res3 = json.parse(Ret3, &stream3, .{
|
|
||||||
.allocator = std.heap.c_allocator,
|
|
||||||
.allow_camel_case_conversion = true, // new option
|
|
||||||
.allow_snake_case_conversion = true, // new option
|
|
||||||
.allow_unknown_fields = true, // new option
|
|
||||||
}) catch unreachable;
|
|
||||||
std.log.info("{}", .{res3});
|
|
||||||
std.log.info("{s}", .{res3.getCallerIdentityResponse.getCallerIdentityResult.user_id});
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user