update code generation without containing struct
Some checks failed
continuous-integration/drone/push Build is failing

Instead we will rely here on the implicit struct provided by the file
itself. This means that when the service_manifest.zig import is
assigned, we don't end up with a construct like "manifest.sts.sts" to
get to the sts service. This simplifies greatly the way that
servicemodel.zig needs to behave.

One down side, however, is that the structure does not seem
accessible with the current zig language, making metaInfo unable
to access itself as it did before. Or maybe it can, I just can't find
it. So, this change also adds a new "service_metadata" public constant
with the same declarations being published at the file level, and
that is the new return from the metaInfo function. Our aws.zig
only really needs the action and that metadata, so we're ok with that
even if there is some duplication (we could codegen pointers over, and
maybe should to save a little bit of space).
This commit is contained in:
Emil Lerch 2021-06-30 13:40:20 -07:00
parent 13e43528b5
commit dfa0be60b9
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
3 changed files with 40 additions and 48 deletions

View File

@ -63,7 +63,7 @@ fn generateServicesForFilePath(allocator: *std.mem.Allocator, comptime terminato
defer file.close();
return try generateServices(allocator, terminator, file, writer);
}
fn generateServices(allocator: *std.mem.Allocator, comptime terminator: []const u8, file: std.fs.File, writer: anytype) ![][]const u8 {
fn generateServices(allocator: *std.mem.Allocator, comptime _: []const u8, file: std.fs.File, writer: anytype) ![][]const u8 {
const json = try file.readToEndAlloc(allocator, 1024 * 1024 * 1024);
defer allocator.free(json);
const model = try smithy.parse(allocator, json);
@ -108,9 +108,15 @@ fn generateServices(allocator: *std.mem.Allocator, comptime terminator: []const
// sdk_id. Not sure this will simple...
const constant_name = try snake.fromPascalCase(allocator, sdk_id);
try constant_names.append(constant_name);
try writer.print("pub const {s}: struct ", .{constant_name});
_ = try writer.write("{\n");
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});
// 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");
try writer.print(" version: []const u8 = \"{s}\",\n", .{version});
try writer.print(" sdk_id: []const u8 = \"{s}\",\n", .{sdk_id});
try writer.print(" arn_namespace: []const u8 = \"{s}\",\n", .{arn_namespace});
@ -118,14 +124,11 @@ fn generateServices(allocator: *std.mem.Allocator, comptime terminator: []const
try writer.print(" sigv4_name: []const u8 = \"{s}\",\n", .{sigv4_name});
// 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});
_ = try writer.write("} = .{};\n");
// Operations
for (service.shape.service.operations) |op|
try generateOperation(allocator, shapes.get(op).?, shapes, writer, constant_name);
// End service
_ = try writer.write("} = .{}" ++ terminator ++ " // end of service: ");
try writer.print("{s}\n", .{arn_namespace}); // this var needs to match above
}
return constant_names.toOwnedSlice();
}
@ -133,15 +136,15 @@ fn generateOperation(allocator: *std.mem.Allocator, operation: smithy.ShapeInfo,
const snake_case_name = try snake.fromPascalCase(allocator, operation.name);
defer allocator.free(snake_case_name);
const prefix = " ";
const prefix = " ";
var type_stack = std.ArrayList(*const smithy.ShapeInfo).init(allocator);
defer type_stack.deinit();
// indent should start at 4 spaces here
const operation_name = avoidReserved(snake_case_name);
try writer.print(" {s}: struct ", .{operation_name});
try writer.print("pub const {s}: struct ", .{operation_name});
_ = try writer.write("{\n");
try writer.print(" action_name: []const u8 = \"{s}\",\n", .{operation.name});
_ = try writer.write(" Request: type = ");
try writer.print(" action_name: []const u8 = \"{s}\",\n", .{operation.name});
_ = try writer.write(" Request: type = ");
if (operation.shape.operation.input) |member| {
try generateTypeFor(allocator, member, shapes, writer, prefix, false, &type_stack, false);
_ = try writer.write("\n");
@ -151,24 +154,24 @@ fn generateOperation(allocator: *std.mem.Allocator, operation: smithy.ShapeInfo,
try generateMetadataFunction(service, operation_name, prefix, writer);
}
_ = try writer.write(",\n");
_ = try writer.write(" Response: type = ");
_ = try writer.write(" Response: type = ");
if (operation.shape.operation.output) |member| {
try generateTypeFor(allocator, member, shapes, writer, " ", true, &type_stack, true);
try generateTypeFor(allocator, member, shapes, writer, " ", true, &type_stack, true);
} else _ = try writer.write("struct {}"); // we want to maintain consistency with other ops
_ = try writer.write(",\n");
if (operation.shape.operation.errors) |errors| {
_ = try writer.write(" ServiceError: type = error{\n");
_ = try writer.write(" ServiceError: type = error{\n");
for (errors) |err| {
const err_name = getErrorName(shapes.get(err).?.name); // need to remove "exception"
try writer.print(" {s},\n", .{err_name});
try writer.print(" {s},\n", .{err_name});
}
_ = try writer.write(" },\n");
_ = try writer.write(" },\n");
}
_ = try writer.write(" } = .{},\n");
_ = try writer.write("} = .{};\n");
}
fn generateMetadataFunction(service: []const u8, operation_name: []const u8, comptime prefix: []const u8, writer: anytype) !void {
fn generateMetadataFunction(_: []const u8, operation_name: []const u8, comptime prefix: []const u8, writer: anytype) !void {
// 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 };
@ -176,9 +179,9 @@ fn generateMetadataFunction(service: []const u8, operation_name: []const u8, com
// We want to add a short "get my parents" function into the response
try writer.print("{s} ", .{prefix});
_ = try writer.write("pub fn metaInfo(_: @This()) struct { ");
try writer.print("service: @TypeOf({s}), action: @TypeOf({s}.{s})", .{ service, service, operation_name });
_ = try writer.write(" } {\n" ++ prefix ++ " return .{ ");
try writer.print(".service = {s}, .action = {s}.{s}", .{ service, service, operation_name });
try writer.print("service_metadata: @TypeOf(service_metadata), action: @TypeOf({s})", .{operation_name});
_ = try writer.write(" } {\n" ++ prefix ++ " return .{ .service_metadata = service_metadata, ");
try writer.print(".action = {s}", .{operation_name});
_ = try writer.write(" };\n" ++ prefix ++ " }\n" ++ prefix ++ "}");
}
fn getErrorName(err_name: []const u8) []const u8 {

View File

@ -43,16 +43,16 @@ pub const Aws = struct {
// every codegenned request object includes a metaInfo function to get
// pointers to service and action
const meta_info = request.metaInfo();
const service = meta_info.service;
const service_meta = meta_info.service_metadata;
const action = meta_info.action;
log.debug("call: prefix {s}, sigv4 {s}, version {s}, action {s}", .{
service.endpoint_prefix,
service.sigv4_name,
service.version,
service_meta.endpoint_prefix,
service_meta.sigv4_name,
service_meta.version,
action.action_name,
});
log.debug("proto: {s}", .{service.aws_protocol});
log.debug("proto: {s}", .{service_meta.aws_protocol});
// It seems as though there are 3 major branches of the 6 protocols.
// 1. query/ec2_query, which are identical until you get to complex
@ -62,8 +62,8 @@ pub const Aws = struct {
// for empty body serialization), but differ in error handling.
// We're not doing a lot of error handling here, though.
// 3. rest_xml: This is a one-off for S3, never used since
switch (service.aws_protocol) {
.query, .ec2_query => return self.callQuery(request, service, action, options),
switch (service_meta.aws_protocol) {
.query, .ec2_query => return self.callQuery(request, service_meta, action, options),
.rest_json_1, .json_1_0, .json_1_1 => @compileError("REST Json, Json 1.0/1.1 protocol not yet supported"),
.rest_xml => @compileError("REST XML protocol not yet supported"),
}
@ -74,7 +74,7 @@ pub const Aws = struct {
// Query, so we'll handle both here. Realistically we probably don't effectively
// handle lists and maps properly anyway yet, so we'll go for it and see
// where it breaks. PRs and/or failing test cases appreciated.
fn callQuery(self: Self, comptime request: anytype, service: anytype, action: anytype, options: Options) !FullResponse(request) {
fn callQuery(self: Self, comptime request: anytype, service_meta: anytype, action: anytype, options: Options) !FullResponse(request) {
var buffer = std.ArrayList(u8).init(self.allocator);
defer buffer.deinit();
const writer = buffer.writer();
@ -84,16 +84,16 @@ pub const Aws = struct {
});
const continuation = if (buffer.items.len > 0) "&" else "";
const body = try std.fmt.allocPrint(self.allocator, "Action={s}&Version={s}{s}{s}\n", .{ action.action_name, service.version, continuation, buffer.items });
const body = try std.fmt.allocPrint(self.allocator, "Action={s}&Version={s}{s}{s}\n", .{ action.action_name, service_meta.version, continuation, buffer.items });
defer self.allocator.free(body);
const FullR = FullResponse(request);
const response = try self.aws_http.callApi(
service.endpoint_prefix,
service_meta.endpoint_prefix,
body,
.{
.region = options.region,
.dualstack = options.dualstack,
.sigv4_service_name = service.sigv4_name,
.sigv4_service_name = service_meta.sigv4_name,
},
);
// TODO: Can response handling be reused?

View File

@ -3,20 +3,12 @@ const service_list = @import("models/service_manifest.zig");
const expectEqualStrings = std.testing.expectEqualStrings;
pub fn Services(service_imports: anytype) type {
if (service_imports.len == 0)
return service_list;
if (service_imports.len == 0) return services;
// From here, the fields of our structure can be generated at comptime...
var fields: [serviceCount(service_imports)]std.builtin.TypeInfo.StructField = undefined;
// This is run at comptime with multiple nested loops and a large (267 at
// time of writing) number of services. 4 was chosen by trial and error,
// but otherwise the branch count will be the product of field length,
// service list length and the number of imports requested
// @setEvalBranchQuota(4 * fields.len * service_list.len * std.math.min(service_imports.len, 1));
for (fields) |*item, i| {
const import_service = @field(service_list, @tagName(service_imports[i]));
const import_field = @field(import_service, @tagName(service_imports[i]));
const import_field = @field(service_list, @tagName(service_imports[i]));
item.* = .{
.name = @tagName(service_imports[i]),
.field_type = @TypeOf(import_field),
@ -44,7 +36,7 @@ fn serviceCount(desired_services: anytype) usize {
/// Using this constant may blow up build times. Recommed using Services()
/// function directly, e.g. const services = Services(.{.sts, .ec2, .s3, .ddb}){};
pub const services = Services(.{}){};
pub const services = service_list;
test "services includes sts" {
try expectEqualStrings("2011-06-15", services.sts.version);
@ -55,13 +47,10 @@ test "sts includes get_caller_identity" {
test "can get service and action name from request" {
// get request object. This call doesn't have parameters
const req = services.sts.get_caller_identity.Request{};
// const metadata = @TypeOf(req).metaInfo();
const metadata = req.metaInfo();
try expectEqualStrings("2011-06-15", metadata.service.version);
// expectEqualStrings("GetCallerIdentity", metadata.action.action_name);
try expectEqualStrings("2011-06-15", metadata.service_metadata.version);
}
test "can filter services" {
const filtered_services = Services(.{ .sts, .waf_v2 }){};
// const filtered_services = Services(.{.sts}){};
try expectEqualStrings("2011-06-15", filtered_services.sts.version);
}