diff --git a/src/aws.zig b/src/aws.zig
index a3da71c..33a0b16 100644
--- a/src/aws.zig
+++ b/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")) {
@@ -255,8 +257,37 @@ pub fn Request(comptime request_action: anytype) type {
body_assigned = true;
}
- if (!body_assigned)
- return error.XmlSerializationNotImplemented;
+ 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;
}
@@ -2305,7 +2336,10 @@ test "rest_xml_with_input_s3: S3 create bucket" {
\\
, test_harness.request_options.request_body);
// Response expectations
- try std.testing.expectEqualStrings("9PEYBAZ9J7TPRX43", call.response_metadata.request_id);
+ 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;
diff --git a/src/xml_serializer.zig b/src/xml_serializer.zig
index 23b2f59..91ba069 100644
--- a/src/xml_serializer.zig
+++ b/src/xml_serializer.zig
@@ -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(
+ \\
+ \\
+ \\ John
+ \\ Doe
+ \\
+ , 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(
+ \\
+ \\
+ \\ John
+ \\ Doe
+ \\
+ , 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(
+ \\
+ \\
+ \\
+ \\ John
+ \\ Doe
+ \\
+ \\
+ , result);
+ }
+}