provide smithy-based json serialization for request data
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This will use the actual structure name or the override from the trait as needed. Return value support is also enabled in the code generation but not in aws.zig. The current fuzzy checks should get most of the way there though
This commit is contained in:
parent
5c29120a44
commit
17b0ae9551
|
@ -329,13 +329,35 @@ fn generateSimpleTypeFor(_: anytype, type_name: []const u8, writer: anytype, all
|
|||
}
|
||||
|
||||
fn generateComplexTypeFor(allocator: *std.mem.Allocator, members: []smithy.TypeMember, type_type_name: []const u8, shapes: anytype, writer: anytype, prefix: []const u8, all_required: bool, type_stack: anytype) anyerror!void {
|
||||
const Mapping = struct { snake: []const u8, json: []const u8 };
|
||||
var mappings = try std.ArrayList(Mapping).initCapacity(allocator, members.len);
|
||||
defer {
|
||||
for (mappings.items) |mapping| {
|
||||
allocator.free(mapping.snake);
|
||||
}
|
||||
mappings.deinit();
|
||||
}
|
||||
// prolog. We'll rely on caller to get the spacing correct here
|
||||
_ = try writer.write(type_type_name);
|
||||
_ = try writer.write(" {\n");
|
||||
for (members) |member| {
|
||||
const new_prefix = try std.fmt.allocPrint(allocator, " {s}", .{prefix});
|
||||
defer allocator.free(new_prefix);
|
||||
// This is our mapping
|
||||
const snake_case_member = try snake.fromPascalCase(allocator, member.name);
|
||||
// So it looks like some services have duplicate names?! Check out "httpMethod"
|
||||
// in API Gateway. Not sure what we're supposed to do there. Checking the go
|
||||
// sdk, they move this particular duplicate to 'http_method' - not sure yet
|
||||
// if this is a hard-coded exception`
|
||||
var found_trait = false;
|
||||
for (member.traits) |trait| {
|
||||
if (trait == .json_name) {
|
||||
found_trait = true;
|
||||
mappings.appendAssumeCapacity(.{ .snake = try allocator.dupe(u8, snake_case_member), .json = trait.json_name });
|
||||
}
|
||||
}
|
||||
if (!found_trait)
|
||||
mappings.appendAssumeCapacity(.{ .snake = try allocator.dupe(u8, snake_case_member), .json = member.name });
|
||||
defer allocator.free(snake_case_member);
|
||||
try writer.print("{s} {s}: ", .{ prefix, avoidReserved(snake_case_member) });
|
||||
if (!all_required) try writeOptional(member.traits, writer, null);
|
||||
|
@ -344,6 +366,30 @@ fn generateComplexTypeFor(allocator: *std.mem.Allocator, members: []smithy.TypeM
|
|||
try writeOptional(member.traits, writer, " = null");
|
||||
_ = try writer.write(",\n");
|
||||
}
|
||||
|
||||
// Add in json mappings. The function looks like this:
|
||||
//
|
||||
// pub fn jsonFieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 {
|
||||
// const mappings = .{
|
||||
// .exclusive_start_table_name = "ExclusiveStartTableName",
|
||||
// .limit = "Limit",
|
||||
// };
|
||||
// return @field(mappings, field_name);
|
||||
// }
|
||||
//
|
||||
// TODO: There is a smithy trait that will specify the json name. We should be using
|
||||
// this instead if applicable.
|
||||
try writer.print("\n{s} pub fn jsonFieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 ", .{prefix});
|
||||
_ = try writer.write("{\n");
|
||||
try writer.print("{s} const mappings = .", .{prefix});
|
||||
_ = try writer.write("{\n");
|
||||
for (mappings.items) |mapping| {
|
||||
try writer.print("{s} .{s} = \"{s}\",\n", .{ prefix, avoidReserved(mapping.snake), mapping.json });
|
||||
}
|
||||
_ = try writer.write(prefix);
|
||||
_ = try writer.write(" };\n");
|
||||
try writer.print("{s} return @field(mappings, field_name);\n{s}", .{ prefix, prefix });
|
||||
_ = try writer.write(" }\n");
|
||||
}
|
||||
|
||||
fn writeOptional(traits: ?[]smithy.Trait, writer: anytype, value: ?[]const u8) !void {
|
||||
|
|
31
src/aws.zig
31
src/aws.zig
|
@ -93,7 +93,7 @@ pub const Aws = struct {
|
|||
//
|
||||
var nameAllocator = std.heap.ArenaAllocator.init(self.allocator);
|
||||
defer nameAllocator.deinit();
|
||||
try json.stringify(request, .{ .whitespace = .{}, .allocator = &nameAllocator.allocator, .nameTransform = pascalTransformer }, buffer.writer());
|
||||
try json.stringify(request, .{ .whitespace = .{} }, buffer.writer());
|
||||
|
||||
var content_type: []const u8 = undefined;
|
||||
switch (service_meta.aws_protocol) {
|
||||
|
@ -377,8 +377,33 @@ fn queryFieldTransformer(field_name: []const u8, encoding_options: url.EncodingO
|
|||
return try case.snakeToPascal(encoding_options.allocator.?, field_name);
|
||||
}
|
||||
|
||||
fn pascalTransformer(field_name: []const u8, options: json.StringifyOptions) anyerror![]const u8 {
|
||||
return try case.snakeToPascal(options.allocator.?, field_name);
|
||||
test "basic json request serialization" {
|
||||
const allocator = std.testing.allocator;
|
||||
const svs = Services(.{.dynamo_db}){};
|
||||
const request = svs.dynamo_db.list_tables.Request{
|
||||
.limit = 1,
|
||||
};
|
||||
var buffer = std.ArrayList(u8).init(allocator);
|
||||
defer buffer.deinit();
|
||||
|
||||
// The transformer needs to allocate stuff out of band, but we
|
||||
// can guarantee we don't need the memory after this call completes,
|
||||
// so we'll use an arena allocator to whack everything.
|
||||
// TODO: Determine if sending in null values is ok, or if we need another
|
||||
// tweak to the stringify function to exclude. According to the
|
||||
// smithy spec, "A null value MAY be provided or omitted
|
||||
// for a boxed member with no observable difference." But we're
|
||||
// seeing a lot of differences here between spec and reality
|
||||
//
|
||||
var nameAllocator = std.heap.ArenaAllocator.init(allocator);
|
||||
defer nameAllocator.deinit();
|
||||
try json.stringify(request, .{ .whitespace = .{} }, buffer.writer());
|
||||
try std.testing.expectEqualStrings(
|
||||
\\{
|
||||
\\ "ExclusiveStartTableName": null,
|
||||
\\ "Limit": 1
|
||||
\\}
|
||||
, buffer.items);
|
||||
}
|
||||
// Use for debugging json responses of specific requests
|
||||
// test "dummy request" {
|
||||
|
|
20
src/json.zig
20
src/json.zig
|
@ -2656,15 +2656,6 @@ pub const StringifyOptions = struct {
|
|||
|
||||
string: StringOptions = StringOptions{ .String = .{} },
|
||||
|
||||
nameTransform: fn ([]const u8, StringifyOptions) anyerror![]const u8 = nullTransform,
|
||||
|
||||
/// Not used by stringify - might be needed for your name transformer
|
||||
allocator: ?*std.mem.Allocator = null,
|
||||
|
||||
fn nullTransform(name: []const u8, _: StringifyOptions) ![]const u8 {
|
||||
return name;
|
||||
}
|
||||
|
||||
/// Should []u8 be serialised as a string? or an array?
|
||||
pub const StringOptions = union(enum) {
|
||||
Array,
|
||||
|
@ -2777,10 +2768,13 @@ pub fn stringify(
|
|||
try out_stream.writeByte('\n');
|
||||
try child_whitespace.outputIndent(out_stream);
|
||||
}
|
||||
const name = child_options.nameTransform(Field.name, options) catch {
|
||||
return error.NameTransformationError;
|
||||
};
|
||||
try stringify(name, options, out_stream);
|
||||
if (comptime std.meta.trait.hasFn("jsonFieldNameFor")(T)) {
|
||||
const name = value.jsonFieldNameFor(Field.name);
|
||||
try stringify(name, options, out_stream);
|
||||
} else {
|
||||
try stringify(Field.name, options, out_stream);
|
||||
}
|
||||
|
||||
try out_stream.writeByte(':');
|
||||
if (child_options.whitespace) |child_whitespace| {
|
||||
if (child_whitespace.separator) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user