Compare commits

...

8 Commits

Author SHA1 Message Date
1b831cd91d
convert to json - core processing fully generic 2021-05-13 15:53:53 -07:00
da5e58872f
additional option for snake case
This will usually be enabled in conjunction with camelCase, to the degree
that maybe this is a bit confused?
2021-05-13 10:56:14 -07:00
1d10ec3e17
only allocate if mutation is neded 2021-05-13 10:10:12 -07:00
dd5ad72b5a
change import to allow embedding json.zig in this project 2021-05-13 10:05:23 -07:00
e76eb45003
allow camel case conversion for json keys 2021-05-13 10:04:01 -07:00
94266582b7
allow unknown struct fields 2021-05-13 09:03:00 -07:00
c24ef3f3b8
allow unknown non-struct fields 2021-05-13 07:56:43 -07:00
0bafc185c8
add json.zig from std lib. Commit e6881d4373ce355f3f4565c22c125870ed8fcfc8 2021-05-12 17:20:36 -07:00
3 changed files with 3203 additions and 82 deletions

View File

@ -1,5 +1,5 @@
const std = @import("std");
const xml = @import("xml.zig");
const json = @import("json.zig");
const c = @cImport({
@cInclude("bitfield-workaround.h");
@cInclude("aws/common/allocator.h");
@ -67,17 +67,6 @@ fn ServiceActionResponse(comptime service: []const u8, comptime action: []const
arn: []const u8,
user_id: []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;
@ -165,13 +154,6 @@ pub const Aws = struct {
var tls_ctx_options: ?*c.aws_tls_ctx_options = 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 {
return struct {
result: *T,
@ -246,13 +228,14 @@ pub const Aws = struct {
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);
// This is true weirdness, but we are running into compiler bugs. Touch only if
// prepared...
const service = @field(services, action_info.service);
const action = @field(service, action_info.action);
const R = Response(request);
const FullR = FullResponse(request);
log.debug("service {s}", .{action_info.service});
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);
defer response.deinit();
// TODO: Check status code for badness
const doc = try xml.parse(self.allocator, response.body);
const result = doc.root.findChildByTag("GetCallerIdentityResult");
return R{
.arn = result.?.getCharData("Arn").?,
.user_id = result.?.getCharData("UserId").?,
.account = result.?.getCharData("Account").?,
var stream = json.TokenStream.init(response.body);
const parser_options = json.ParseOptions{
.allocator = self.allocator,
.raw_response = doc,
.response_metadata = try metadataFromResponse(self.allocator, response.body),
.allow_camel_case_conversion = true, // new option
.allow_snake_case_conversion = true, // new option
.allow_unknown_fields = true, // new option. Cannot yet handle non-struct fields though
};
}
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;
const SResponse = ServerResponse(request);
const parsed_response = try json.parse(SResponse, &stream, parser_options);
// Grab the first (and only) object from the server. Server shape expected to be:
// { ActionResponse: {ActionResult: {...}, ResponseMetadata: {...} } }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Next line of code pulls this portion
//
//
// And the response property below will pull whatever is the ActionResult object
// 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().
const real_response = @field(parsed_response, @typeInfo(SResponse).Struct.fields[0].name);
return FullR{
.response = @field(real_response, @typeInfo(@TypeOf(real_response)).Struct.fields[0].name),
.response_metadata = .{
.request_id = real_response.ResponseMetadata.RequestId,
},
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]),
.parser_options = parser_options,
// .ParsedType = ServerResponse,
.raw_parsed = parsed_response,
};
}
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 {
const endpoint = try regionSubDomain(self.allocator, service, options.region, options.dualstack);
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 {
const accept_header = c.aws_http_header{
.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,
};
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", .{});
}
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 {
region: []const u8 = "aws-global",
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;
}

3038
src/json.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,6 @@
const std = @import("std");
const aws = @import("aws.zig");
const json = @import("json.zig");
pub fn log(
comptime level: std.log.Level,
@ -10,7 +11,6 @@ pub fn log(
// Ignore awshttp messages
if (scope == .awshttp and @enumToInt(level) >= @enumToInt(std.log.Level.debug))
return;
const scope_prefix = "(" ++ @tagName(scope) ++ "): ";
const prefix = "[" ++ @tagName(level) ++ "] " ++ scope_prefix;
@ -27,6 +27,15 @@ pub fn main() anyerror!void {
// defer file.close();
// var child_allocator = std.heap.c_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 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);
// TODO: This is a bit wonky. Root cause is lack of declarations in
// 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) {
std.time.sleep(1000 * std.time.ns_per_ms);
std.log.info("second request", .{});
@ -51,13 +57,32 @@ pub fn main() anyerror!void {
var client2 = aws.Aws.init(allocator);
defer client2.deinit();
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("id: {s}", .{resp.user_id});
std.log.info("account: {s}", .{resp.account});
std.log.info("arn: {s}", .{resp.response.arn});
std.log.info("id: {s}", .{resp.response.user_id});
std.log.info("account: {s}", .{resp.response.account});
std.log.info("requestId: {s}", .{resp.response_metadata.request_id});
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});
}