convert to json - core processing fully generic
This commit is contained in:
parent
da5e58872f
commit
1b831cd91d
204
src/aws.zig
204
src/aws.zig
|
@ -1,5 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const xml = @import("xml.zig");
|
const json = @import("json.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,17 +67,6 @@ 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;
|
||||||
|
@ -165,13 +154,6 @@ 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,
|
||||||
|
@ -246,13 +228,14 @@ pub const Aws = struct {
|
||||||
log.debug("Deinit complete", .{});
|
log.debug("Deinit complete", .{});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn call(self: Self, comptime request: anytype, options: Options) !Response(request) {
|
pub fn call(self: Self, comptime request: anytype, options: Options) !FullResponse(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});
|
||||||
|
@ -260,52 +243,37 @@ 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
|
||||||
const doc = try xml.parse(self.allocator, response.body);
|
var stream = json.TokenStream.init(response.body);
|
||||||
const result = doc.root.findChildByTag("GetCallerIdentityResult");
|
|
||||||
return R{
|
const parser_options = json.ParseOptions{
|
||||||
.arn = result.?.getCharData("Arn").?,
|
|
||||||
.user_id = result.?.getCharData("UserId").?,
|
|
||||||
.account = result.?.getCharData("Account").?,
|
|
||||||
.allocator = self.allocator,
|
.allocator = self.allocator,
|
||||||
.raw_response = doc,
|
.allow_camel_case_conversion = true, // new option
|
||||||
.response_metadata = try metadataFromResponse(self.allocator, response.body),
|
.allow_snake_case_conversion = true, // new option
|
||||||
|
.allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
|
||||||
};
|
};
|
||||||
}
|
const SResponse = ServerResponse(request);
|
||||||
fn actionForRequest(comptime request: anytype) struct { service: []const u8, action: []const u8, service_obj: anytype } {
|
const parsed_response = try json.parse(SResponse, &stream, parser_options);
|
||||||
const type_name = @typeName(@TypeOf(request));
|
|
||||||
var service_start: usize = 0;
|
// Grab the first (and only) object from the server. Server shape expected to be:
|
||||||
var service_end: usize = 0;
|
// { ActionResponse: {ActionResult: {...}, ResponseMetadata: {...} } }
|
||||||
var action_start: usize = 0;
|
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
var action_end: usize = 0;
|
// Next line of code pulls this portion
|
||||||
for (type_name) |ch, i| {
|
//
|
||||||
switch (ch) {
|
//
|
||||||
'(' => service_start = i + 2,
|
// And the response property below will pull whatever is the ActionResult object
|
||||||
')' => action_end = i - 1,
|
// We can grab index [0] as structs are guaranteed by zig to be returned in the order
|
||||||
',' => {
|
// declared, and we're declaring in that order in ServerResponse().
|
||||||
service_end = i - 1;
|
const real_response = @field(parsed_response, @typeInfo(SResponse).Struct.fields[0].name);
|
||||||
action_start = i + 2;
|
return FullR{
|
||||||
},
|
.response = @field(real_response, @typeInfo(@TypeOf(real_response)).Struct.fields[0].name),
|
||||||
else => continue,
|
.response_metadata = .{
|
||||||
}
|
.request_id = real_response.ResponseMetadata.RequestId,
|
||||||
}
|
},
|
||||||
// const zero: usize = 0;
|
.parser_options = parser_options,
|
||||||
// TODO: Figure out why if statement isn't working
|
// .ParsedType = ServerResponse,
|
||||||
// if (serviceStart == zero or serviceEnd == zero or actionStart == zero or actionEnd == zero) {
|
.raw_parsed = parsed_response,
|
||||||
// @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();
|
||||||
|
@ -618,7 +586,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("*/*"),
|
.value = c.aws_byte_cursor_from_c_str("application/json"),
|
||||||
.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)
|
||||||
|
@ -962,16 +930,6 @@ 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,
|
||||||
|
@ -1143,3 +1101,103 @@ 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;
|
||||||
|
}
|
||||||
|
|
43
src/main.zig
43
src/main.zig
|
@ -1,5 +1,6 @@
|
||||||
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,
|
||||||
|
@ -10,7 +11,6 @@ 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,6 +27,15 @@ 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{
|
||||||
|
@ -39,11 +48,8 @@ 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 aws.Aws.responseDeinit(resp.raw_response, resp.response_metadata);
|
defer resp.deinit();
|
||||||
|
|
||||||
// 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", .{});
|
||||||
|
@ -51,13 +57,32 @@ 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 aws.Aws.responseDeinit(resp2.raw_response, resp2.response_metadata);
|
defer resp2.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
std.log.info("arn: {s}", .{resp.arn});
|
std.log.info("arn: {s}", .{resp.response.arn});
|
||||||
std.log.info("id: {s}", .{resp.user_id});
|
std.log.info("id: {s}", .{resp.response.user_id});
|
||||||
std.log.info("account: {s}", .{resp.account});
|
std.log.info("account: {s}", .{resp.response.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