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); }