From dfa0be60b990e77f90e1c5f49112648fe7e6f2a2 Mon Sep 17 00:00:00 2001 From: Emil Lerch Date: Wed, 30 Jun 2021 13:40:20 -0700 Subject: [PATCH] update code generation without containing struct 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). --- codegen/src/main.zig | 47 +++++++++++++++++++++++--------------------- src/aws.zig | 22 ++++++++++----------- src/servicemodel.zig | 19 ++++-------------- 3 files changed, 40 insertions(+), 48 deletions(-) diff --git a/codegen/src/main.zig b/codegen/src/main.zig index bdc6995..d47de74 100644 --- a/codegen/src/main.zig +++ b/codegen/src/main.zig @@ -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 { diff --git a/src/aws.zig b/src/aws.zig index dee8b1c..f207c55 100644 --- a/src/aws.zig +++ b/src/aws.zig @@ -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? diff --git a/src/servicemodel.zig b/src/servicemodel.zig index 481c0a9..df4e395 100644 --- a/src/servicemodel.zig +++ b/src/servicemodel.zig @@ -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); }