work around zig compiler bug
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Emil Lerch 2022-02-11 13:36:38 -08:00
parent 8e9b85b35f
commit 0706dd5e6f
Signed by: lobo
GPG Key ID: A7B62D657EF764F8
2 changed files with 108 additions and 56 deletions

View File

@ -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/) [![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 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 services only support XML, and more work is needed to parse and integrate
errors while incorporating the XML parser in conjunction with a process type hydration from the base parsing. S3 also requires some plumbing tweaks
to fill the types. S3 also requires some plumbing tweaks in the signature in the signature calculation. Examples of usage are in src/main.zig.
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) Current executable size for the demo is 953k (90k of which is the AWS PEM file)
after compiling with -Drelease-safe and after compiling with -Drelease-safe and
@ -53,11 +52,7 @@ implemented.
TODO List: TODO List:
* To work around compiler issues, the best option may be to convert from * Complete integration of Xml responses with remaining code base
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.
* Implement [AWS restXml protocol](https://awslabs.github.io/smithy/1.0/spec/aws/aws-restxml-protocol.html). * 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. 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). * Implement [AWS EC2 query protocol](https://awslabs.github.io/smithy/1.0/spec/aws/aws-ec2-query-protocol.html).

View File

@ -1,6 +1,8 @@
const std = @import("std"); const std = @import("std");
const xml = @import("xml.zig"); const xml = @import("xml.zig");
const log = std.log.scoped(.xml_shaper);
fn Parsed(comptime T: type) type { fn Parsed(comptime T: type) type {
return struct { return struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -42,6 +44,8 @@ fn Parsed(comptime T: type) type {
.Many => {}, .Many => {},
.C => {}, .C => {},
.Slice => { .Slice => {
for (obj) |child|
deinitObject(allocator, child);
allocator.free(obj); allocator.free(obj);
}, },
} }
@ -175,11 +179,13 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions)
// return error.MoreElementsThanFields; // return error.MoreElementsThanFields;
// } // }
log.debug("Processing fields in struct: {s}", .{@typeName(T)});
inline for (struct_info.fields) |field, i| { inline for (struct_info.fields) |field, i| {
var name = field.name; var name = field.name;
var found_value = false;
if (comptime std.meta.trait.hasFn("fieldNameFor")(T)) if (comptime std.meta.trait.hasFn("fieldNameFor")(T))
name = r.fieldNameFor(field.name); 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); var iterator = element.findChildrenByTag(name);
if (options.match_predicate) |predicate| { if (options.match_predicate) |predicate| {
iterator.predicate = predicate; iterator.predicate = predicate;
@ -193,14 +199,17 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions)
// return error.UnexpectedValue; // return error.UnexpectedValue;
// } // }
// } else { // } else {
log.debug("Found child element {s}", .{child.tag});
@field(r, field.name) = try parseInternal(field.field_type, child, options); @field(r, field.name) = try parseInternal(field.field_type, child, options);
fields_seen[i] = true; fields_seen[i] = true;
fields_set = fields_set + 1; fields_set = fields_set + 1;
// } found_value = true;
} else {
return error.NoValueForField;
} }
// 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) if (fields_set != struct_info.fields.len)
return error.FieldElementMismatch; // see fields_seen for details return error.FieldElementMismatch; // see fields_seen for details
@ -259,14 +268,23 @@ fn parseInternal(comptime T: type, element: *xml.Element, options: ParseOptions)
// <Item>bar</Item> // <Item>bar</Item>
// <Items> // <Items>
if (ptr_info.child != u8) { if (ptr_info.child != u8) {
std.log.debug("ptr_info.child == {s}", .{@typeName(ptr_info.child)}); log.debug("type = {s}, ptr_info.child == {s}, element = {s}", .{ @typeName(T), @typeName(ptr_info.child), element.tag });
const children = try allocator.alloc(ptr_info.child, element.children.items.len); var iterator = element.elements();
var inx: usize = 0; var children = std.ArrayList(ptr_info.child).init(allocator);
while (inx < children.len) { defer children.deinit();
children[inx] = try parseInternal(ptr_info.child, element.children.items[inx].Element, options); while (iterator.next()) |child_element| {
inx += 1; 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); return try allocator.dupe(u8, element.children.items[0].CharData);
}, },
@ -390,13 +408,32 @@ test "can parse an optional boolean type" {
\\ <fooBar>true</fooBar> \\ <fooBar>true</fooBar>
\\</Example> \\</Example>
; ;
const Example = struct { const ExampleDoesNotMatter = struct {
foo_bar: ?bool = null, 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(); defer parsed_data.deinit();
try testing.expectEqual(@as(?bool, true), parsed_data.parsed_value.foo_bar); 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 =
\\<?xml version="1.0" encoding="UTF-8"?>
\\<Example xmlns="http://example.example.com/doc/2016-11-15/">
\\ <fooBar>true</fooBar>
\\ <fooBaz>true</fooBaz>
\\</Example>
;
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" { test "can parse a nested type" {
const allocator = std.testing.allocator; const allocator = std.testing.allocator;
const data = const data =
@ -416,6 +453,28 @@ test "can parse a nested type" {
defer parsed_data.deinit(); defer parsed_data.deinit();
try testing.expectEqualStrings("baz", parsed_data.parsed_value.foo.bar); 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 =
\\<?xml version="1.0" encoding="UTF-8"?>
\\<Example xmlns="http://example.example.com/doc/2016-11-15/">
\\ <foo>
\\ <bar>baz</bar>
\\ <qux>baz</qux>
\\ </foo>
\\</Example>
;
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 { const service_metadata: struct {
version: []const u8 = "2016-11-15", version: []const u8 = "2016-11-15",
@ -450,9 +509,11 @@ const describe_regions: struct {
}, },
Response: type = struct { Response: type = struct {
regions: ?[]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, endpoint: ?[]const u8 = null,
region_name: ?[]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 { pub fn fieldNameFor(_: @This(), comptime field_name: []const u8) []const u8 {
const mappings = .{ const mappings = .{
@ -473,35 +534,31 @@ const describe_regions: struct {
}, },
} = .{}; } = .{};
// This test results in "broken LLVM module found: Operand is null" test "can parse something serious" {
// br i1 %120, label %ErrRetReturn12, <null operand!>, !dbg !10637
//
// This is a bug in the Zig compiler.
// test "can parse something serious" {
// std.testing.log_level = .debug; // std.testing.log_level = .debug;
// std.log.debug("", .{}); log.debug("", .{});
//
// const allocator = std.testing.allocator; const allocator = std.testing.allocator;
// const data = const data =
// \\<?xml version="1.0" encoding="UTF-8"?> \\<?xml version="1.0" encoding="UTF-8"?>
// \\<DescribeRegionsResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/"> \\<DescribeRegionsResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
// \\ <requestId>8d6bfc99-978b-4146-ba23-2e5fe5b65406</requestId> \\ <requestId>8d6bfc99-978b-4146-ba23-2e5fe5b65406</requestId>
// \\ <regionInfo> \\ <regionInfo>
// \\ <item> \\ <item>
// \\ <regionName>eu-north-1</regionName> \\ <regionName>eu-north-1</regionName>
// \\ <regionEndpoint>ec2.eu-north-1.amazonaws.com</regionEndpoint> \\ <regionEndpoint>ec2.eu-north-1.amazonaws.com</regionEndpoint>
// \\ <optInStatus>opt-in-not-required</optInStatus> \\ </item>
// \\ </item> \\ <item>
// \\ <item> \\ <regionName>ap-south-1</regionName>
// \\ <regionName>ap-south-1</regionName> \\ <regionEndpoint>ec2.ap-south-1.amazonaws.com</regionEndpoint>
// \\ <regionEndpoint>ec2.ap-south-1.amazonaws.com</regionEndpoint> \\ </item>
// \\ <optInStatus>opt-in-not-required</optInStatus> \\ </regionInfo>
// \\ </item> \\</DescribeRegionsResponse>
// \\ </regionInfo> ;
// \\</DescribeRegionsResponse> // const ServerResponse = struct { DescribeRegionsResponse: describe_regions.Response, };
// ; const parsed_data = try parse(describe_regions.Response, data, .{ .allocator = allocator });
// const parsed_data = try parse(describe_regions.Response, data, .{ .allocator = allocator }); defer parsed_data.deinit();
// defer parsed_data.deinit(); try testing.expect(parsed_data.parsed_value.regions != null);
// 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("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.?);
// } }