diff --git a/README.md b/README.md index b108750..88b4309 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,9 @@ [![Build Status](https://drone.lerch.org/api/badges/lobo/aws-sdk-for-zig/status.svg?ref=refs/heads/master)](https://drone.lerch.org/api/badges/lobo/aws-sdk-for-zig/) This SDK currently supports all AWS services except EC2 and S3. These two -services only support XML, and zig 0.9.0 and master both trigger compile -errors while incorporating the XML parser in conjunction with a process -to fill the types. S3 also requires some plumbing tweaks in the signature -calculation. Examples of usage are in src/main.zig. +services only support XML, and more work is needed to parse and integrate +type hydration from the base parsing. S3 also requires some plumbing tweaks +in the signature calculation. Examples of usage are in src/main.zig. Current executable size for the demo is 953k (90k of which is the AWS PEM file) after compiling with -Drelease-safe and @@ -53,11 +52,7 @@ implemented. TODO List: -* To work around compiler issues, the best option may be to convert from - Xml to json, then parse from there. This will be pursued first. It may need - to wait for zig 0.10.0 when self-hosted compiler is likely to be completed - (zig 0.10.0 eta May 2022) discovered. If we need to wait, S3, EC2 and other - restXml protocols will be blocked. +* Complete integration of Xml responses with remaining code base * Implement [AWS restXml protocol](https://awslabs.github.io/smithy/1.0/spec/aws/aws-restxml-protocol.html). Includes S3. Total service count 4. This may be blocked due to the same issue as EC2. * Implement [AWS EC2 query protocol](https://awslabs.github.io/smithy/1.0/spec/aws/aws-ec2-query-protocol.html). diff --git a/src/xml_shaper.zig b/src/xml_shaper.zig index 30d76d4..a850275 100644 --- a/src/xml_shaper.zig +++ b/src/xml_shaper.zig @@ -1,6 +1,8 @@ const std = @import("std"); const xml = @import("xml.zig"); +const log = std.log.scoped(.xml_shaper); + fn Parsed(comptime T: type) type { return struct { allocator: std.mem.Allocator, @@ -42,6 +44,8 @@ fn Parsed(comptime T: type) type { .Many => {}, .C => {}, .Slice => { + for (obj) |child| + deinitObject(allocator, child); allocator.free(obj); }, } @@ -175,11 +179,13 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions) // return error.MoreElementsThanFields; // } + log.debug("Processing fields in struct: {s}", .{@typeName(T)}); inline for (struct_info.fields) |field, i| { var name = field.name; + var found_value = false; if (comptime std.meta.trait.hasFn("fieldNameFor")(T)) name = r.fieldNameFor(field.name); - std.log.debug("Field name: {s}, Element: {s}, Adjusted field name: {s}\n", .{ field.name, element.tag, name }); + log.debug("Field name: {s}, Element: {s}, Adjusted field name: {s}", .{ field.name, element.tag, name }); var iterator = element.findChildrenByTag(name); if (options.match_predicate) |predicate| { iterator.predicate = predicate; @@ -193,14 +199,17 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions) // return error.UnexpectedValue; // } // } else { + log.debug("Found child element {s}", .{child.tag}); @field(r, field.name) = try parseInternal(field.field_type, child, options); fields_seen[i] = true; fields_set = fields_set + 1; - // } - - } else { - return error.NoValueForField; + found_value = true; } + // Using this else clause breaks zig, so we'll use a boolean instead + if (!found_value) return error.NoValueForField; + // } else { + // return error.NoValueForField; + // } } if (fields_set != struct_info.fields.len) return error.FieldElementMismatch; // see fields_seen for details @@ -259,14 +268,23 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions) // bar // if (ptr_info.child != u8) { - std.log.debug("ptr_info.child == {s}", .{@typeName(ptr_info.child)}); - const children = try allocator.alloc(ptr_info.child, element.children.items.len); - var inx: usize = 0; - while (inx < children.len) { - children[inx] = try parseInternal(ptr_info.child, element.children.items[inx].Element, options); - inx += 1; + log.debug("type = {s}, ptr_info.child == {s}, element = {s}", .{ @typeName(T), @typeName(ptr_info.child), element.tag }); + var iterator = element.elements(); + var children = std.ArrayList(ptr_info.child).init(allocator); + defer children.deinit(); + while (iterator.next()) |child_element| { + try children.append(try parseInternal(ptr_info.child, child_element, options)); } - return children; + return children.toOwnedSlice(); + // var inx: usize = 0; + // while (inx < children.len) { + // switch (element.children.items[inx]) { + // .Element => children[inx] = try parseInternal(ptr_info.child, element.children.items[inx].Element, options), + // .CharData => children[inx] = try allocator.dupe(u8, element.children.items[inx].CharData), + // .Comment => children[inx] = try allocator.dupe(u8, element.children.items[inx].Comment), // This might be an error... + // } + // inx += 1; + // } } return try allocator.dupe(u8, element.children.items[0].CharData); }, @@ -390,13 +408,32 @@ test "can parse an optional boolean type" { \\ true \\ ; - const Example = struct { + const ExampleDoesNotMatter = struct { foo_bar: ?bool = null, }; - const parsed_data = try parse(Example, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); + const parsed_data = try parse(ExampleDoesNotMatter, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); defer parsed_data.deinit(); try testing.expectEqual(@as(?bool, true), parsed_data.parsed_value.foo_bar); } + +// This is the simplest test so far that breaks zig +test "can parse a boolean type (two fields)" { + const allocator = std.testing.allocator; + const data = + \\ + \\ + \\ true + \\ true + \\ + ; + const ExampleDoesNotMatter = struct { + foo_bar: bool, + foo_baz: bool, + }; + const parsed_data = try parse(ExampleDoesNotMatter, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); + defer parsed_data.deinit(); + try testing.expectEqual(@as(bool, true), parsed_data.parsed_value.foo_bar); +} test "can parse a nested type" { const allocator = std.testing.allocator; const data = @@ -416,6 +453,28 @@ test "can parse a nested type" { defer parsed_data.deinit(); try testing.expectEqualStrings("baz", parsed_data.parsed_value.foo.bar); } +test "can parse a nested type - two fields" { + const allocator = std.testing.allocator; + const data = + \\ + \\ + \\ + \\ baz + \\ baz + \\ + \\ + ; + const Example = struct { + foo: struct { + bar: []const u8, + qux: []const u8, + }, + }; + const parsed_data = try parse(Example, data, .{ .allocator = allocator, .match_predicate = fuzzyEqual }); + defer parsed_data.deinit(); + try testing.expectEqualStrings("baz", parsed_data.parsed_value.foo.bar); + try testing.expectEqualStrings("baz", parsed_data.parsed_value.foo.qux); +} const service_metadata: struct { version: []const u8 = "2016-11-15", @@ -450,9 +509,11 @@ const describe_regions: struct { }, Response: type = struct { regions: ?[]struct { + // Having two of these causes the zig compiler bug + // Only one of them works fine. This leads me to believe that + // it has something to do with the inline for endpoint: ?[]const u8 = null, region_name: ?[]const u8 = null, - opt_in_status: ?[]const u8 = null, pub fn fieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 { const mappings = .{ @@ -473,35 +534,31 @@ const describe_regions: struct { }, } = .{}; -// This test results in "broken LLVM module found: Operand is null" -// br i1 %120, label %ErrRetReturn12, , !dbg !10637 -// -// This is a bug in the Zig compiler. -// test "can parse something serious" { -// std.testing.log_level = .debug; -// std.log.debug("", .{}); -// -// const allocator = std.testing.allocator; -// const data = -// \\ -// \\ -// \\ 8d6bfc99-978b-4146-ba23-2e5fe5b65406 -// \\ -// \\ -// \\ eu-north-1 -// \\ ec2.eu-north-1.amazonaws.com -// \\ opt-in-not-required -// \\ -// \\ -// \\ ap-south-1 -// \\ ec2.ap-south-1.amazonaws.com -// \\ opt-in-not-required -// \\ -// \\ -// \\ -// ; -// const parsed_data = try parse(describe_regions.Response, data, .{ .allocator = allocator }); -// defer parsed_data.deinit(); -// try testing.expect(parsed_data.parsed_value.regions != null); -// // try testing.expectEqualStrings("eu-north-1", parsed_data.parsed_value.regions.?[0].region_name.?); -// } +test "can parse something serious" { + // std.testing.log_level = .debug; + log.debug("", .{}); + + const allocator = std.testing.allocator; + const data = + \\ + \\ + \\ 8d6bfc99-978b-4146-ba23-2e5fe5b65406 + \\ + \\ + \\ eu-north-1 + \\ ec2.eu-north-1.amazonaws.com + \\ + \\ + \\ ap-south-1 + \\ ec2.ap-south-1.amazonaws.com + \\ + \\ + \\ + ; + // const ServerResponse = struct { DescribeRegionsResponse: describe_regions.Response, }; + const parsed_data = try parse(describe_regions.Response, data, .{ .allocator = allocator }); + defer parsed_data.deinit(); + try testing.expect(parsed_data.parsed_value.regions != null); + try testing.expectEqualStrings("eu-north-1", parsed_data.parsed_value.regions.?[0].region_name.?); + try testing.expectEqualStrings("ec2.eu-north-1.amazonaws.com", parsed_data.parsed_value.regions.?[0].endpoint.?); +}