Compare commits
	
		
			3 commits
		
	
	
		
			28698e8ec4
			...
			f4c306a2df
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f4c306a2df | |||
| 52f99bb35f | |||
| 0a21a9f184 | 
					 2 changed files with 222 additions and 10 deletions
				
			
		
							
								
								
									
										84
									
								
								src/aws.zig
									
										
									
									
									
								
							
							
						
						
									
										84
									
								
								src/aws.zig
									
										
									
									
									
								
							|  | @ -236,6 +236,8 @@ pub fn Request(comptime request_action: anytype) type { | |||
|                 } | ||||
|             } | ||||
|             aws_request.body = buffer.items; | ||||
|             var rest_xml_body: ?[]const u8 = null; | ||||
|             defer if (rest_xml_body) |b| options.client.allocator.free(b); | ||||
|             if (Self.service_meta.aws_protocol == .rest_xml) { | ||||
|                 if (std.mem.eql(u8, "PUT", aws_request.method) or std.mem.eql(u8, "POST", aws_request.method)) { | ||||
|                     if (@hasDecl(ActionRequest, "http_payload")) { | ||||
|  | @ -243,7 +245,49 @@ pub fn Request(comptime request_action: anytype) type { | |||
|                         // the http_payload declaration on the request type. | ||||
|                         // Hopefully these will always be ?[]const u8, otherwise | ||||
|                         // we should see a compile error on this line | ||||
|                         aws_request.body = @field(request, ActionRequest.http_payload).?; | ||||
|                         const payload = @field(request, ActionRequest.http_payload); | ||||
|                         const T = @TypeOf(payload); | ||||
|                         var body_assigned = false; | ||||
|                         if (T == ?[]const u8) { | ||||
|                             aws_request.body = payload.?; | ||||
|                             body_assigned = true; | ||||
|                         } | ||||
|                         if (T == []const u8) { | ||||
|                             aws_request.body = payload; | ||||
|                             body_assigned = true; | ||||
|                         } | ||||
| 
 | ||||
|                         if (!body_assigned) { | ||||
|                             const sm = ActionRequest.metaInfo().service_metadata; | ||||
|                             if (!std.mem.eql(u8, sm.endpoint_prefix, "s3")) | ||||
|                                 // Because the attributes below are most likely only | ||||
|                                 // applicable to s3, we are better off to fail | ||||
|                                 // early. This portion of the code base should | ||||
|                                 // only be executed for s3 as no other known | ||||
|                                 // service uses this protocol | ||||
|                                 return error.NotImplemented; | ||||
| 
 | ||||
|                             const attrs = try std.fmt.allocPrint( | ||||
|                                 options.client.allocator, | ||||
|                                 "xmlns=\"http://{s}.amazonaws.com/doc/{s}/\"", | ||||
|                                 .{ sm.endpoint_prefix, sm.version }, | ||||
|                             ); | ||||
|                             defer options.client.allocator.free(attrs); // once serialized, the value should be copied over | ||||
| 
 | ||||
|                             // Need to serialize this | ||||
|                             rest_xml_body = try xml_serializer.stringifyAlloc( | ||||
|                                 options.client.allocator, | ||||
|                                 payload, | ||||
|                                 .{ | ||||
|                                     .whitespace = .indent_2, | ||||
|                                     .root_name = request.fieldNameFor(ActionRequest.http_payload), | ||||
|                                     .root_attributes = attrs, | ||||
|                                     .emit_null_optional_fields = false, | ||||
|                                     .include_declaration = false, | ||||
|                                 }, | ||||
|                             ); | ||||
|                             aws_request.body = rest_xml_body.?; | ||||
|                         } | ||||
|                     } else { | ||||
|                         return error.NotImplemented; | ||||
|                     } | ||||
|  | @ -2259,6 +2303,44 @@ test "ec2_query_with_input: EC2 describe instances" { | |||
|     try std.testing.expectEqualStrings("i-0212d7d1f62b96676", call.response.reservations.?[1].instances.?[0].instance_id.?); | ||||
|     try std.testing.expectEqualStrings("123456789012:found-me", call.response.reservations.?[1].instances.?[0].tags.?[0].value.?); | ||||
| } | ||||
| test "rest_xml_with_input_s3: S3 create bucket" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     var test_harness = TestSetup.init(.{ | ||||
|         .allocator = allocator, | ||||
|         .server_response = | ||||
|         \\ | ||||
|         , | ||||
|         .server_response_headers = &.{ // I don't see content type coming back in actual S3 requests | ||||
|             .{ .name = "x-amzn-RequestId", .value = "9PEYBAZ9J7TPRX43" }, | ||||
|             .{ .name = "x-amz-id-2", .value = "u7lzgW0tIyRP15vSUsVOXxJ37OfVCO8lZmLIVuqeq5EE4tNp9qebb5fy+/kendlZpR4YQE+y4Xg=" }, | ||||
|         }, | ||||
|     }); | ||||
|     defer test_harness.deinit(); | ||||
|     errdefer test_harness.creds.deinit(); | ||||
|     const options = try test_harness.start(); | ||||
|     const s3 = (Services(.{.s3}){}).s3; | ||||
|     const call = try test_harness.client.call(s3.create_bucket.Request{ | ||||
|         .bucket = "", | ||||
|         .create_bucket_configuration = .{ | ||||
|             .location_constraint = "us-west-2", | ||||
|         }, | ||||
|     }, options); | ||||
|     defer call.deinit(); | ||||
|     test_harness.stop(); | ||||
|     // Request expectations | ||||
|     try std.testing.expectEqual(std.http.Method.PUT, test_harness.request_options.request_method); | ||||
|     try std.testing.expectEqualStrings("/", test_harness.request_options.request_target); | ||||
|     try std.testing.expectEqualStrings( | ||||
|         \\<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> | ||||
|         \\  <LocationConstraint>us-west-2</LocationConstraint> | ||||
|         \\</CreateBucketConfiguration> | ||||
|     , test_harness.request_options.request_body); | ||||
|     // Response expectations | ||||
|     try std.testing.expectEqualStrings( | ||||
|         "9PEYBAZ9J7TPRX43, host_id: u7lzgW0tIyRP15vSUsVOXxJ37OfVCO8lZmLIVuqeq5EE4tNp9qebb5fy+/kendlZpR4YQE+y4Xg=", | ||||
|         call.response_metadata.request_id, | ||||
|     ); | ||||
| } | ||||
| test "rest_xml_no_input: S3 list buckets" { | ||||
|     const allocator = std.testing.allocator; | ||||
|     var test_harness = TestSetup.init(.{ | ||||
|  |  | |||
|  | @ -20,6 +20,9 @@ pub const StringifyOptions = struct { | |||
|     /// Root element name to use when serializing a value that doesn't have a natural name | ||||
|     root_name: ?[]const u8 = "root", | ||||
| 
 | ||||
|     /// Root attributes (e.g. xmlns="...") that will be added to the root element node only | ||||
|     root_attributes: []const u8 = "", | ||||
| 
 | ||||
|     /// Function to determine the element name for an array item based on the element | ||||
|     /// name of the array containing the elements. See arrayElementPluralToSingluarTransformation | ||||
|     /// and arrayElementNoopTransformation functions for examples | ||||
|  | @ -58,7 +61,10 @@ pub fn stringify( | |||
| 
 | ||||
|     // Start serialization with the root element | ||||
|     const root_name = options.root_name; | ||||
|     try serializeValue(value, root_name, options, writer.any(), 0); | ||||
|     if (@typeInfo(@TypeOf(value)) != .optional or value == null) | ||||
|         try serializeValue(value, root_name, options, writer.any(), 0) | ||||
|     else | ||||
|         try serializeValue(value.?, root_name, options, writer.any(), 0); | ||||
| } | ||||
| 
 | ||||
| /// Serializes a value to XML and returns an allocated string | ||||
|  | @ -84,18 +90,21 @@ fn serializeValue( | |||
| ) !void { | ||||
|     const T = @TypeOf(value); | ||||
| 
 | ||||
|     try writeIndent(writer, depth, options.whitespace); | ||||
|     // const output_indent = !(!options.emit_null_optional_fields and @typeInfo(@TypeOf(value)) == .optional and value == null); | ||||
|     const output_indent = options.emit_null_optional_fields or @typeInfo(@TypeOf(value)) != .optional or value != null; | ||||
| 
 | ||||
|     if (output_indent and element_name != null) | ||||
|         try writeIndent(writer, depth, options.whitespace); | ||||
| 
 | ||||
|     // const write_outer_element = | ||||
|     //     @typeInfo(T) != .optional or | ||||
|     //     options.emit_strings_as_arrays == false or | ||||
|     //     (@typeInfo(T) == .optional and element_name != null) or | ||||
|     //     (options.emit_strings_as_arrays and (@typeInfo(T) != .array or @typeInfo(T).array.child != u8)); | ||||
|     // Start element tag | ||||
|     if (@typeInfo(T) != .optional and @typeInfo(T) != .array) { | ||||
|         if (element_name) |n| { | ||||
|             try writer.writeAll("<"); | ||||
|             try writer.writeAll(n); | ||||
|             if (depth == 0 and options.root_attributes.len > 0) { | ||||
|                 try writer.writeByte(' '); | ||||
|                 try writer.writeAll(options.root_attributes); | ||||
|             } | ||||
|             try writer.writeAll(">"); | ||||
|         } | ||||
|     } | ||||
|  | @ -197,8 +206,9 @@ fn serializeValue( | |||
|                     else | ||||
|                         field.name; // TODO: field mapping | ||||
| 
 | ||||
|                 const field_value = @field(value, field.name); | ||||
|                 try serializeValue( | ||||
|                     @field(value, field.name), | ||||
|                     field_value, | ||||
|                     field_name, | ||||
|                     options, | ||||
|                     writer, | ||||
|  | @ -206,7 +216,11 @@ fn serializeValue( | |||
|                 ); | ||||
| 
 | ||||
|                 if (options.whitespace != .minified) { | ||||
|                     try writer.writeByte('\n'); | ||||
|                     if (!options.emit_null_optional_fields and @typeInfo(@TypeOf(field_value)) == .optional and field_value == null) { | ||||
|                         // Skip writing anything | ||||
|                     } else { | ||||
|                         try writer.writeByte('\n'); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|  | @ -661,3 +675,119 @@ test "structs with custom field names" { | |||
|         , result); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| test "structs with optional values" { | ||||
|     const testing = std.testing; | ||||
|     const allocator = testing.allocator; | ||||
| 
 | ||||
|     const Person = struct { | ||||
|         first_name: []const u8, | ||||
|         middle_name: ?[]const u8 = null, | ||||
|         last_name: []const u8, | ||||
|     }; | ||||
| 
 | ||||
|     const person = Person{ | ||||
|         .first_name = "John", | ||||
|         .last_name = "Doe", | ||||
|     }; | ||||
| 
 | ||||
|     { | ||||
|         const result = try stringifyAlloc( | ||||
|             allocator, | ||||
|             person, | ||||
|             .{ | ||||
|                 .whitespace = .indent_2, | ||||
|                 .emit_null_optional_fields = false, | ||||
|                 .root_attributes = "xmlns=\"http://example.com/blah/xxxx/\"", | ||||
|             }, | ||||
|         ); | ||||
|         defer allocator.free(result); | ||||
|         try testing.expectEqualStrings( | ||||
|             \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|             \\<root xmlns="http://example.com/blah/xxxx/"> | ||||
|             \\  <first_name>John</first_name> | ||||
|             \\  <last_name>Doe</last_name> | ||||
|             \\</root> | ||||
|         , result); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| test "optional structs with value" { | ||||
|     const testing = std.testing; | ||||
|     const allocator = testing.allocator; | ||||
| 
 | ||||
|     const Person = struct { | ||||
|         first_name: []const u8, | ||||
|         middle_name: ?[]const u8 = null, | ||||
|         last_name: []const u8, | ||||
|     }; | ||||
| 
 | ||||
|     const person: ?Person = Person{ | ||||
|         .first_name = "John", | ||||
|         .last_name = "Doe", | ||||
|     }; | ||||
| 
 | ||||
|     { | ||||
|         const result = try stringifyAlloc( | ||||
|             allocator, | ||||
|             person, | ||||
|             .{ | ||||
|                 .whitespace = .indent_2, | ||||
|                 .emit_null_optional_fields = false, | ||||
|                 .root_attributes = "xmlns=\"http://example.com/blah/xxxx/\"", | ||||
|             }, | ||||
|         ); | ||||
|         defer allocator.free(result); | ||||
|         try testing.expectEqualStrings( | ||||
|             \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|             \\<root xmlns="http://example.com/blah/xxxx/"> | ||||
|             \\  <first_name>John</first_name> | ||||
|             \\  <last_name>Doe</last_name> | ||||
|             \\</root> | ||||
|         , result); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| test "nested optional structs with value" { | ||||
|     const testing = std.testing; | ||||
|     const allocator = testing.allocator; | ||||
| 
 | ||||
|     const Name = struct { | ||||
|         first_name: []const u8, | ||||
|         middle_name: ?[]const u8 = null, | ||||
|         last_name: []const u8, | ||||
|     }; | ||||
| 
 | ||||
|     const Person = struct { | ||||
|         name: ?Name, | ||||
|     }; | ||||
| 
 | ||||
|     const person: ?Person = Person{ | ||||
|         .name = .{ | ||||
|             .first_name = "John", | ||||
|             .last_name = "Doe", | ||||
|         }, | ||||
|     }; | ||||
| 
 | ||||
|     { | ||||
|         const result = try stringifyAlloc( | ||||
|             allocator, | ||||
|             person, | ||||
|             .{ | ||||
|                 .whitespace = .indent_2, | ||||
|                 .emit_null_optional_fields = false, | ||||
|                 .root_attributes = "xmlns=\"http://example.com/blah/xxxx/\"", | ||||
|             }, | ||||
|         ); | ||||
|         defer allocator.free(result); | ||||
|         try testing.expectEqualStrings( | ||||
|             \\<?xml version="1.0" encoding="UTF-8"?> | ||||
|             \\<root xmlns="http://example.com/blah/xxxx/"> | ||||
|             \\  <name> | ||||
|             \\    <first_name>John</first_name> | ||||
|             \\    <last_name>Doe</last_name> | ||||
|             \\  </name> | ||||
|             \\</root> | ||||
|         , result); | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue