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/)
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).

View File

@ -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)
// <Item>bar</Item>
// <Items>
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" {
\\ <fooBar>true</fooBar>
\\</Example>
;
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 =
\\<?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" {
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 =
\\<?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 {
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, <null operand!>, !dbg !10637
//
// This is a bug in the Zig compiler.
// test "can parse something serious" {
test "can parse something serious" {
// std.testing.log_level = .debug;
// std.log.debug("", .{});
//
// const allocator = std.testing.allocator;
// const data =
// \\<?xml version="1.0" encoding="UTF-8"?>
// \\<DescribeRegionsResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
// \\ <requestId>8d6bfc99-978b-4146-ba23-2e5fe5b65406</requestId>
// \\ <regionInfo>
// \\ <item>
// \\ <regionName>eu-north-1</regionName>
// \\ <regionEndpoint>ec2.eu-north-1.amazonaws.com</regionEndpoint>
// \\ <optInStatus>opt-in-not-required</optInStatus>
// \\ </item>
// \\ <item>
// \\ <regionName>ap-south-1</regionName>
// \\ <regionEndpoint>ec2.ap-south-1.amazonaws.com</regionEndpoint>
// \\ <optInStatus>opt-in-not-required</optInStatus>
// \\ </item>
// \\ </regionInfo>
// \\</DescribeRegionsResponse>
// ;
// 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.?);
// }
log.debug("", .{});
const allocator = std.testing.allocator;
const data =
\\<?xml version="1.0" encoding="UTF-8"?>
\\<DescribeRegionsResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
\\ <requestId>8d6bfc99-978b-4146-ba23-2e5fe5b65406</requestId>
\\ <regionInfo>
\\ <item>
\\ <regionName>eu-north-1</regionName>
\\ <regionEndpoint>ec2.eu-north-1.amazonaws.com</regionEndpoint>
\\ </item>
\\ <item>
\\ <regionName>ap-south-1</regionName>
\\ <regionEndpoint>ec2.ap-south-1.amazonaws.com</regionEndpoint>
\\ </item>
\\ </regionInfo>
\\</DescribeRegionsResponse>
;
// 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.?);
}