2021-05-30 01:17:45 +00:00
|
|
|
const std = @import("std");
|
2021-06-30 16:05:21 +00:00
|
|
|
const smithy = @import("smithy");
|
2021-05-30 01:17:45 +00:00
|
|
|
const snake = @import("snake.zig");
|
2021-09-05 20:10:48 +00:00
|
|
|
const json_zig = @embedFile("json.zig");
|
2021-05-30 01:17:45 +00:00
|
|
|
|
|
|
|
pub fn main() anyerror!void {
|
|
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
|
|
defer arena.deinit();
|
|
|
|
const allocator = &arena.allocator;
|
|
|
|
|
|
|
|
const args = try std.process.argsAlloc(allocator);
|
|
|
|
defer std.process.argsFree(allocator, args);
|
|
|
|
const stdout = std.io.getStdOut().writer();
|
2021-09-05 20:10:48 +00:00
|
|
|
const json_file = try std.fs.cwd().createFile("json.zig", .{});
|
|
|
|
defer json_file.close();
|
|
|
|
try json_file.writer().writeAll(json_zig);
|
2021-06-04 00:40:47 +00:00
|
|
|
const manifest_file = try std.fs.cwd().createFile("service_manifest.zig", .{});
|
|
|
|
defer manifest_file.close();
|
|
|
|
const manifest = manifest_file.writer();
|
2021-05-30 01:17:45 +00:00
|
|
|
var inx: u32 = 0;
|
|
|
|
for (args) |arg| {
|
|
|
|
if (inx == 0) {
|
|
|
|
inx = inx + 1;
|
|
|
|
continue;
|
|
|
|
}
|
2021-06-04 00:40:47 +00:00
|
|
|
try processFile(arg, stdout, manifest);
|
2021-05-30 01:17:45 +00:00
|
|
|
inx = inx + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (args.len == 0)
|
2021-06-04 00:40:47 +00:00
|
|
|
_ = try generateServices(allocator, ";", std.io.getStdIn(), stdout);
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
|
|
|
|
2021-06-04 00:40:47 +00:00
|
|
|
fn processFile(arg: []const u8, stdout: anytype, manifest: anytype) !void {
|
2021-05-30 01:17:45 +00:00
|
|
|
// It's probably best to create our own allocator here so we can deint at the end and
|
|
|
|
// toss all allocations related to the services in this file
|
|
|
|
// I can't guarantee we're not leaking something, and at the end of the
|
|
|
|
// day I'm not sure we want to track down leaks
|
|
|
|
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
|
|
|
|
defer arena.deinit();
|
|
|
|
const allocator = &arena.allocator;
|
2021-06-04 00:40:47 +00:00
|
|
|
var writer = &stdout;
|
|
|
|
var file: std.fs.File = undefined;
|
|
|
|
const filename = try std.fmt.allocPrint(allocator, "{s}.zig", .{arg});
|
|
|
|
defer allocator.free(filename);
|
|
|
|
file = try std.fs.cwd().createFile(filename, .{ .truncate = true });
|
|
|
|
errdefer file.close();
|
|
|
|
writer = &file.writer();
|
2021-09-05 20:10:48 +00:00
|
|
|
_ = try writer.write("const std = @import(\"std\");\n");
|
|
|
|
_ = try writer.write("const serializeMap = @import(\"json.zig\").serializeMap;\n");
|
2021-06-30 16:07:35 +00:00
|
|
|
_ = try writer.write("const smithy = @import(\"smithy\");\n\n");
|
2021-06-04 00:40:47 +00:00
|
|
|
std.log.info("Processing file: {s}", .{arg});
|
|
|
|
const service_names = generateServicesForFilePath(allocator, ";", arg, writer) catch |err| {
|
|
|
|
std.log.crit("Error processing file: {s}", .{arg});
|
|
|
|
return err;
|
|
|
|
};
|
|
|
|
defer {
|
|
|
|
for (service_names) |name| allocator.free(name);
|
|
|
|
allocator.free(service_names);
|
|
|
|
}
|
|
|
|
file.close();
|
|
|
|
for (service_names) |name| {
|
2021-06-30 16:11:53 +00:00
|
|
|
try manifest.print("pub const {s} = @import(\"{s}\");\n", .{ name, std.fs.path.basename(filename) });
|
2021-06-04 00:40:47 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-30 01:17:45 +00:00
|
|
|
|
2021-06-04 00:40:47 +00:00
|
|
|
fn generateServicesForFilePath(allocator: *std.mem.Allocator, comptime terminator: []const u8, path: []const u8, writer: anytype) ![][]const u8 {
|
|
|
|
const file = try std.fs.cwd().openFile(path, .{ .read = true, .write = false });
|
|
|
|
defer file.close();
|
|
|
|
return try generateServices(allocator, terminator, file, writer);
|
|
|
|
}
|
2021-06-30 20:40:20 +00:00
|
|
|
fn generateServices(allocator: *std.mem.Allocator, comptime _: []const u8, file: std.fs.File, writer: anytype) ![][]const u8 {
|
2021-05-30 01:17:45 +00:00
|
|
|
const json = try file.readToEndAlloc(allocator, 1024 * 1024 * 1024);
|
|
|
|
defer allocator.free(json);
|
|
|
|
const model = try smithy.parse(allocator, json);
|
|
|
|
defer model.deinit();
|
2021-09-05 20:09:22 +00:00
|
|
|
var shapes = std.StringHashMap(smithy.ShapeInfo).init(allocator);
|
2021-05-30 01:17:45 +00:00
|
|
|
defer shapes.deinit();
|
2021-09-05 20:09:22 +00:00
|
|
|
var services = std.ArrayList(smithy.ShapeInfo).init(allocator);
|
2021-05-30 01:17:45 +00:00
|
|
|
defer services.deinit();
|
|
|
|
for (model.shapes) |shape| {
|
|
|
|
try shapes.put(shape.id, shape);
|
|
|
|
switch (shape.shape) {
|
|
|
|
.service => try services.append(shape),
|
|
|
|
else => {},
|
|
|
|
}
|
|
|
|
}
|
2021-06-04 00:40:47 +00:00
|
|
|
var constant_names = std.ArrayList([]const u8).init(allocator);
|
|
|
|
defer constant_names.deinit();
|
2021-05-30 01:17:45 +00:00
|
|
|
for (services.items) |service| {
|
|
|
|
var sdk_id: []const u8 = undefined;
|
|
|
|
var version: []const u8 = service.shape.service.version;
|
2021-08-13 00:51:07 +00:00
|
|
|
var name: []const u8 = service.name;
|
2021-05-30 01:17:45 +00:00
|
|
|
var arn_namespace: []const u8 = undefined;
|
|
|
|
var sigv4_name: []const u8 = undefined;
|
|
|
|
var endpoint_prefix: []const u8 = undefined;
|
|
|
|
var aws_protocol: smithy.AwsProtocol = undefined;
|
|
|
|
for (service.shape.service.traits) |trait| {
|
|
|
|
// need the info/get the info
|
|
|
|
switch (trait) {
|
|
|
|
.aws_api_service => {
|
|
|
|
arn_namespace = trait.aws_api_service.arn_namespace;
|
|
|
|
sdk_id = trait.aws_api_service.sdk_id;
|
|
|
|
endpoint_prefix = trait.aws_api_service.endpoint_prefix;
|
|
|
|
},
|
|
|
|
.aws_auth_sigv4 => sigv4_name = trait.aws_auth_sigv4.name,
|
|
|
|
.aws_protocol => aws_protocol = trait.aws_protocol,
|
|
|
|
else => {},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Service struct
|
|
|
|
// name of the field will be snake_case of whatever comes in from
|
|
|
|
// sdk_id. Not sure this will simple...
|
2021-07-23 21:04:12 +00:00
|
|
|
const constant_name = try constantName(allocator, sdk_id);
|
2021-06-04 00:40:47 +00:00
|
|
|
try constant_names.append(constant_name);
|
2021-06-30 20:40:20 +00:00
|
|
|
try writer.print("const Self = @This();\n", .{});
|
|
|
|
try writer.print("pub const version: []const u8 = \"{s}\";\n", .{version});
|
|
|
|
try writer.print("pub const sdk_id: []const u8 = \"{s}\";\n", .{sdk_id});
|
|
|
|
try writer.print("pub const arn_namespace: []const u8 = \"{s}\";\n", .{arn_namespace});
|
|
|
|
try writer.print("pub const endpoint_prefix: []const u8 = \"{s}\";\n", .{endpoint_prefix});
|
|
|
|
try writer.print("pub const sigv4_name: []const u8 = \"{s}\";\n", .{sigv4_name});
|
2021-08-13 00:51:07 +00:00
|
|
|
try writer.print("pub const name: []const u8 = \"{s}\";\n", .{name});
|
2021-06-30 20:40:20 +00:00
|
|
|
// TODO: This really should just be ".whatevs". We're fully qualifying here, which isn't typical
|
|
|
|
try writer.print("pub const aws_protocol: smithy.AwsProtocol = smithy.{s};\n\n", .{aws_protocol});
|
|
|
|
_ = try writer.write("pub const service_metadata : struct {\n");
|
2021-06-30 16:10:08 +00:00
|
|
|
try writer.print(" version: []const u8 = \"{s}\",\n", .{version});
|
2021-05-30 01:17:45 +00:00
|
|
|
try writer.print(" sdk_id: []const u8 = \"{s}\",\n", .{sdk_id});
|
|
|
|
try writer.print(" arn_namespace: []const u8 = \"{s}\",\n", .{arn_namespace});
|
|
|
|
try writer.print(" endpoint_prefix: []const u8 = \"{s}\",\n", .{endpoint_prefix});
|
|
|
|
try writer.print(" sigv4_name: []const u8 = \"{s}\",\n", .{sigv4_name});
|
2021-08-13 00:51:07 +00:00
|
|
|
try writer.print(" name: []const u8 = \"{s}\",\n", .{name});
|
2021-05-30 01:17:45 +00:00
|
|
|
// TODO: This really should just be ".whatevs". We're fully qualifying here, which isn't typical
|
|
|
|
try writer.print(" aws_protocol: smithy.AwsProtocol = smithy.{s},\n", .{aws_protocol});
|
2021-06-30 20:40:20 +00:00
|
|
|
_ = try writer.write("} = .{};\n");
|
2021-05-30 01:17:45 +00:00
|
|
|
|
|
|
|
// Operations
|
|
|
|
for (service.shape.service.operations) |op|
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateOperation(allocator, shapes.get(op).?, shapes, writer);
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
2021-06-04 00:39:38 +00:00
|
|
|
return constant_names.toOwnedSlice();
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
2021-07-23 21:04:12 +00:00
|
|
|
fn constantName(allocator: *std.mem.Allocator, id: []const u8) ![]const u8 {
|
|
|
|
// There are some ids that don't follow consistent rules, so we'll
|
|
|
|
// look for the exceptions and, if not found, revert to the snake case
|
|
|
|
// algorithm
|
|
|
|
|
|
|
|
// This one might be a bug in snake, but it's the only example so HPDL
|
|
|
|
if (std.mem.eql(u8, id, "SESv2")) return try std.fmt.allocPrint(allocator, "ses_v2", .{});
|
|
|
|
// IoT is an acryonym, but snake wouldn't know that. Interestingly not all
|
|
|
|
// iot services are capitalizing that way.
|
|
|
|
if (std.mem.eql(u8, id, "IoTSiteWise")) return try std.fmt.allocPrint(allocator, "iot_site_wise", .{}); //sitewise?
|
|
|
|
if (std.mem.eql(u8, id, "IoTFleetHub")) return try std.fmt.allocPrint(allocator, "iot_fleet_hub", .{});
|
|
|
|
if (std.mem.eql(u8, id, "IoTSecureTunneling")) return try std.fmt.allocPrint(allocator, "iot_secure_tunneling", .{});
|
|
|
|
if (std.mem.eql(u8, id, "IoTThingsGraph")) return try std.fmt.allocPrint(allocator, "iot_things_graph", .{});
|
|
|
|
// snake turns this into dev_ops, which is a little weird
|
|
|
|
if (std.mem.eql(u8, id, "DevOps Guru")) return try std.fmt.allocPrint(allocator, "devops_guru", .{});
|
|
|
|
if (std.mem.eql(u8, id, "FSx")) return try std.fmt.allocPrint(allocator, "fsx", .{});
|
|
|
|
|
|
|
|
// Not a special case - just snake it
|
|
|
|
return try snake.fromPascalCase(allocator, id);
|
|
|
|
}
|
2021-09-05 20:09:22 +00:00
|
|
|
|
|
|
|
const GenerationState = struct {
|
|
|
|
type_stack: *std.ArrayList(*const smithy.ShapeInfo),
|
|
|
|
map_fields: *std.StringArrayHashMap(std.ArrayList([]const u8)),
|
|
|
|
allocator: *std.mem.Allocator,
|
|
|
|
indent_level: u64,
|
|
|
|
all_required: bool,
|
|
|
|
};
|
|
|
|
|
|
|
|
fn outputIndent(state: GenerationState, writer: anytype) !void {
|
|
|
|
const n_chars = 4 * state.indent_level;
|
|
|
|
try writer.writeByteNTimes(' ', n_chars);
|
|
|
|
}
|
|
|
|
fn generateOperation(allocator: *std.mem.Allocator, operation: smithy.ShapeInfo, shapes: std.StringHashMap(smithy.ShapeInfo), writer: anytype) !void {
|
2021-06-04 00:39:38 +00:00
|
|
|
const snake_case_name = try snake.fromPascalCase(allocator, operation.name);
|
|
|
|
defer allocator.free(snake_case_name);
|
2021-05-30 01:17:45 +00:00
|
|
|
|
|
|
|
var type_stack = std.ArrayList(*const smithy.ShapeInfo).init(allocator);
|
|
|
|
defer type_stack.deinit();
|
2021-09-05 20:09:22 +00:00
|
|
|
var map_fields = std.StringArrayHashMap(std.ArrayList([]const u8)).init(allocator);
|
|
|
|
defer {
|
|
|
|
for (map_fields.values()) |v|
|
|
|
|
v.deinit();
|
|
|
|
map_fields.deinit();
|
|
|
|
}
|
|
|
|
const state = GenerationState{
|
|
|
|
.type_stack = &type_stack,
|
|
|
|
.map_fields = &map_fields,
|
|
|
|
.allocator = allocator,
|
|
|
|
.indent_level = 1,
|
|
|
|
.all_required = false,
|
|
|
|
};
|
|
|
|
var child_state = state;
|
|
|
|
child_state.indent_level += 1;
|
2021-05-30 01:17:45 +00:00
|
|
|
// indent should start at 4 spaces here
|
2021-06-09 23:14:09 +00:00
|
|
|
const operation_name = avoidReserved(snake_case_name);
|
2021-06-30 20:40:20 +00:00
|
|
|
try writer.print("pub const {s}: struct ", .{operation_name});
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write("{\n");
|
2021-08-13 18:03:11 +00:00
|
|
|
for (operation.shape.operation.traits) |trait| {
|
|
|
|
if (trait == .http) {
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
|
|
|
_ = try writer.write("pub const http_config = .{\n");
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
try writer.print(".method = \"{s}\",\n", .{trait.http.method});
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
try writer.print(".uri = \"{s}\",\n", .{trait.http.uri});
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
try writer.print(".success_code = {d},\n", .{trait.http.code});
|
|
|
|
try outputIndent(state, writer);
|
|
|
|
_ = try writer.write("};\n\n");
|
2021-08-13 18:03:11 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
|
|
|
try writer.print("action_name: []const u8 = \"{s}\",\n", .{operation.name});
|
|
|
|
try outputIndent(state, writer);
|
|
|
|
_ = try writer.write("Request: type = ");
|
2021-05-30 01:17:45 +00:00
|
|
|
if (operation.shape.operation.input) |member| {
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateTypeFor(member, shapes, writer, state, false);
|
2021-06-09 23:14:09 +00:00
|
|
|
_ = try writer.write("\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateMetadataFunction(operation_name, state, writer);
|
2021-06-09 23:14:09 +00:00
|
|
|
} else {
|
|
|
|
_ = try writer.write("struct {\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateMetadataFunction(operation_name, state, writer);
|
2021-06-09 23:14:09 +00:00
|
|
|
}
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write(",\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
|
|
|
_ = try writer.write("Response: type = ");
|
2021-05-30 01:17:45 +00:00
|
|
|
if (operation.shape.operation.output) |member| {
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateTypeFor(member, shapes, writer, state, true);
|
2021-05-30 01:17:45 +00:00
|
|
|
} else _ = try writer.write("struct {}"); // we want to maintain consistency with other ops
|
|
|
|
_ = try writer.write(",\n");
|
|
|
|
|
|
|
|
if (operation.shape.operation.errors) |errors| {
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
|
|
|
_ = try writer.write("ServiceError: type = error{\n");
|
2021-05-30 01:17:45 +00:00
|
|
|
for (errors) |err| {
|
|
|
|
const err_name = getErrorName(shapes.get(err).?.name); // need to remove "exception"
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
try writer.print("{s},\n", .{err_name});
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
|
|
|
_ = try writer.write("},\n");
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
2021-06-30 20:40:20 +00:00
|
|
|
_ = try writer.write("} = .{};\n");
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-05 20:09:22 +00:00
|
|
|
fn generateMetadataFunction(operation_name: []const u8, state: GenerationState, writer: anytype) !void {
|
2021-06-09 23:14:09 +00:00
|
|
|
// TODO: Shove these lines in here, and also the else portion
|
|
|
|
// pub fn metaInfo(self: @This()) struct { service: @TypeOf(sts), action: @TypeOf(sts.get_caller_identity) } {
|
|
|
|
// return .{ .service = sts, .action = sts.get_caller_identity };
|
|
|
|
// }
|
|
|
|
// We want to add a short "get my parents" function into the response
|
2021-09-05 20:09:22 +00:00
|
|
|
var child_state = state;
|
|
|
|
child_state.indent_level += 1;
|
|
|
|
try outputIndent(child_state, writer);
|
2021-08-25 00:02:28 +00:00
|
|
|
_ = try writer.write("pub fn metaInfo() struct { ");
|
2021-06-30 20:40:20 +00:00
|
|
|
try writer.print("service_metadata: @TypeOf(service_metadata), action: @TypeOf({s})", .{operation_name});
|
2021-09-05 20:09:22 +00:00
|
|
|
_ = try writer.write(" } {\n");
|
|
|
|
child_state.indent_level += 1;
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
_ = try writer.write("return .{ .service_metadata = service_metadata, ");
|
2021-06-30 20:40:20 +00:00
|
|
|
try writer.print(".action = {s}", .{operation_name});
|
2021-09-05 20:09:22 +00:00
|
|
|
_ = try writer.write(" };\n");
|
|
|
|
child_state.indent_level -= 1;
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
_ = try writer.write("}\n");
|
|
|
|
try outputIndent(state, writer);
|
|
|
|
try writer.writeByte('}');
|
2021-06-09 23:14:09 +00:00
|
|
|
}
|
2021-05-30 01:17:45 +00:00
|
|
|
fn getErrorName(err_name: []const u8) []const u8 {
|
|
|
|
if (endsWith("Exception", err_name))
|
|
|
|
return err_name[0 .. err_name.len - "Exception".len];
|
|
|
|
|
|
|
|
if (endsWith("Fault", err_name))
|
|
|
|
return err_name[0 .. err_name.len - "Fault".len];
|
|
|
|
return err_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
fn endsWith(item: []const u8, str: []const u8) bool {
|
|
|
|
if (str.len < item.len) return false;
|
|
|
|
return std.mem.eql(u8, item, str[str.len - item.len ..]);
|
|
|
|
}
|
|
|
|
/// return type is anyerror!void as this is a recursive function, so the compiler cannot properly infer error types
|
2021-09-05 20:09:22 +00:00
|
|
|
fn generateTypeFor(shape_id: []const u8, shapes: std.StringHashMap(smithy.ShapeInfo), writer: anytype, state: GenerationState, end_structure: bool) anyerror!void {
|
2021-05-30 01:17:45 +00:00
|
|
|
if (shapes.get(shape_id) == null) {
|
|
|
|
std.debug.print("Shape ID not found. This is most likely a bug. Shape ID: {s}\n", .{shape_id});
|
|
|
|
return error.InvalidType;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We assume it must exist
|
|
|
|
const shape_info = shapes.get(shape_id).?;
|
|
|
|
const shape = shape_info.shape;
|
|
|
|
// Check for ourselves up the stack
|
|
|
|
var self_occurences: u8 = 0;
|
2021-09-05 20:09:22 +00:00
|
|
|
for (state.type_stack.items) |i| {
|
2021-05-30 01:17:45 +00:00
|
|
|
// NOTE: shapes.get isn't providing a consistent pointer - is it allocating each time?
|
|
|
|
// we will therefore need to compare ids
|
|
|
|
if (std.mem.eql(u8, i.*.id, shape_info.id))
|
|
|
|
self_occurences = self_occurences + 1;
|
|
|
|
}
|
|
|
|
// Debugging
|
|
|
|
// if (std.mem.eql(u8, shape_info.name, "Expression")) {
|
|
|
|
// std.log.info(" Type stack len: {d}, occurences: {d}\n", .{ type_stack.items.len, self_occurences });
|
|
|
|
// if (type_stack.items.len > 15) {
|
|
|
|
// std.log.info(" Type stack:\n", .{});
|
|
|
|
// for (type_stack.items) |i|
|
|
|
|
// std.log.info(" {s}: {*}", .{ i.*.id, i });
|
|
|
|
// return error.BugDetected;
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// End Debugging
|
|
|
|
if (self_occurences > 2) { // TODO: What's the appropriate number here?
|
|
|
|
// TODO: Determine if this warrants the creation of another public
|
|
|
|
// type to properly reference. Realistically, AWS or the service
|
|
|
|
// must be blocking deep recursion somewhere or this would be a great
|
|
|
|
// DOS attack
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateSimpleTypeFor("nothing", "[]const u8", writer);
|
2021-05-30 01:17:45 +00:00
|
|
|
std.log.warn("Type cycle detected, limiting depth. Type: {s}", .{shape_id});
|
|
|
|
// std.log.info(" Type stack:\n", .{});
|
|
|
|
// for (type_stack.items) |i|
|
|
|
|
// std.log.info(" {s}", .{i.*.id});
|
|
|
|
return;
|
|
|
|
}
|
2021-09-05 20:09:22 +00:00
|
|
|
try state.type_stack.append(&shape_info);
|
2021-05-30 01:17:45 +00:00
|
|
|
switch (shape) {
|
2021-06-30 16:10:08 +00:00
|
|
|
.structure => {
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateComplexTypeFor(shape_id, shape.structure.members, "struct", shapes, writer, state);
|
2021-06-09 23:14:09 +00:00
|
|
|
if (end_structure) {
|
|
|
|
// epilog
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
2021-06-09 23:14:09 +00:00
|
|
|
_ = try writer.write("}");
|
|
|
|
}
|
|
|
|
},
|
2021-06-30 16:10:08 +00:00
|
|
|
.uniontype => {
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateComplexTypeFor(shape_id, shape.uniontype.members, "union", shapes, writer, state);
|
2021-06-09 23:14:09 +00:00
|
|
|
// epilog
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
2021-06-09 23:14:09 +00:00
|
|
|
_ = try writer.write("}");
|
|
|
|
},
|
2021-09-05 20:09:22 +00:00
|
|
|
.string => |s| try generateSimpleTypeFor(s, "[]const u8", writer),
|
|
|
|
.integer => |s| try generateSimpleTypeFor(s, "i64", writer),
|
2021-06-30 16:10:08 +00:00
|
|
|
.list => {
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write("[]");
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateTypeFor(shape.list.member_target, shapes, writer, state, true);
|
2021-05-30 01:17:45 +00:00
|
|
|
},
|
2021-06-30 16:10:08 +00:00
|
|
|
.set => {
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write("[]");
|
2021-09-05 20:09:22 +00:00
|
|
|
try generateTypeFor(shape.set.member_target, shapes, writer, state, true);
|
2021-05-30 01:17:45 +00:00
|
|
|
},
|
2021-09-05 20:09:22 +00:00
|
|
|
.timestamp => |s| try generateSimpleTypeFor(s, "i64", writer),
|
|
|
|
.blob => |s| try generateSimpleTypeFor(s, "[]const u8", writer),
|
|
|
|
.boolean => |s| try generateSimpleTypeFor(s, "bool", writer),
|
|
|
|
.double => |s| try generateSimpleTypeFor(s, "f64", writer),
|
|
|
|
.float => |s| try generateSimpleTypeFor(s, "f32", writer),
|
|
|
|
.long => |s| try generateSimpleTypeFor(s, "i64", writer),
|
2021-05-30 01:17:45 +00:00
|
|
|
.map => {
|
2021-09-05 20:09:22 +00:00
|
|
|
// TODO: We need this:
|
|
|
|
//
|
|
|
|
// pub fn jsonStringifyField(self: @This(), comptime field_name: []const u8, options: anytype, out_stream: anytype) !bool {
|
|
|
|
// if (std.mem.eql(u8, "tags", field_name))
|
|
|
|
// return try serializeMap(self.tags, self.jsonFieldNameFor("tags"), options, out_stream);
|
|
|
|
// return false;
|
|
|
|
// }
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write("[]struct {\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
var child_state = state;
|
|
|
|
child_state.indent_level += 1;
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
_ = try writer.write("key: ");
|
|
|
|
try writeOptional(shape.map.traits, writer, null);
|
|
|
|
try generateTypeFor(shape.map.key, shapes, writer, state, true);
|
|
|
|
try writeOptional(shape.map.traits, writer, " = null");
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write(",\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
_ = try writer.write("value: ");
|
|
|
|
try writeOptional(shape.map.traits, writer, null);
|
|
|
|
try generateTypeFor(shape.map.key, shapes, writer, state, true);
|
|
|
|
try writeOptional(shape.map.traits, writer, " = null");
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write(",\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write("}");
|
|
|
|
},
|
|
|
|
else => {
|
|
|
|
std.log.err("encountered unimplemented shape type {s} for shape_id {s}. Generated code will not compile", .{ @tagName(shape), shape_id });
|
|
|
|
// Not sure we want to return here - I think we want an exhaustive list
|
|
|
|
// return error{UnimplementedShapeType}.UnimplementedShapeType;
|
|
|
|
},
|
|
|
|
}
|
2021-09-05 20:09:22 +00:00
|
|
|
_ = state.type_stack.pop();
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
|
|
|
|
2021-09-05 20:09:22 +00:00
|
|
|
fn generateSimpleTypeFor(_: anytype, type_name: []const u8, writer: anytype) !void {
|
|
|
|
_ = try writer.write(type_name); // This had required stuff but the problem was elsewhere. Better to leave as function just in case
|
2021-05-30 04:03:45 +00:00
|
|
|
}
|
2021-09-05 20:09:22 +00:00
|
|
|
fn generateComplexTypeFor(shape_id: []const u8, members: []smithy.TypeMember, type_type_name: []const u8, shapes: std.StringHashMap(smithy.ShapeInfo), writer: anytype, state: GenerationState) anyerror!void {
|
|
|
|
_ = shape_id;
|
2021-08-13 17:28:29 +00:00
|
|
|
const Mapping = struct { snake: []const u8, json: []const u8 };
|
2021-09-05 20:09:22 +00:00
|
|
|
var json_field_name_mappings = try std.ArrayList(Mapping).initCapacity(state.allocator, members.len);
|
2021-08-13 17:28:29 +00:00
|
|
|
defer {
|
2021-09-05 20:09:22 +00:00
|
|
|
for (json_field_name_mappings.items) |mapping|
|
|
|
|
state.allocator.free(mapping.snake);
|
2021-08-14 01:12:05 +00:00
|
|
|
json_field_name_mappings.deinit();
|
|
|
|
}
|
|
|
|
// There is an httpQueryParams trait as well, but nobody is using it. API GW
|
|
|
|
// pretends to, but it's an empty map
|
|
|
|
//
|
|
|
|
// Same with httpPayload
|
|
|
|
//
|
|
|
|
// httpLabel is interesting - right now we just assume anything can be used - do we need to track this?
|
2021-09-05 20:09:22 +00:00
|
|
|
var http_query_mappings = try std.ArrayList(Mapping).initCapacity(state.allocator, members.len);
|
2021-08-14 01:12:05 +00:00
|
|
|
defer {
|
2021-09-05 20:09:22 +00:00
|
|
|
for (http_query_mappings.items) |mapping|
|
|
|
|
state.allocator.free(mapping.snake);
|
2021-08-14 01:12:05 +00:00
|
|
|
http_query_mappings.deinit();
|
|
|
|
}
|
2021-09-05 20:09:22 +00:00
|
|
|
var http_header_mappings = try std.ArrayList(Mapping).initCapacity(state.allocator, members.len);
|
2021-08-14 01:12:05 +00:00
|
|
|
defer {
|
2021-09-05 20:09:22 +00:00
|
|
|
for (http_header_mappings.items) |mapping|
|
|
|
|
state.allocator.free(mapping.snake);
|
2021-08-14 01:12:05 +00:00
|
|
|
http_header_mappings.deinit();
|
2021-08-13 17:28:29 +00:00
|
|
|
}
|
2021-05-30 01:17:45 +00:00
|
|
|
// prolog. We'll rely on caller to get the spacing correct here
|
2021-05-30 04:03:45 +00:00
|
|
|
_ = try writer.write(type_type_name);
|
|
|
|
_ = try writer.write(" {\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
var child_state = state;
|
|
|
|
child_state.indent_level += 1;
|
2021-05-30 01:17:45 +00:00
|
|
|
for (members) |member| {
|
2021-08-13 17:28:29 +00:00
|
|
|
// This is our mapping
|
2021-09-05 20:09:22 +00:00
|
|
|
const snake_case_member = try snake.fromPascalCase(state.allocator, member.name);
|
2021-08-13 17:28:29 +00:00
|
|
|
// 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`
|
2021-08-14 01:12:05 +00:00
|
|
|
var found_name_trait = false;
|
2021-08-13 17:28:29 +00:00
|
|
|
for (member.traits) |trait| {
|
2021-08-14 01:12:05 +00:00
|
|
|
switch (trait) {
|
|
|
|
.json_name => {
|
|
|
|
found_name_trait = true;
|
2021-09-05 20:09:22 +00:00
|
|
|
json_field_name_mappings.appendAssumeCapacity(.{ .snake = try state.allocator.dupe(u8, snake_case_member), .json = trait.json_name });
|
2021-08-14 01:12:05 +00:00
|
|
|
},
|
2021-09-05 20:09:22 +00:00
|
|
|
.http_query => http_query_mappings.appendAssumeCapacity(.{ .snake = try state.allocator.dupe(u8, snake_case_member), .json = trait.http_query }),
|
|
|
|
.http_header => http_header_mappings.appendAssumeCapacity(.{ .snake = try state.allocator.dupe(u8, snake_case_member), .json = trait.http_header }),
|
2021-08-14 01:12:05 +00:00
|
|
|
else => {},
|
2021-08-13 17:28:29 +00:00
|
|
|
}
|
|
|
|
}
|
2021-08-14 01:12:05 +00:00
|
|
|
if (!found_name_trait)
|
2021-09-05 20:09:22 +00:00
|
|
|
json_field_name_mappings.appendAssumeCapacity(.{ .snake = try state.allocator.dupe(u8, snake_case_member), .json = member.name });
|
|
|
|
defer state.allocator.free(snake_case_member);
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
try writer.print("{s}: ", .{avoidReserved(snake_case_member)});
|
|
|
|
try writeOptional(member.traits, writer, null);
|
|
|
|
try generateTypeFor(member.target, shapes, writer, child_state, true);
|
|
|
|
if (!std.mem.eql(u8, "union", type_type_name))
|
2021-06-09 23:14:09 +00:00
|
|
|
try writeOptional(member.traits, writer, " = null");
|
2021-05-30 01:17:45 +00:00
|
|
|
_ = try writer.write(",\n");
|
|
|
|
}
|
2021-08-13 17:28:29 +00:00
|
|
|
|
2021-08-14 01:12:05 +00:00
|
|
|
// Add in http query metadata (only relevant to REST JSON APIs - do we care?
|
|
|
|
// pub const http_query = .{
|
|
|
|
// .master_region = "MasterRegion",
|
|
|
|
// .function_version = "FunctionVersion",
|
|
|
|
// .marker = "Marker",
|
|
|
|
// .max_items = "MaxItems",
|
|
|
|
// };
|
|
|
|
if (http_query_mappings.items.len > 0) _ = try writer.write("\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
try writeMappings(child_state, "pub ", "http_query", http_query_mappings, false, writer);
|
2021-08-14 01:12:05 +00:00
|
|
|
if (http_query_mappings.items.len > 0 and http_header_mappings.items.len > 0) _ = try writer.write("\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
try writeMappings(child_state, "pub ", "http_header", http_header_mappings, false, writer);
|
2021-08-14 01:12:05 +00:00
|
|
|
|
2021-08-13 17:28:29 +00:00
|
|
|
// 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);
|
|
|
|
// }
|
|
|
|
//
|
2021-09-05 20:09:22 +00:00
|
|
|
try writer.writeByte('\n');
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
_ = try writer.write("pub fn jsonFieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 ");
|
2021-08-13 17:28:29 +00:00
|
|
|
_ = try writer.write("{\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
var grandchild_state = child_state;
|
|
|
|
grandchild_state.indent_level += 1;
|
|
|
|
// We need to force output here becaseu we're referencing the field in the return statement below
|
|
|
|
try writeMappings(grandchild_state, "", "mappings", json_field_name_mappings, true, writer);
|
|
|
|
try outputIndent(grandchild_state, writer);
|
|
|
|
_ = try writer.write("return @field(mappings, field_name);\n");
|
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
_ = try writer.write("}\n");
|
|
|
|
|
|
|
|
// TODO: Deal with the jsonStringifyField stuff
|
2021-08-14 01:12:05 +00:00
|
|
|
}
|
|
|
|
|
2021-09-05 20:09:22 +00:00
|
|
|
fn writeMappings(state: GenerationState, @"pub": []const u8, mapping_name: []const u8, mappings: anytype, force_output: bool, writer: anytype) !void {
|
|
|
|
if (mappings.items.len == 0 and !force_output) return;
|
|
|
|
try outputIndent(state, writer);
|
|
|
|
if (mappings.items.len == 0) {
|
|
|
|
try writer.print("{s}const {s} = ", .{ @"pub", mapping_name });
|
|
|
|
_ = try writer.write(".{};\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
try writer.print("{s}const {s} = .", .{ @"pub", mapping_name });
|
2021-08-13 17:28:29 +00:00
|
|
|
_ = try writer.write("{\n");
|
2021-09-05 20:09:22 +00:00
|
|
|
var child_state = state;
|
|
|
|
child_state.indent_level += 1;
|
2021-08-13 17:28:29 +00:00
|
|
|
for (mappings.items) |mapping| {
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(child_state, writer);
|
|
|
|
try writer.print(".{s} = \"{s}\",\n", .{ avoidReserved(mapping.snake), mapping.json });
|
2021-08-13 17:28:29 +00:00
|
|
|
}
|
2021-09-05 20:09:22 +00:00
|
|
|
try outputIndent(state, writer);
|
2021-08-14 01:12:05 +00:00
|
|
|
_ = try writer.write("};\n");
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn writeOptional(traits: ?[]smithy.Trait, writer: anytype, value: ?[]const u8) !void {
|
|
|
|
if (traits) |ts| {
|
|
|
|
for (ts) |t|
|
2021-05-30 04:03:45 +00:00
|
|
|
if (t == .required) return;
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
2021-05-30 04:03:45 +00:00
|
|
|
|
|
|
|
// not required
|
|
|
|
if (value) |v| {
|
|
|
|
_ = try writer.write(v);
|
2021-06-09 23:14:09 +00:00
|
|
|
} else _ = try writer.write("?");
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|
|
|
|
fn camelCase(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
|
|
|
|
const first_letter = name[0] + ('a' - 'A');
|
|
|
|
return try std.fmt.allocPrint(allocator, "{c}{s}", .{ first_letter, name[1..] });
|
|
|
|
}
|
2021-06-09 23:14:09 +00:00
|
|
|
fn avoidReserved(snake_name: []const u8) []const u8 {
|
|
|
|
if (std.mem.eql(u8, snake_name, "error")) return "@\"error\"";
|
|
|
|
if (std.mem.eql(u8, snake_name, "return")) return "@\"return\"";
|
|
|
|
if (std.mem.eql(u8, snake_name, "not")) return "@\"not\"";
|
|
|
|
if (std.mem.eql(u8, snake_name, "and")) return "@\"and\"";
|
|
|
|
if (std.mem.eql(u8, snake_name, "or")) return "@\"or\"";
|
|
|
|
if (std.mem.eql(u8, snake_name, "test")) return "@\"test\"";
|
|
|
|
if (std.mem.eql(u8, snake_name, "null")) return "@\"null\"";
|
|
|
|
return snake_name;
|
2021-05-30 01:17:45 +00:00
|
|
|
}
|