feat: optional lists and maps to json
This commit is contained in:
		
							parent
							
								
									9bc13d932a
								
							
						
					
					
						commit
						489581ead2
					
				
					 3 changed files with 383 additions and 186 deletions
				
			
		|  | @ -5,6 +5,9 @@ const case = @import("case"); | |||
| 
 | ||||
| var verbose = false; | ||||
| 
 | ||||
| const Shape = @FieldType(smithy.ShapeInfo, "shape"); | ||||
| const Service = @TypeOf((Shape{ .service = undefined }).service); | ||||
| 
 | ||||
| pub fn main() anyerror!void { | ||||
|     const root_progress_node = std.Progress.start(.{}); | ||||
|     defer root_progress_node.end(); | ||||
|  | @ -392,7 +395,8 @@ fn generateServices(allocator: std.mem.Allocator, comptime _: []const u8, file: | |||
|     var generated = std.StringHashMap(void).init(allocator); | ||||
|     defer generated.deinit(); | ||||
| 
 | ||||
|     const state = FileGenerationState{ | ||||
|     var state = FileGenerationState{ | ||||
|         .protocol = undefined, | ||||
|         .shape_references = shape_references, | ||||
|         .additional_types_to_generate = &unresolved, | ||||
|         .additional_types_generated = &generated, | ||||
|  | @ -415,7 +419,10 @@ fn generateServices(allocator: std.mem.Allocator, comptime _: []const u8, file: | |||
|                     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, | ||||
|                 .aws_protocol => { | ||||
|                     aws_protocol = trait.aws_protocol; | ||||
|                     state.protocol = aws_protocol; | ||||
|                 }, | ||||
|                 else => {}, | ||||
|             } | ||||
|         } | ||||
|  | @ -499,32 +506,38 @@ fn constantName(allocator: std.mem.Allocator, id: []const u8, comptime to_case: | |||
|     // look for the exceptions and, if not found, revert to the snake case | ||||
|     // algorithm | ||||
| 
 | ||||
|     var buf = std.mem.zeroes([256]u8); | ||||
|     @memcpy(buf[0..id.len], id); | ||||
| 
 | ||||
|     var name = try allocator.dupe(u8, id); | ||||
| 
 | ||||
|     const simple_replacements = &.{ | ||||
|         &.{ "DevOps", "Devops" }, | ||||
|         &.{ "IoT", "Iot" }, | ||||
|         &.{ "FSx", "Fsx" }, | ||||
|         &.{ "CloudFront", "Cloudfront" }, | ||||
|     }; | ||||
| 
 | ||||
|     inline for (simple_replacements) |rep| { | ||||
|         if (std.mem.indexOf(u8, name, rep[0])) |idx| @memcpy(name[idx .. idx + rep[0].len], rep[1]); | ||||
|     } | ||||
| 
 | ||||
|     if (to_case == .snake) { | ||||
|         // 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", .{}); | ||||
|         if (std.mem.eql(u8, id, "CloudFront")) return try std.fmt.allocPrint(allocator, "cloudfront", .{}); | ||||
|         // 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_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", .{}); | ||||
|         if (std.mem.eql(u8, id, "ETag")) return try std.fmt.allocPrint(allocator, "e_tag", .{}); | ||||
|     } | ||||
| 
 | ||||
|     // Not a special case - just snake it | ||||
|     return try case.allocTo(allocator, to_case, id); | ||||
|     return try case.allocTo(allocator, to_case, name); | ||||
| } | ||||
| 
 | ||||
| const FileGenerationState = struct { | ||||
|     protocol: smithy.AwsProtocol, | ||||
|     shapes: std.StringHashMap(smithy.ShapeInfo), | ||||
|     shape_references: std.StringHashMap(u64), | ||||
|     additional_types_to_generate: *std.ArrayList(smithy.ShapeInfo), | ||||
|     additional_types_generated: *std.StringHashMap(void), | ||||
| }; | ||||
| 
 | ||||
| const GenerationState = struct { | ||||
|     type_stack: *std.ArrayList(*const smithy.ShapeInfo), | ||||
|     file_state: FileGenerationState, | ||||
|  | @ -532,6 +545,26 @@ const GenerationState = struct { | |||
|     allocator: std.mem.Allocator, | ||||
|     indent_level: u64, | ||||
| 
 | ||||
|     fn appendToTypeStack(self: @This(), shape_info: *const smithy.ShapeInfo) !void { | ||||
|         try self.type_stack.append(shape_info); | ||||
|     } | ||||
| 
 | ||||
|     fn popFromTypeStack(self: @This()) void { | ||||
|         _ = self.type_stack.pop(); | ||||
|     } | ||||
| 
 | ||||
|     fn getTypeRecurrenceCount(self: @This(), id: []const u8) u8 { | ||||
|         var self_occurences: u8 = 0; | ||||
| 
 | ||||
|         for (self.type_stack.items) |i| { | ||||
|             if (std.mem.eql(u8, i.id, id)) { | ||||
|                 self_occurences += 1; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return self_occurences; | ||||
|     } | ||||
| 
 | ||||
|     fn indent(self: @This()) GenerationState { | ||||
|         var new_state = self.clone(); | ||||
|         new_state.indent_level += 1; | ||||
|  | @ -629,7 +662,7 @@ fn generateOperation(allocator: std.mem.Allocator, operation: smithy.ShapeInfo, | |||
|         }; | ||||
| 
 | ||||
|         if (maybe_shape_id == null or | ||||
|             (try shapeInfoForId(maybe_shape_id.?, state)).shape == .unit) | ||||
|             (try shapeInfoForId(maybe_shape_id.?, state.file_state.shapes)).shape == .unit) | ||||
|         { | ||||
|             _ = try writer.write("struct {\n"); | ||||
|         } else if (maybe_shape_id) |shape_id| { | ||||
|  | @ -640,6 +673,7 @@ fn generateOperation(allocator: std.mem.Allocator, operation: smithy.ShapeInfo, | |||
|                 .request => { | ||||
|                     var new_state = state.clone(); | ||||
|                     new_state.indent_level = 0; | ||||
|                     std.debug.assert(new_state.type_stack.items.len == 0); | ||||
| 
 | ||||
|                     try generateToJsonFunction(shape_id, writer.any(), new_state, generate_type_options.keyCase(.pascal)); | ||||
| 
 | ||||
|  | @ -720,7 +754,19 @@ fn generateMetadataFunction(operation_name: []const u8, state: GenerationState, | |||
|     } | ||||
| } | ||||
| 
 | ||||
| const Shape = @FieldType(smithy.ShapeInfo, "shape"); | ||||
| fn findTrait(trait_type: smithy.TraitType, traits: []smithy.Trait) ?smithy.Trait { | ||||
|     for (traits) |trait| { | ||||
|         if (trait == trait_type) { | ||||
|             return trait; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return null; | ||||
| } | ||||
| 
 | ||||
| fn hasTrait(trait_type: smithy.TraitType, traits: []smithy.Trait) bool { | ||||
|     return findTrait(trait_type, traits) != null; | ||||
| } | ||||
| 
 | ||||
| const JsonMember = struct { | ||||
|     field_name: []const u8, | ||||
|  | @ -730,33 +776,62 @@ const JsonMember = struct { | |||
|     shape_info: smithy.ShapeInfo, | ||||
| }; | ||||
| 
 | ||||
| fn getJsonMembers(allocator: std.mem.Allocator, shape: Shape, state: GenerationState) !std.ArrayListUnmanaged(JsonMember) { | ||||
|     var json_members = std.ArrayListUnmanaged(JsonMember){}; | ||||
| fn getJsonMembers(allocator: std.mem.Allocator, shape: Shape, state: GenerationState) !?std.ArrayListUnmanaged(JsonMember) { | ||||
|     const is_json_shape = switch (state.file_state.protocol) { | ||||
|         .json_1_0, .json_1_1, .rest_json_1 => true, | ||||
|         else => false, | ||||
|     }; | ||||
| 
 | ||||
|     for (shape.structure.members) |member| { | ||||
|         var found_name_trait = false; | ||||
|     if (!is_json_shape) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     var hash_map = std.StringHashMapUnmanaged(smithy.TypeMember){}; | ||||
| 
 | ||||
|     const shape_members = getShapeMembers(shape); | ||||
|     for (shape_members) |member| { | ||||
|         try hash_map.putNoClobber(state.allocator, member.name, member); | ||||
|     } | ||||
| 
 | ||||
|     for (shape_members) |member| { | ||||
|         for (member.traits) |trait| { | ||||
|             if (found_name_trait) { | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             switch (trait) { | ||||
|                 .json_name => |key| { | ||||
|                     found_name_trait = true; | ||||
|                     try json_members.append(allocator, .{ | ||||
|                         .field_name = try constantName(allocator, member.name, .snake), | ||||
|                         .json_key = key, | ||||
|                         .target = member.target, | ||||
|                         .type_member = member, | ||||
|                         .shape_info = try shapeInfoForId(member.target, state), | ||||
|                     }); | ||||
|                 .http_header, .http_query => { | ||||
|                     std.debug.assert(hash_map.remove(member.name)); | ||||
|                     break; | ||||
|                 }, | ||||
|                 else => {}, | ||||
|                 else => continue, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     if (hash_map.count() == 0) { | ||||
|         return null; | ||||
|     } | ||||
| 
 | ||||
|     var json_members = std.ArrayListUnmanaged(JsonMember){}; | ||||
| 
 | ||||
|     var iter = hash_map.iterator(); | ||||
|     while (iter.next()) |kvp| { | ||||
|         const member = kvp.value_ptr.*; | ||||
| 
 | ||||
|         const key = blk: { | ||||
|             if (findTrait(.json_name, member.traits)) |trait| { | ||||
|                 break :blk trait.json_name; | ||||
|             } | ||||
| 
 | ||||
|             break :blk try constantName(allocator, member.name, .camel); | ||||
|         }; | ||||
| 
 | ||||
|         try json_members.append(allocator, .{ | ||||
|             .field_name = try constantName(allocator, member.name, .snake), | ||||
|             .json_key = key, | ||||
|             .target = member.target, | ||||
|             .type_member = member, | ||||
|             .shape_info = try shapeInfoForId(member.target, state.file_state.shapes), | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     return json_members; | ||||
| } | ||||
| 
 | ||||
|  | @ -764,42 +839,44 @@ fn generateToJsonFunction(shape_id: []const u8, writer: std.io.AnyWriter, state: | |||
|     _ = options; | ||||
|     const allocator = state.allocator; | ||||
| 
 | ||||
|     const shape_info = try shapeInfoForId(shape_id, state); | ||||
|     const shape_info = try shapeInfoForId(shape_id, state.file_state.shapes); | ||||
|     const shape = shape_info.shape; | ||||
| 
 | ||||
|     var json_members = try getJsonMembers(allocator, shape, state); | ||||
|     defer json_members.deinit(allocator); | ||||
|     if (try getJsonMembers(allocator, shape, state)) |json_members| { | ||||
|         if (json_members.items.len > 0) { | ||||
|             try writer.writeAll("/// Allocator should be from an Arena\n"); | ||||
|             try writer.writeAll("pub fn toJson(self: @This(), allocator: std.mem.Allocator) !std.json.Value {\n"); | ||||
|             try writer.writeAll("var object_map = std.json.ObjectMap.init(allocator);\n"); | ||||
| 
 | ||||
|     if (json_members.items.len > 0) { | ||||
|         try writer.writeAll("/// Allocator should be from an Arena\n"); | ||||
|         try writer.writeAll("pub fn toJson(self: @This(), allocator: std.mem.Allocator) !std.json.Value {\n"); | ||||
|         try writer.writeAll("var object_map = std.json.ObjectMap.init(allocator);\n"); | ||||
|             for (json_members.items) |member| { | ||||
|                 const member_value = try getMemberValueBlock(allocator, "self", member); | ||||
|                 defer allocator.free(member_value); | ||||
| 
 | ||||
|         for (json_members.items) |member| { | ||||
|             const member_value = try getMemberValueBlock(allocator, "self", member); | ||||
|             defer allocator.free(member_value); | ||||
|                 try writer.print("try object_map.put(\"{s}\", ", .{member.json_key}); | ||||
|                 try memberToJson( | ||||
|                     .{ | ||||
|                         .shape_id = member.target, | ||||
|                         .field_name = member.field_name, | ||||
|                         .field_value = member_value, | ||||
|                         .state = state.indent(), | ||||
|                         .member = member.type_member, | ||||
|                     }, | ||||
|                     writer, | ||||
|                 ); | ||||
|                 try writer.writeAll(");\n"); | ||||
|             } | ||||
| 
 | ||||
|             try writer.print("try object_map.put(\"{s}\", ", .{member.json_key}); | ||||
|             try memberToJson( | ||||
|                 member.target, | ||||
|                 member.field_name, | ||||
|                 member_value, | ||||
|                 state.indent(), | ||||
|                 writer, | ||||
|             ); | ||||
|             try writer.writeAll(");\n"); | ||||
|             try writer.writeAll("return .{ .object = object_map, };\n"); | ||||
|             try writer.writeAll("}\n\n"); | ||||
| 
 | ||||
|             // json stringify function | ||||
|             try writer.writeAll("pub fn jsonStringify(self: @This(), jw: anytype) !void {\n"); | ||||
|             try writer.writeAll("var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);\n"); | ||||
|             try writer.writeAll("defer arena.deinit();\n"); | ||||
|             try writer.writeAll("const json_value = try self.toJson(arena.allocator());\n"); | ||||
|             try writer.writeAll("try jw.write(json_value);\n"); | ||||
|             try writer.writeAll("}\n"); | ||||
|         } | ||||
| 
 | ||||
|         try writer.writeAll("return .{ .object = object_map, };\n"); | ||||
|         try writer.writeAll("}\n\n"); | ||||
| 
 | ||||
|         // json stringify function | ||||
|         try writer.writeAll("pub fn jsonStringify(self: @This(), jw: anytype) !void {\n"); | ||||
|         try writer.writeAll("var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);\n"); | ||||
|         try writer.writeAll("defer arena.deinit();\n"); | ||||
|         try writer.writeAll("const json_value = try self.toJson(arena.allocator());\n"); | ||||
|         try writer.writeAll("try jw.write(json_value);\n"); | ||||
|         try writer.writeAll("}\n"); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -831,6 +908,14 @@ fn getShapeTraits(shape: Shape) []smithy.Trait { | |||
|     }; | ||||
| } | ||||
| 
 | ||||
| fn getShapeMembers(shape: Shape) []smithy.TypeMember { | ||||
|     return switch (shape) { | ||||
|         .structure => |s| s.members, | ||||
|         .uniontype => |s| s.members, | ||||
|         else => std.debug.panic("Unexpected shape type: {}", .{shape}), | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| fn shapeIsLeaf(shape: Shape) bool { | ||||
|     return switch (shape) { | ||||
|         .@"enum", | ||||
|  | @ -853,16 +938,7 @@ fn shapeIsLeaf(shape: Shape) bool { | |||
| } | ||||
| 
 | ||||
| fn shapeIsOptional(traits: []smithy.Trait) bool { | ||||
|     var optional = true; | ||||
| 
 | ||||
|     for (traits) |t| { | ||||
|         if (t == .required) { | ||||
|             optional = false; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return optional; | ||||
|     return !hasTrait(.required, traits); | ||||
| } | ||||
| 
 | ||||
| fn getShapeJsonValueType(shape: Shape) []const u8 { | ||||
|  | @ -875,24 +951,24 @@ fn getShapeJsonValueType(shape: Shape) []const u8 { | |||
|     }; | ||||
| } | ||||
| 
 | ||||
| fn getMemberValueBlock(allocator: std.mem.Allocator, source: []const u8, member: JsonMember) ![]const u8 { | ||||
|     const member_value = try std.fmt.allocPrint(allocator, "@field({s}, \"{s}\")", .{ source, member.field_name }); | ||||
|     defer allocator.free(member_value); | ||||
| fn writeMemberValue( | ||||
|     allocator: std.mem.Allocator, | ||||
|     writer: anytype, | ||||
|     member_value: []const u8, | ||||
|     shape_info: smithy.ShapeInfo, | ||||
|     traits: []smithy.Trait, | ||||
| ) !void { | ||||
|     if (shapeIsLeaf(shape_info.shape)) { | ||||
|         const json_value_type = getShapeJsonValueType(shape_info.shape); | ||||
| 
 | ||||
|     const member_value_name = try case.allocTo(allocator, .snake, member_value); | ||||
|     defer allocator.free(member_value_name); | ||||
|         if (shapeIsOptional(traits)) { | ||||
|             const member_value_capture = try case.allocTo(allocator, .snake, member_value); | ||||
|             defer allocator.free(member_value_capture); | ||||
| 
 | ||||
|     var output_block = std.ArrayListUnmanaged(u8){}; | ||||
|     var writer = output_block.writer(allocator); | ||||
| 
 | ||||
|     if (shapeIsLeaf(member.shape_info.shape)) { | ||||
|         const json_value_type = getShapeJsonValueType(member.shape_info.shape); | ||||
| 
 | ||||
|         if (shapeIsOptional(member.type_member.traits)) { | ||||
|             try writer.print("if ({s}) |{s}|", .{ member_value, member_value_name }); | ||||
|             try writer.print("if ({s}) |{s}|", .{ member_value, member_value_capture }); | ||||
|             try writer.writeAll("std.json.Value{"); | ||||
|             try writer.writeAll(json_value_type); | ||||
|             try writer.print(" = {s}", .{member_value_name}); | ||||
|             try writer.print(" = {s}", .{member_value_capture}); | ||||
|             try writer.writeAll("} else .{ .null = undefined }"); | ||||
|         } else { | ||||
|             try writer.writeAll("std.json.Value{"); | ||||
|  | @ -903,59 +979,102 @@ fn getMemberValueBlock(allocator: std.mem.Allocator, source: []const u8, member: | |||
|     } else { | ||||
|         try writer.writeAll(member_value); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| fn getMemberValueBlock(allocator: std.mem.Allocator, source: []const u8, member: JsonMember) ![]const u8 { | ||||
|     const member_value = try std.fmt.allocPrint(allocator, "@field({s}, \"{s}\")", .{ source, member.field_name }); | ||||
|     defer allocator.free(member_value); | ||||
| 
 | ||||
|     var output_block = std.ArrayListUnmanaged(u8){}; | ||||
|     const writer = output_block.writer(allocator); | ||||
| 
 | ||||
|     try writeMemberValue( | ||||
|         allocator, | ||||
|         writer, | ||||
|         member_value, | ||||
|         member.shape_info, | ||||
|         member.type_member.traits, | ||||
|     ); | ||||
| 
 | ||||
|     return output_block.toOwnedSlice(allocator); | ||||
| } | ||||
| 
 | ||||
| fn memberToJson(shape_id: []const u8, name: []const u8, value: []const u8, state: GenerationState, writer: std.io.AnyWriter) !void { | ||||
|     const shape_info = try shapeInfoForId(shape_id, state); | ||||
| const MemberToJsonParams = struct { | ||||
|     shape_id: []const u8, | ||||
|     field_name: []const u8, | ||||
|     field_value: []const u8, | ||||
|     state: GenerationState, | ||||
|     member: smithy.TypeMember, | ||||
| }; | ||||
| 
 | ||||
| fn memberToJson(params: MemberToJsonParams, writer: std.io.AnyWriter) !void { | ||||
|     const shape_id = params.shape_id; | ||||
|     const state = params.state; | ||||
|     const value = params.field_value; | ||||
|     const name = params.field_name; | ||||
| 
 | ||||
|     const shape_info = try shapeInfoForId(shape_id, state.file_state.shapes); | ||||
|     const shape = shape_info.shape; | ||||
|     const allocator = state.allocator; | ||||
| 
 | ||||
|     if (state.getTypeRecurrenceCount(shape_id) > 2) { | ||||
|         try writer.writeAll(value); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     try state.appendToTypeStack(&shape_info); | ||||
|     defer state.popFromTypeStack(); | ||||
| 
 | ||||
|     switch (shape) { | ||||
|         .structure => { | ||||
|             const structure_name = try std.fmt.allocPrint(state.allocator, "{s}_structure_{d}", .{ name, state.indent_level }); | ||||
|         .structure, .uniontype => { | ||||
|             const shape_type = "structure"; | ||||
| 
 | ||||
|             const structure_name = try std.fmt.allocPrint(state.allocator, "{s}_{s}_{d}", .{ name, shape_type, state.indent_level }); | ||||
|             defer state.allocator.free(structure_name); | ||||
| 
 | ||||
|             try writer.print("\n// start {s}: {s}\n", .{ shape_type, structure_name }); | ||||
|             defer writer.print("// end {s}: {s}\n", .{ shape_type, structure_name }) catch std.debug.panic("Unreachable", .{}); | ||||
| 
 | ||||
|             const blk_name = try std.fmt.allocPrint(state.allocator, "{s}_blk", .{structure_name}); | ||||
|             defer state.allocator.free(blk_name); | ||||
| 
 | ||||
|             var json_members = try getJsonMembers(state.allocator, shape, state); | ||||
|             defer json_members.deinit(state.allocator); | ||||
|             if (try getJsonMembers(state.allocator, shape, state)) |json_members| { | ||||
|                 try writer.writeAll(blk_name); | ||||
|                 try writer.writeAll(": {\n"); | ||||
| 
 | ||||
|             try writer.writeAll(blk_name); | ||||
|             try writer.writeAll(": {\n"); | ||||
|                 if (json_members.items.len > 0) { | ||||
|                     try writer.print("var {s} = std.json.ObjectMap.init(allocator);\n", .{structure_name}); | ||||
| 
 | ||||
|             if (json_members.items.len > 0) { | ||||
|                 try writer.print("var {s} = std.json.ObjectMap.init(allocator);\n", .{structure_name}); | ||||
|                     for (json_members.items) |member| { | ||||
|                         const member_value = try getMemberValueBlock(allocator, value, member); | ||||
|                         defer allocator.free(member_value); | ||||
| 
 | ||||
|                 for (json_members.items) |member| { | ||||
|                     const member_value = try getMemberValueBlock(allocator, value, member); | ||||
|                     defer allocator.free(member_value); | ||||
|                         try writer.print("try {s}.put(\"{s}\", ", .{ structure_name, member.json_key }); | ||||
|                         try memberToJson( | ||||
|                             .{ | ||||
|                                 .shape_id = member.target, | ||||
|                                 .field_name = member.field_name, | ||||
|                                 .field_value = member_value, | ||||
|                                 .state = state.indent(), | ||||
|                                 .member = member.type_member, | ||||
|                             }, | ||||
|                             writer, | ||||
|                         ); | ||||
|                         try writer.writeAll(");\n"); | ||||
|                     } | ||||
| 
 | ||||
|                     try writer.print("try {s}.put(\"{s}\", ", .{ structure_name, member.json_key }); | ||||
|                     try memberToJson( | ||||
|                         member.target, | ||||
|                         member.field_name, | ||||
|                         member_value, | ||||
|                         state.indent(), | ||||
|                         writer, | ||||
|                     ); | ||||
|                     try writer.writeAll(");\n"); | ||||
|                     try writer.print("break :{s} ", .{blk_name}); | ||||
|                     try writer.writeAll(".{ .object = "); | ||||
|                     try writer.writeAll(structure_name); | ||||
|                     try writer.writeAll("};"); | ||||
|                 } else { | ||||
|                     try writer.print("break :{s} ", .{blk_name}); | ||||
|                     try writer.writeAll(".{ .null = undefined };"); | ||||
|                 } | ||||
| 
 | ||||
|                 try writer.print("break :{s} ", .{blk_name}); | ||||
|                 try writer.writeAll(".{ .object = "); | ||||
|                 try writer.writeAll(structure_name); | ||||
|                 try writer.writeAll("};"); | ||||
|             } else { | ||||
|                 try writer.print("break :{s} ", .{blk_name}); | ||||
|                 try writer.writeAll(".{ .null = undefined };"); | ||||
|                 try writer.writeAll("}\n"); | ||||
|             } | ||||
| 
 | ||||
|             try writer.writeAll("},\n"); | ||||
|         }, | ||||
|         .uniontype => std.debug.panic("Uniontype not implemented", .{}), | ||||
|         .timestamp => { | ||||
|             try writer.writeAll("try std.json.Value.jsonParse(allocator, "); | ||||
|             try writer.writeAll(value); | ||||
|  | @ -965,33 +1084,77 @@ fn memberToJson(shape_id: []const u8, name: []const u8, value: []const u8, state | |||
|             const list_name = try std.fmt.allocPrint(state.allocator, "{s}_list_{d}", .{ name, state.indent_level }); | ||||
|             defer state.allocator.free(list_name); | ||||
| 
 | ||||
|             const list_value_name = try std.fmt.allocPrint(allocator, "{s}_value", .{list_name}); | ||||
|             defer allocator.free(list_value_name); | ||||
|             try writer.print("\n// start list: {s}\n", .{list_name}); | ||||
|             defer writer.print("// end list: {s}\n", .{list_name}) catch std.debug.panic("Unreachable", .{}); | ||||
| 
 | ||||
|             const list_each_value = try std.fmt.allocPrint(allocator, "{s}_value", .{list_name}); | ||||
|             defer allocator.free(list_each_value); | ||||
| 
 | ||||
|             const list_value_name_local = try std.fmt.allocPrint(allocator, "{s}_local", .{list_each_value}); | ||||
|             defer allocator.free(list_value_name_local); | ||||
| 
 | ||||
|             const blk_name = try std.fmt.allocPrint(state.allocator, "{s}_blk", .{list_name}); | ||||
|             defer state.allocator.free(blk_name); | ||||
| 
 | ||||
|             const list_capture = try std.fmt.allocPrint(state.allocator, "{s}_capture", .{list_name}); | ||||
|             defer state.allocator.free(list_capture); | ||||
| 
 | ||||
|             try writer.writeAll(blk_name); | ||||
|             try writer.writeAll(": {\n"); | ||||
|             { | ||||
|                 try writer.print("var {s} = std.json.Array.init(allocator);\n", .{list_name}); | ||||
|                 try writer.print("const {s} = {s};\n", .{ list_value_name_local, value }); | ||||
| 
 | ||||
|                 try writer.print("for ({s}) |{s}|", .{ value, list_value_name }); | ||||
|                 const list_is_optional = shapeIsOptional(l.traits); | ||||
| 
 | ||||
|                 var list_value = list_value_name_local; | ||||
| 
 | ||||
|                 if (list_is_optional) { | ||||
|                     list_value = list_capture; | ||||
| 
 | ||||
|                     try writer.print("if ({s}) |{s}| ", .{ | ||||
|                         list_value_name_local, | ||||
|                         list_capture, | ||||
|                     }); | ||||
|                     try writer.writeAll("{\n"); | ||||
|                 } | ||||
| 
 | ||||
|                 const list_target_shape_info = try shapeInfoForId(l.member_target, state.file_state.shapes); | ||||
| 
 | ||||
|                 // start loop | ||||
|                 try writer.print("for ({s}) |{s}|", .{ list_value, list_each_value }); | ||||
|                 try writer.writeAll("{\n"); | ||||
|                 try writer.print("try {s}.append(", .{list_name}); | ||||
|                 try memberToJson(l.member_target, "value", list_value_name, state, writer); | ||||
|                 try writeMemberValue( | ||||
|                     allocator, | ||||
|                     writer, | ||||
|                     list_each_value, | ||||
|                     list_target_shape_info, | ||||
|                     @constCast(&[_]smithy.Trait{.required}), | ||||
|                 ); | ||||
|                 try writer.writeAll(");"); | ||||
|                 try writer.writeAll("}\n"); | ||||
|                 // end loop | ||||
| 
 | ||||
|                 try writer.print("break :{s} {s};", .{ blk_name, list_name }); | ||||
|                 if (list_is_optional) { | ||||
|                     try writer.writeAll("}\n"); | ||||
|                 } | ||||
| 
 | ||||
|                 try writer.print("break :{s} ", .{blk_name}); | ||||
|                 try writer.writeAll(".{ .array = "); | ||||
|                 try writer.print(" {s} ", .{list_name}); | ||||
|                 try writer.writeAll("};"); | ||||
|             } | ||||
|             try writer.writeAll("},\n"); | ||||
|             try writer.writeAll("}\n"); | ||||
|         }, | ||||
|         .set => std.debug.panic("Set not implemented", .{}), | ||||
|         .map => |m| { | ||||
|             const map_name = try std.fmt.allocPrint(state.allocator, "{s}_object_map_{d}", .{ name, state.indent_level }); | ||||
|             defer state.allocator.free(map_name); | ||||
| 
 | ||||
|             try writer.print("\n// start map: {s}\n", .{map_name}); | ||||
|             defer writer.print("// end map: {s}\n", .{map_name}) catch std.debug.panic("Unreachable", .{}); | ||||
| 
 | ||||
|             const map_value_capture = try std.fmt.allocPrint(allocator, "{s}_kvp", .{map_name}); | ||||
|             defer allocator.free(map_value_capture); | ||||
| 
 | ||||
|  | @ -1004,14 +1167,12 @@ fn memberToJson(shape_id: []const u8, name: []const u8, value: []const u8, state | |||
|             const map_value_block = try getMemberValueBlock(allocator, map_value_capture, .{ | ||||
|                 .field_name = "value", | ||||
|                 .json_key = undefined, | ||||
|                 .shape_info = try shapeInfoForId(m.value, state), | ||||
|                 .shape_info = try shapeInfoForId(m.value, state.file_state.shapes), | ||||
|                 .target = m.value, | ||||
|                 .type_member = .{ | ||||
|                     .name = undefined, | ||||
|                     .target = undefined, | ||||
|                     .traits = @constCast(&[_]smithy.Trait{ | ||||
|                         smithy.Trait{ .required = .{} }, | ||||
|                     }), | ||||
|                     .traits = @constCast(&[_]smithy.Trait{.required}), | ||||
|                 }, | ||||
|             }); | ||||
|             defer allocator.free(map_value_block); | ||||
|  | @ -1019,27 +1180,82 @@ fn memberToJson(shape_id: []const u8, name: []const u8, value: []const u8, state | |||
|             const blk_name = try std.fmt.allocPrint(state.allocator, "{s}_blk", .{map_name}); | ||||
|             defer state.allocator.free(blk_name); | ||||
| 
 | ||||
|             const map_capture = try std.fmt.allocPrint(state.allocator, "{s}_capture", .{map_name}); | ||||
| 
 | ||||
|             try writer.writeAll(blk_name); | ||||
|             try writer.writeAll(": {\n"); | ||||
|             { | ||||
|                 const map_member = params.member; | ||||
|                 const key_member = smithy.TypeMember{ | ||||
|                     .name = "key", | ||||
|                     .target = m.key, | ||||
|                     .traits = @constCast(&[_]smithy.Trait{.required}), | ||||
|                 }; | ||||
|                 const value_member = smithy.TypeMember{ | ||||
|                     .name = "value", | ||||
|                     .target = m.value, | ||||
|                     .traits = @constCast(&[_]smithy.Trait{.required}), | ||||
|                 }; | ||||
| 
 | ||||
|                 const map_is_optional = !hasTrait(.required, map_member.traits); | ||||
| 
 | ||||
|                 var map_value = value; | ||||
| 
 | ||||
|                 if (map_is_optional) { | ||||
|                     map_value = map_capture; | ||||
| 
 | ||||
|                     try writer.print("if ({s}) |{s}| ", .{ | ||||
|                         value, | ||||
|                         map_capture, | ||||
|                     }); | ||||
|                     try writer.writeAll("{\n"); | ||||
|                 } | ||||
| 
 | ||||
|                 try writer.print("var {s} = std.json.ObjectMap.init(allocator);\n", .{map_name}); | ||||
| 
 | ||||
|                 try writer.print("for ({s}) |{s}|", .{ value, map_value_capture }); | ||||
|                 // start loop | ||||
|                 try writer.print("for ({s}) |{s}|", .{ map_value, map_value_capture }); | ||||
|                 try writer.writeAll("{\n"); | ||||
|                 try writer.print("const {s} = {s};\n", .{ value_name, map_value_block }); | ||||
|                 try writer.print("const {s} = ", .{value_name}); | ||||
|                 try memberToJson(.{ | ||||
|                     .shape_id = m.value, | ||||
|                     .field_name = "value", | ||||
|                     .field_value = map_value_block, | ||||
|                     .state = state, | ||||
|                     .member = value_member, | ||||
|                 }, writer); | ||||
|                 try writer.writeAll(";\n"); | ||||
|                 try writer.print("try {s}.put(\n", .{map_name}); | ||||
|                 try memberToJson(m.key, "key", map_value_capture_key, state.indent(), writer); | ||||
|                 try memberToJson(.{ | ||||
|                     .shape_id = m.key, | ||||
|                     .field_name = "key", | ||||
|                     .field_value = map_value_capture_key, | ||||
|                     .state = state.indent(), | ||||
|                     .member = key_member, | ||||
|                 }, writer); | ||||
|                 try writer.writeAll(", "); | ||||
|                 try memberToJson(m.value, "value", value_name, state.indent(), writer); | ||||
|                 try memberToJson(.{ | ||||
|                     .shape_id = m.value, | ||||
|                     .field_name = "value", | ||||
|                     .field_value = value_name, | ||||
|                     .state = state.indent(), | ||||
|                     .member = value_member, | ||||
|                 }, writer); | ||||
|                 try writer.writeAll(");\n"); | ||||
|                 try writer.writeAll("}\n"); | ||||
|                 // end loop | ||||
| 
 | ||||
|                 try writer.print("break :{s}", .{blk_name}); | ||||
|                 try writer.writeAll(".{ .object = "); | ||||
|                 try writer.writeAll(map_name); | ||||
|                 try writer.writeAll("};\n"); | ||||
| 
 | ||||
|                 if (map_is_optional) { | ||||
|                     try writer.writeAll("}\n"); | ||||
|                     try writer.print("break :{s} .null;", .{blk_name}); | ||||
|                 } | ||||
|             } | ||||
|             try writer.writeAll("},\n"); | ||||
|             try writer.writeAll("}\n"); | ||||
|         }, | ||||
|         .string => { | ||||
|             try writer.writeAll("\n// string\n"); | ||||
|  | @ -1170,8 +1386,9 @@ fn reuseCommonType(shape: smithy.ShapeInfo, writer: anytype, state: GenerationSt | |||
|     } | ||||
|     return rc; | ||||
| } | ||||
| fn shapeInfoForId(id: []const u8, state: GenerationState) !smithy.ShapeInfo { | ||||
|     return state.file_state.shapes.get(id) orelse { | ||||
| 
 | ||||
| fn shapeInfoForId(id: []const u8, shapes: std.StringHashMap(smithy.ShapeInfo)) !smithy.ShapeInfo { | ||||
|     return shapes.get(id) orelse { | ||||
|         std.debug.print("Shape ID not found. This is most likely a bug. Shape ID: {s}\n", .{id}); | ||||
|         return error.InvalidType; | ||||
|     }; | ||||
|  | @ -1203,28 +1420,11 @@ fn generateTypeFor(shape_id: []const u8, writer: anytype, state: GenerationState | |||
|     var rc = false; | ||||
| 
 | ||||
|     // We assume it must exist | ||||
|     const shape_info = try shapeInfoForId(shape_id, state); | ||||
|     const shape_info = try shapeInfoForId(shape_id, state.file_state.shapes); | ||||
|     const shape = shape_info.shape; | ||||
| 
 | ||||
|     // Check for ourselves up the stack | ||||
|     var self_occurences: u8 = 0; | ||||
|     for (state.type_stack.items) |i| { | ||||
|         // 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 | ||||
|     const self_occurences: u8 = state.getTypeRecurrenceCount(shape_id); | ||||
|     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 | ||||
|  | @ -1242,8 +1442,10 @@ fn generateTypeFor(shape_id: []const u8, writer: anytype, state: GenerationState | |||
|         // } | ||||
|         return false; // not a map | ||||
|     } | ||||
|     try state.type_stack.append(&shape_info); | ||||
|     defer _ = state.type_stack.pop(); | ||||
| 
 | ||||
|     try state.appendToTypeStack(&shape_info); | ||||
|     defer state.popFromTypeStack(); | ||||
| 
 | ||||
|     switch (shape) { | ||||
|         .structure => { | ||||
|             if (!try reuseCommonType(shape_info, writer, state)) { | ||||
|  | @ -1499,15 +1701,8 @@ fn writeMappings(state: GenerationState, @"pub": []const u8, mapping_name: []con | |||
| } | ||||
| 
 | ||||
| fn writeOptional(traits: ?[]smithy.Trait, writer: anytype, value: ?[]const u8) !void { | ||||
|     if (traits) |ts| { | ||||
|         for (ts) |t| | ||||
|             if (t == .required) return; | ||||
|     } | ||||
| 
 | ||||
|     // not required | ||||
|     if (value) |v| { | ||||
|         _ = try writer.write(v); | ||||
|     } else _ = try writer.write("?"); | ||||
|     if (traits) |ts| if (hasTrait(.required, ts)) return; | ||||
|     try writer.writeAll(value orelse "?"); | ||||
| } | ||||
| fn camelCase(allocator: std.mem.Allocator, name: []const u8) ![]const u8 { | ||||
|     const first_letter = name[0] + ('a' - 'A'); | ||||
|  |  | |||
|  | @ -10,18 +10,20 @@ pub const DateFormat = enum { | |||
| pub const Timestamp = enum(zeit.Nanoseconds) { | ||||
|     _, | ||||
| 
 | ||||
|     pub fn jsonStringify(value: Timestamp, options: json.StringifyOptions, out_stream: anytype) !void { | ||||
|         _ = options; | ||||
| 
 | ||||
|         const instant = try zeit.instant(.{ | ||||
|     pub fn jsonStringify(value: Timestamp, jw: anytype) !void { | ||||
|         const instant = zeit.instant(.{ | ||||
|             .source = .{ | ||||
|                 .unix_nano = @intFromEnum(value), | ||||
|             }, | ||||
|         }); | ||||
|         }) catch std.debug.panic("Failed to parse timestamp to instant: {d}", .{value}); | ||||
| 
 | ||||
|         try out_stream.writeAll("\""); | ||||
|         try instant.time().gofmt(out_stream, "Mon, 02 Jan 2006 15:04:05 GMT"); | ||||
|         try out_stream.writeAll("\""); | ||||
|         const fmt = "Mon, 02 Jan 2006 15:04:05 GMT"; | ||||
|         var buf = std.mem.zeroes([fmt.len]u8); | ||||
| 
 | ||||
|         var fbs = std.io.fixedBufferStream(&buf); | ||||
|         instant.time().gofmt(fbs.writer(), fmt) catch std.debug.panic("Failed to format instant: {d}", .{instant.timestamp}); | ||||
| 
 | ||||
|         try jw.write(&buf); | ||||
|     } | ||||
| 
 | ||||
|     pub fn parse(val: []const u8) !Timestamp { | ||||
|  |  | |||
							
								
								
									
										20
									
								
								src/aws.zig
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								src/aws.zig
									
										
									
									
									
								
							|  | @ -234,7 +234,7 @@ pub fn Request(comptime request_action: anytype) type { | |||
|             defer buffer.deinit(); | ||||
|             if (Self.service_meta.aws_protocol == .rest_json_1) { | ||||
|                 if (std.mem.eql(u8, "PUT", aws_request.method) or std.mem.eql(u8, "POST", aws_request.method)) { | ||||
|                     try json.stringify(request, .{ .whitespace = .{}, .emit_null = false, .exclude_fields = al.items }, buffer.writer()); | ||||
|                     try std.json.stringify(request, .{ .whitespace = .indent_4 }, buffer.writer()); | ||||
|                 } | ||||
|             } | ||||
|             aws_request.body = buffer.items; | ||||
|  | @ -326,7 +326,7 @@ pub fn Request(comptime request_action: anytype) type { | |||
|             //       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 | ||||
|             try json.stringify(request, .{ .whitespace = .{} }, buffer.writer()); | ||||
|             try std.json.stringify(request, .{ .whitespace = .indent_4 }, buffer.writer()); | ||||
| 
 | ||||
|             var content_type: []const u8 = undefined; | ||||
|             switch (Self.service_meta.aws_protocol) { | ||||
|  | @ -1149,9 +1149,9 @@ fn buildPath( | |||
|                         defer encoded_buffer.deinit(); | ||||
|                         const replacement_writer = replacement_buffer.writer(); | ||||
|                         // std.mem.replacementSize | ||||
|                         try json.stringify( | ||||
|                         try std.json.stringify( | ||||
|                             @field(request, field.name), | ||||
|                             .{}, | ||||
|                             .{ .whitespace = .indent_4 }, | ||||
|                             replacement_writer, | ||||
|                         ); | ||||
|                         const trimmed_replacement_val = std.mem.trim(u8, replacement_buffer.items, "\""); | ||||
|  | @ -1266,7 +1266,7 @@ fn addBasicQueryArg(prefix: []const u8, key: []const u8, value: anytype, writer: | |||
|     _ = try writer.write("="); | ||||
|     var encoding_writer = uriEncodingWriter(writer); | ||||
|     var ignoring_writer = ignoringWriter(encoding_writer.writer(), '"'); | ||||
|     try json.stringify(value, .{}, ignoring_writer.writer()); | ||||
|     try std.json.stringify(value, .{}, ignoring_writer.writer()); | ||||
|     return true; | ||||
| } | ||||
| pub fn uriEncodingWriter(child_stream: anytype) UriEncodingWriter(@TypeOf(child_stream)) { | ||||
|  | @ -1385,7 +1385,7 @@ test "custom serialization for map objects" { | |||
|     tags.appendAssumeCapacity(.{ .key = "Foo", .value = "Bar" }); | ||||
|     tags.appendAssumeCapacity(.{ .key = "Baz", .value = "Qux" }); | ||||
|     const req = services.lambda.TagResourceRequest{ .resource = "hello", .tags = tags.items }; | ||||
|     try json.stringify(req, .{ .whitespace = .{} }, buffer.writer()); | ||||
|     try std.json.stringify(req, .{ .whitespace = .indent_4 }, buffer.writer()); | ||||
|     try std.testing.expectEqualStrings( | ||||
|         \\{ | ||||
|         \\    "Resource": "hello", | ||||
|  | @ -1413,7 +1413,7 @@ test "proper serialization for kms" { | |||
|         .dry_run = false, | ||||
|         .grant_tokens = &[_][]const u8{}, | ||||
|     }; | ||||
|     try json.stringify(req, .{ .whitespace = .{} }, buffer.writer()); | ||||
|     try std.json.stringify(req, .{ .whitespace = .indent_4 }, buffer.writer()); | ||||
|     try std.testing.expectEqualStrings( | ||||
|         \\{ | ||||
|         \\    "KeyId": "42", | ||||
|  | @ -1436,7 +1436,7 @@ test "proper serialization for kms" { | |||
|         .dry_run = false, | ||||
|         .grant_tokens = &[_][]const u8{}, | ||||
|     }; | ||||
|     try json.stringify(req_null, .{ .whitespace = .{} }, buffer_null.writer()); | ||||
|     try std.json.stringify(req_null, .{ .whitespace = .indent_4 }, buffer_null.writer()); | ||||
|     try std.testing.expectEqualStrings( | ||||
|         \\{ | ||||
|         \\    "KeyId": "42", | ||||
|  | @ -1527,7 +1527,7 @@ test "basic json request serialization" { | |||
|     //       for a boxed member with no observable difference." But we're | ||||
|     //       seeing a lot of differences here between spec and reality | ||||
|     // | ||||
|     try json.stringify(request, .{ .whitespace = .{} }, buffer.writer()); | ||||
|     try std.json.stringify(request, .{ .whitespace = .indent_4 }, buffer.writer()); | ||||
|     try std.testing.expectEqualStrings( | ||||
|         \\{ | ||||
|         \\    "ExclusiveStartTableName": null, | ||||
|  | @ -2018,7 +2018,7 @@ test "json_1_0_query_with_input: dynamodb listTables runtime" { | |||
|     }); | ||||
|     defer test_harness.deinit(); | ||||
|     const options = try test_harness.start(); | ||||
|     const dynamo_db = (Services(.{.dynamo_db}){}).dynamo_db; | ||||
|     const dynamo_db = services.dynamo_db; | ||||
|     const call = try test_harness.client.call(dynamo_db.list_tables.Request{ | ||||
|         .limit = 1, | ||||
|     }, options); | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue