initial code based on zig 0.9.0
This commit is contained in:
parent
1a29a532c9
commit
9a2ce42bbf
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2023 Emil Lerch
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
8
README.md
Normal file
8
README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
AWS Smithy Model for Zig
|
||||||
|
========================
|
||||||
|
|
||||||
|
This holds a smithy project for zig, extracted from the AWS SDK for Zig for
|
||||||
|
use as a package. Built for zig 0.11
|
||||||
|
|
||||||
|
TODO: complete the readme
|
||||||
|
TODO: CI
|
17
build.zig
Normal file
17
build.zig
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.build.Builder) void {
|
||||||
|
// Standard release options allow the person running `zig build` to select
|
||||||
|
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
|
||||||
|
const mode = b.standardReleaseOptions();
|
||||||
|
|
||||||
|
const lib = b.addStaticLibrary("smithy", "src/smithy.zig");
|
||||||
|
lib.setBuildMode(mode);
|
||||||
|
lib.install();
|
||||||
|
|
||||||
|
var main_tests = b.addTest("src/smithy.zig");
|
||||||
|
main_tests.setBuildMode(mode);
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run library tests");
|
||||||
|
test_step.dependOn(&main_tests.step);
|
||||||
|
}
|
802
src/smithy.zig
Normal file
802
src/smithy.zig
Normal file
|
@ -0,0 +1,802 @@
|
||||||
|
const std = @import("std");
|
||||||
|
const expect = std.testing.expect;
|
||||||
|
|
||||||
|
// TODO: validate this matches the schema
|
||||||
|
pub const Smithy = struct {
|
||||||
|
version: []const u8,
|
||||||
|
metadata: ModelMetadata,
|
||||||
|
shapes: []ShapeInfo,
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
pub fn init(allocator: std.mem.Allocator, version: []const u8, metadata: ModelMetadata, shapeinfo: []ShapeInfo) Smithy {
|
||||||
|
return .{
|
||||||
|
.version = version,
|
||||||
|
.metadata = metadata,
|
||||||
|
.shapes = shapeinfo,
|
||||||
|
.allocator = allocator,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
pub fn deinit(self: Self) void {
|
||||||
|
for (self.shapes) |s| {
|
||||||
|
switch (s.shape) {
|
||||||
|
.string,
|
||||||
|
.byte,
|
||||||
|
.short,
|
||||||
|
.integer,
|
||||||
|
.long,
|
||||||
|
.float,
|
||||||
|
.double,
|
||||||
|
.bigInteger,
|
||||||
|
.bigDecimal,
|
||||||
|
.blob,
|
||||||
|
.boolean,
|
||||||
|
.timestamp,
|
||||||
|
.document,
|
||||||
|
.member,
|
||||||
|
.resource,
|
||||||
|
=> |v| self.allocator.free(v.traits),
|
||||||
|
.structure => |v| {
|
||||||
|
for (v.members) |m| self.allocator.free(m.traits);
|
||||||
|
self.allocator.free(v.members);
|
||||||
|
self.allocator.free(v.traits);
|
||||||
|
},
|
||||||
|
.uniontype => |v| {
|
||||||
|
for (v.members) |m| self.allocator.free(m.traits);
|
||||||
|
self.allocator.free(v.members);
|
||||||
|
self.allocator.free(v.traits);
|
||||||
|
},
|
||||||
|
.service => |v| {
|
||||||
|
self.allocator.free(v.traits);
|
||||||
|
self.allocator.free(v.operations);
|
||||||
|
},
|
||||||
|
.operation => |v| {
|
||||||
|
if (v.errors) |e| self.allocator.free(e);
|
||||||
|
self.allocator.free(v.traits);
|
||||||
|
},
|
||||||
|
.list => |v| {
|
||||||
|
self.allocator.free(v.traits);
|
||||||
|
},
|
||||||
|
.set => |v| {
|
||||||
|
self.allocator.free(v.traits);
|
||||||
|
},
|
||||||
|
.map => |v| {
|
||||||
|
self.allocator.free(v.key);
|
||||||
|
self.allocator.free(v.value);
|
||||||
|
self.allocator.free(v.traits);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.allocator.free(self.shapes);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
pub const ShapeInfo = struct {
|
||||||
|
id: []const u8,
|
||||||
|
namespace: []const u8,
|
||||||
|
name: []const u8,
|
||||||
|
member: ?[]const u8,
|
||||||
|
|
||||||
|
shape: Shape,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ModelMetadata = struct {
|
||||||
|
suppressions: []struct {
|
||||||
|
id: []const u8,
|
||||||
|
namespace: []const u8,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TraitType = enum {
|
||||||
|
aws_api_service,
|
||||||
|
aws_auth_sigv4,
|
||||||
|
aws_protocol,
|
||||||
|
ec2_query_name,
|
||||||
|
http,
|
||||||
|
http_header,
|
||||||
|
http_label,
|
||||||
|
http_query,
|
||||||
|
http_payload,
|
||||||
|
json_name,
|
||||||
|
xml_name,
|
||||||
|
required,
|
||||||
|
documentation,
|
||||||
|
pattern,
|
||||||
|
range,
|
||||||
|
length,
|
||||||
|
box,
|
||||||
|
sparse,
|
||||||
|
};
|
||||||
|
pub const Trait = union(TraitType) {
|
||||||
|
aws_api_service: struct {
|
||||||
|
sdk_id: []const u8,
|
||||||
|
arn_namespace: []const u8,
|
||||||
|
cloudformation_name: []const u8,
|
||||||
|
cloudtrail_event_source: []const u8,
|
||||||
|
endpoint_prefix: []const u8,
|
||||||
|
},
|
||||||
|
aws_auth_sigv4: struct {
|
||||||
|
name: []const u8,
|
||||||
|
},
|
||||||
|
aws_protocol: AwsProtocol,
|
||||||
|
ec2_query_name: []const u8,
|
||||||
|
json_name: []const u8,
|
||||||
|
xml_name: []const u8,
|
||||||
|
http: struct {
|
||||||
|
method: []const u8,
|
||||||
|
uri: []const u8,
|
||||||
|
code: i64 = 200,
|
||||||
|
},
|
||||||
|
http_header: []const u8,
|
||||||
|
http_label: []const u8,
|
||||||
|
http_query: []const u8,
|
||||||
|
http_payload: struct {},
|
||||||
|
required: struct {},
|
||||||
|
documentation: []const u8,
|
||||||
|
pattern: []const u8,
|
||||||
|
range: struct { // most data is actually integers, but as some are floats, we'll use that here
|
||||||
|
min: ?f64,
|
||||||
|
max: ?f64,
|
||||||
|
},
|
||||||
|
length: struct {
|
||||||
|
min: ?f64,
|
||||||
|
max: ?f64,
|
||||||
|
},
|
||||||
|
box: struct {},
|
||||||
|
sparse: struct {},
|
||||||
|
};
|
||||||
|
const ShapeType = enum {
|
||||||
|
blob,
|
||||||
|
boolean,
|
||||||
|
string,
|
||||||
|
byte,
|
||||||
|
short,
|
||||||
|
integer,
|
||||||
|
long,
|
||||||
|
float,
|
||||||
|
double,
|
||||||
|
bigInteger,
|
||||||
|
bigDecimal,
|
||||||
|
timestamp,
|
||||||
|
document,
|
||||||
|
member,
|
||||||
|
list,
|
||||||
|
set,
|
||||||
|
map,
|
||||||
|
structure,
|
||||||
|
uniontype,
|
||||||
|
service,
|
||||||
|
operation,
|
||||||
|
resource,
|
||||||
|
};
|
||||||
|
const TraitsOnly = struct {
|
||||||
|
traits: []Trait,
|
||||||
|
};
|
||||||
|
pub const TypeMember = struct {
|
||||||
|
name: []const u8,
|
||||||
|
target: []const u8,
|
||||||
|
traits: []Trait,
|
||||||
|
};
|
||||||
|
const Shape = union(ShapeType) {
|
||||||
|
blob: TraitsOnly,
|
||||||
|
boolean: TraitsOnly,
|
||||||
|
string: TraitsOnly,
|
||||||
|
byte: TraitsOnly,
|
||||||
|
short: TraitsOnly,
|
||||||
|
integer: TraitsOnly,
|
||||||
|
long: TraitsOnly,
|
||||||
|
float: TraitsOnly,
|
||||||
|
double: TraitsOnly,
|
||||||
|
bigInteger: TraitsOnly,
|
||||||
|
bigDecimal: TraitsOnly,
|
||||||
|
timestamp: TraitsOnly,
|
||||||
|
document: TraitsOnly,
|
||||||
|
member: TraitsOnly,
|
||||||
|
list: struct {
|
||||||
|
member_target: []const u8,
|
||||||
|
traits: []Trait,
|
||||||
|
},
|
||||||
|
set: struct {
|
||||||
|
member_target: []const u8,
|
||||||
|
traits: []Trait,
|
||||||
|
},
|
||||||
|
map: struct {
|
||||||
|
key: []const u8,
|
||||||
|
value: []const u8,
|
||||||
|
traits: []Trait,
|
||||||
|
},
|
||||||
|
structure: struct {
|
||||||
|
members: []TypeMember,
|
||||||
|
traits: []Trait,
|
||||||
|
},
|
||||||
|
uniontype: struct {
|
||||||
|
members: []TypeMember,
|
||||||
|
traits: []Trait,
|
||||||
|
},
|
||||||
|
service: struct {
|
||||||
|
version: []const u8,
|
||||||
|
operations: [][]const u8,
|
||||||
|
resources: [][]const u8,
|
||||||
|
traits: []Trait,
|
||||||
|
},
|
||||||
|
operation: struct {
|
||||||
|
input: ?[]const u8,
|
||||||
|
output: ?[]const u8,
|
||||||
|
errors: ?[][]const u8,
|
||||||
|
traits: []Trait,
|
||||||
|
},
|
||||||
|
resource: TraitsOnly,
|
||||||
|
};
|
||||||
|
|
||||||
|
// https://awslabs.github.io/smithy/1.0/spec/aws/index.html
|
||||||
|
pub const AwsProtocol = enum {
|
||||||
|
query,
|
||||||
|
rest_xml,
|
||||||
|
json_1_1,
|
||||||
|
json_1_0,
|
||||||
|
rest_json_1,
|
||||||
|
ec2_query,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn parse(allocator: std.mem.Allocator, json_model: []const u8) !Smithy {
|
||||||
|
// construct a parser. We're not copying strings here, but that may
|
||||||
|
// be a poor decision
|
||||||
|
var parser = std.json.Parser.init(allocator, false);
|
||||||
|
defer parser.deinit();
|
||||||
|
var vt = try parser.parse(json_model);
|
||||||
|
defer vt.deinit();
|
||||||
|
return Smithy.init(
|
||||||
|
allocator,
|
||||||
|
vt.root.Object.get("smithy").?.String,
|
||||||
|
ModelMetadata{
|
||||||
|
// TODO: implement
|
||||||
|
.suppressions = &.{},
|
||||||
|
},
|
||||||
|
try shapes(allocator, vt.root.Object.get("shapes").?.Object),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// anytype: HashMap([]const u8, std.json.Value...)
|
||||||
|
// list must be deinitialized by caller
|
||||||
|
fn shapes(allocator: std.mem.Allocator, map: anytype) ![]ShapeInfo {
|
||||||
|
var list = try std.ArrayList(ShapeInfo).initCapacity(allocator, map.count());
|
||||||
|
defer list.deinit();
|
||||||
|
var iterator = map.iterator();
|
||||||
|
while (iterator.next()) |kv| {
|
||||||
|
const id_info = try parseId(kv.key_ptr.*);
|
||||||
|
try list.append(.{
|
||||||
|
.id = id_info.id,
|
||||||
|
.namespace = id_info.namespace,
|
||||||
|
.name = id_info.name,
|
||||||
|
.member = id_info.member,
|
||||||
|
.shape = try getShape(allocator, kv.value_ptr.*),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// This seems to be a synonym for the simple type "string"
|
||||||
|
// https://awslabs.github.io/smithy/1.0/spec/core/model.html#simple-types
|
||||||
|
// But I don't see it in the spec. We might need to preload other similar
|
||||||
|
// simple types?
|
||||||
|
try list.append(.{
|
||||||
|
.id = "smithy.api#String",
|
||||||
|
.namespace = "smithy.api",
|
||||||
|
.name = "String",
|
||||||
|
.member = null,
|
||||||
|
.shape = Shape{
|
||||||
|
.string = .{
|
||||||
|
.traits = &.{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try list.append(.{
|
||||||
|
.id = "smithy.api#Boolean",
|
||||||
|
.namespace = "smithy.api",
|
||||||
|
.name = "Boolean",
|
||||||
|
.member = null,
|
||||||
|
.shape = Shape{
|
||||||
|
.boolean = .{
|
||||||
|
.traits = &.{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try list.append(.{
|
||||||
|
.id = "smithy.api#Integer",
|
||||||
|
.namespace = "smithy.api",
|
||||||
|
.name = "Integer",
|
||||||
|
.member = null,
|
||||||
|
.shape = Shape{
|
||||||
|
.integer = .{
|
||||||
|
.traits = &.{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try list.append(.{
|
||||||
|
.id = "smithy.api#Double",
|
||||||
|
.namespace = "smithy.api",
|
||||||
|
.name = "Double",
|
||||||
|
.member = null,
|
||||||
|
.shape = Shape{
|
||||||
|
.double = .{
|
||||||
|
.traits = &.{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
try list.append(.{
|
||||||
|
.id = "smithy.api#Timestamp",
|
||||||
|
.namespace = "smithy.api",
|
||||||
|
.name = "Timestamp",
|
||||||
|
.member = null,
|
||||||
|
.shape = Shape{
|
||||||
|
.timestamp = .{
|
||||||
|
.traits = &.{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getShape(allocator: std.mem.Allocator, shape: std.json.Value) SmithyParseError!Shape {
|
||||||
|
const shape_type = shape.Object.get("type").?.String;
|
||||||
|
if (std.mem.eql(u8, shape_type, "service"))
|
||||||
|
return Shape{
|
||||||
|
.service = .{
|
||||||
|
.version = shape.Object.get("version").?.String,
|
||||||
|
.operations = if (shape.Object.get("operations")) |ops|
|
||||||
|
try parseTargetList(allocator, ops.Array)
|
||||||
|
else
|
||||||
|
&.{}, // this doesn't make much sense, but it's happening
|
||||||
|
// TODO: implement. We need some sample data tho
|
||||||
|
.resources = &.{},
|
||||||
|
.traits = try parseTraits(allocator, shape.Object.get("traits")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, shape_type, "structure"))
|
||||||
|
return Shape{
|
||||||
|
.structure = .{
|
||||||
|
.members = try parseMembers(allocator, shape.Object.get("members")),
|
||||||
|
.traits = try parseTraits(allocator, shape.Object.get("traits")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, shape_type, "union"))
|
||||||
|
return Shape{
|
||||||
|
.uniontype = .{
|
||||||
|
.members = try parseMembers(allocator, shape.Object.get("members")),
|
||||||
|
.traits = try parseTraits(allocator, shape.Object.get("traits")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, shape_type, "operation"))
|
||||||
|
return Shape{
|
||||||
|
.operation = .{
|
||||||
|
.input = if (shape.Object.get("input")) |member| member.Object.get("target").?.String else null,
|
||||||
|
.output = if (shape.Object.get("output")) |member| member.Object.get("target").?.String else null,
|
||||||
|
.errors = blk: {
|
||||||
|
if (shape.Object.get("errors")) |e| {
|
||||||
|
break :blk try parseTargetList(allocator, e.Array);
|
||||||
|
}
|
||||||
|
break :blk null;
|
||||||
|
},
|
||||||
|
.traits = try parseTraits(allocator, shape.Object.get("traits")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, shape_type, "list"))
|
||||||
|
return Shape{
|
||||||
|
.list = .{
|
||||||
|
.member_target = shape.Object.get("member").?.Object.get("target").?.String,
|
||||||
|
.traits = try parseTraits(allocator, shape.Object.get("traits")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, shape_type, "set"))
|
||||||
|
return Shape{
|
||||||
|
.set = .{
|
||||||
|
.member_target = shape.Object.get("member").?.Object.get("target").?.String,
|
||||||
|
.traits = try parseTraits(allocator, shape.Object.get("traits")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, shape_type, "map"))
|
||||||
|
return Shape{
|
||||||
|
.map = .{
|
||||||
|
.key = shape.Object.get("key").?.Object.get("target").?.String,
|
||||||
|
.value = shape.Object.get("value").?.Object.get("target").?.String,
|
||||||
|
.traits = try parseTraits(allocator, shape.Object.get("traits")),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, shape_type, "string"))
|
||||||
|
return Shape{ .string = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "byte"))
|
||||||
|
return Shape{ .byte = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "short"))
|
||||||
|
return Shape{ .short = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "integer"))
|
||||||
|
return Shape{ .integer = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "long"))
|
||||||
|
return Shape{ .long = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "float"))
|
||||||
|
return Shape{ .float = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "double"))
|
||||||
|
return Shape{ .double = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "bigInteger"))
|
||||||
|
return Shape{ .bigInteger = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "bigDecimal"))
|
||||||
|
return Shape{ .bigDecimal = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "boolean"))
|
||||||
|
return Shape{ .boolean = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "blob"))
|
||||||
|
return Shape{ .blob = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "timestamp"))
|
||||||
|
return Shape{ .timestamp = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "document"))
|
||||||
|
return Shape{ .document = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "member"))
|
||||||
|
return Shape{ .member = try parseTraitsOnly(allocator, shape) };
|
||||||
|
if (std.mem.eql(u8, shape_type, "resource"))
|
||||||
|
return Shape{ .resource = try parseTraitsOnly(allocator, shape) };
|
||||||
|
|
||||||
|
std.debug.print("Invalid Type: {s}", .{shape_type});
|
||||||
|
return SmithyParseError.InvalidType;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseMembers(allocator: std.mem.Allocator, shape: ?std.json.Value) SmithyParseError![]TypeMember {
|
||||||
|
var rc: []TypeMember = &.{};
|
||||||
|
if (shape == null)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
const map = shape.?.Object;
|
||||||
|
var list = std.ArrayList(TypeMember).initCapacity(allocator, map.count()) catch return SmithyParseError.OutOfMemory;
|
||||||
|
defer list.deinit();
|
||||||
|
var iterator = map.iterator();
|
||||||
|
while (iterator.next()) |kv| {
|
||||||
|
try list.append(TypeMember{
|
||||||
|
.name = kv.key_ptr.*,
|
||||||
|
.target = kv.value_ptr.*.Object.get("target").?.String,
|
||||||
|
.traits = try parseTraits(allocator, kv.value_ptr.*.Object.get("traits")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayList of std.Json.Value
|
||||||
|
fn parseTargetList(allocator: std.mem.Allocator, list: anytype) SmithyParseError![][]const u8 {
|
||||||
|
var array_list = std.ArrayList([]const u8).initCapacity(allocator, list.items.len) catch return SmithyParseError.OutOfMemory;
|
||||||
|
defer array_list.deinit();
|
||||||
|
for (list.items) |i| {
|
||||||
|
try array_list.append(i.Object.get("target").?.String);
|
||||||
|
}
|
||||||
|
return array_list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
fn parseTraitsOnly(allocator: std.mem.Allocator, shape: std.json.Value) SmithyParseError!TraitsOnly {
|
||||||
|
return TraitsOnly{
|
||||||
|
.traits = try parseTraits(allocator, shape.Object.get("traits")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseTraits(allocator: std.mem.Allocator, shape: ?std.json.Value) SmithyParseError![]Trait {
|
||||||
|
var rc: []Trait = &.{};
|
||||||
|
if (shape == null)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
const map = shape.?.Object;
|
||||||
|
var list = std.ArrayList(Trait).initCapacity(allocator, map.count()) catch return SmithyParseError.OutOfMemory;
|
||||||
|
defer list.deinit();
|
||||||
|
var iterator = map.iterator();
|
||||||
|
while (iterator.next()) |kv| {
|
||||||
|
if (try getTrait(kv.key_ptr.*, kv.value_ptr.*)) |t|
|
||||||
|
try list.append(t);
|
||||||
|
}
|
||||||
|
return list.toOwnedSlice();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn getTrait(trait_type: []const u8, value: std.json.Value) SmithyParseError!?Trait {
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.api#service"))
|
||||||
|
return Trait{
|
||||||
|
.aws_api_service = .{
|
||||||
|
.sdk_id = value.Object.get("sdkId").?.String,
|
||||||
|
.arn_namespace = value.Object.get("arnNamespace").?.String,
|
||||||
|
.cloudformation_name = value.Object.get("cloudFormationName").?.String,
|
||||||
|
.cloudtrail_event_source = value.Object.get("cloudTrailEventSource").?.String,
|
||||||
|
// what good is a service without an endpoint? I don't know - ask amp
|
||||||
|
.endpoint_prefix = if (value.Object.get("endpointPrefix")) |endpoint| endpoint.String else "",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.auth#sigv4"))
|
||||||
|
return Trait{
|
||||||
|
.aws_auth_sigv4 = .{
|
||||||
|
.name = value.Object.get("name").?.String,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#required"))
|
||||||
|
return Trait{ .required = .{} };
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#sparse"))
|
||||||
|
return Trait{ .sparse = .{} };
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#box"))
|
||||||
|
return Trait{ .box = .{} };
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#range"))
|
||||||
|
return Trait{
|
||||||
|
.range = .{
|
||||||
|
.min = getOptionalNumber(value, "min"),
|
||||||
|
.max = getOptionalNumber(value, "max"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#length"))
|
||||||
|
return Trait{
|
||||||
|
.length = .{
|
||||||
|
.min = getOptionalNumber(value, "min"),
|
||||||
|
.max = getOptionalNumber(value, "max"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.protocols#restJson1"))
|
||||||
|
return Trait{
|
||||||
|
.aws_protocol = .rest_json_1,
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.protocols#awsJson1_0"))
|
||||||
|
return Trait{
|
||||||
|
.aws_protocol = .json_1_0,
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.protocols#awsJson1_1"))
|
||||||
|
return Trait{
|
||||||
|
.aws_protocol = .json_1_1,
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.protocols#restXml"))
|
||||||
|
return Trait{
|
||||||
|
.aws_protocol = .rest_xml,
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.protocols#awsQuery"))
|
||||||
|
return Trait{
|
||||||
|
.aws_protocol = .query,
|
||||||
|
};
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.protocols#ec2Query"))
|
||||||
|
return Trait{
|
||||||
|
.aws_protocol = .ec2_query,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#documentation"))
|
||||||
|
return Trait{ .documentation = value.String };
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#pattern"))
|
||||||
|
return Trait{ .pattern = value.String };
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, trait_type, "aws.protocols#ec2QueryName"))
|
||||||
|
return Trait{ .ec2_query_name = value.String };
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#http")) {
|
||||||
|
var code: i64 = 200;
|
||||||
|
if (value.Object.get("code")) |v| {
|
||||||
|
if (v == .Integer)
|
||||||
|
code = v.Integer;
|
||||||
|
}
|
||||||
|
return Trait{ .http = .{
|
||||||
|
.method = value.Object.get("method").?.String,
|
||||||
|
.uri = value.Object.get("uri").?.String,
|
||||||
|
.code = code,
|
||||||
|
} };
|
||||||
|
}
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#jsonName"))
|
||||||
|
return Trait{ .json_name = value.String };
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#xmlName"))
|
||||||
|
return Trait{ .xml_name = value.String };
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#httpQuery"))
|
||||||
|
return Trait{ .http_query = value.String };
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#httpHeader"))
|
||||||
|
return Trait{ .http_header = value.String };
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#httpPayload"))
|
||||||
|
return Trait{ .http_payload = .{} };
|
||||||
|
|
||||||
|
// TODO: Maybe care about these traits?
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#title"))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, trait_type, "smithy.api#xmlNamespace"))
|
||||||
|
return null;
|
||||||
|
// TODO: win argument with compiler to get this comptime
|
||||||
|
const list =
|
||||||
|
\\aws.api#arnReference
|
||||||
|
\\aws.api#clientDiscoveredEndpoint
|
||||||
|
\\aws.api#clientEndpointDiscovery
|
||||||
|
\\aws.api#arn
|
||||||
|
\\aws.auth#unsignedPayload
|
||||||
|
\\aws.iam#disableConditionKeyInference
|
||||||
|
\\smithy.api#auth
|
||||||
|
\\smithy.api#cors
|
||||||
|
\\smithy.api#deprecated
|
||||||
|
\\smithy.api#endpoint
|
||||||
|
\\smithy.api#enum
|
||||||
|
\\smithy.api#error
|
||||||
|
\\smithy.api#eventPayload
|
||||||
|
\\smithy.api#externalDocumentation
|
||||||
|
\\smithy.api#hostLabel
|
||||||
|
\\smithy.api#httpError
|
||||||
|
\\smithy.api#httpChecksumRequired
|
||||||
|
\\smithy.api#httpLabel
|
||||||
|
\\smithy.api#httpPrefixHeaders
|
||||||
|
\\smithy.api#httpQueryParams
|
||||||
|
\\smithy.api#httpResponseCode
|
||||||
|
\\smithy.api#idempotencyToken
|
||||||
|
\\smithy.api#idempotent
|
||||||
|
\\smithy.api#mediaType
|
||||||
|
\\smithy.api#noReplace
|
||||||
|
\\smithy.api#optionalAuth
|
||||||
|
\\smithy.api#paginated
|
||||||
|
\\smithy.api#readonly
|
||||||
|
\\smithy.api#references
|
||||||
|
\\smithy.api#requiresLength
|
||||||
|
\\smithy.api#retryable
|
||||||
|
\\smithy.api#sensitive
|
||||||
|
\\smithy.api#streaming
|
||||||
|
\\smithy.api#suppress
|
||||||
|
\\smithy.api#tags
|
||||||
|
\\smithy.api#timestampFormat
|
||||||
|
\\smithy.api#xmlAttribute
|
||||||
|
\\smithy.api#xmlFlattened
|
||||||
|
\\smithy.waiters#waitable
|
||||||
|
;
|
||||||
|
var iterator = std.mem.split(u8, list, "\n");
|
||||||
|
while (iterator.next()) |known_but_unimplemented| {
|
||||||
|
if (std.mem.eql(u8, trait_type, known_but_unimplemented))
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Totally unknown type
|
||||||
|
std.log.err("Invalid Trait Type: {s}", .{trait_type});
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
fn getOptionalNumber(value: std.json.Value, key: []const u8) ?f64 {
|
||||||
|
if (value.Object.get(key)) |v|
|
||||||
|
return switch (v) {
|
||||||
|
.Integer => @intToFloat(f64, v.Integer),
|
||||||
|
.Float => v.Float,
|
||||||
|
.Null, .Bool, .NumberString, .String, .Array, .Object => null,
|
||||||
|
};
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const IdInfo = struct { id: []const u8, namespace: []const u8, name: []const u8, member: ?[]const u8 };
|
||||||
|
const SmithyParseError = error{
|
||||||
|
NoHashtagFound,
|
||||||
|
InvalidType,
|
||||||
|
OutOfMemory,
|
||||||
|
};
|
||||||
|
fn parseId(id: []const u8) SmithyParseError!IdInfo {
|
||||||
|
var hashtag: ?usize = null;
|
||||||
|
var dollar: ?usize = null;
|
||||||
|
var inx: usize = 0;
|
||||||
|
for (id) |ch| {
|
||||||
|
switch (ch) {
|
||||||
|
'#' => hashtag = inx,
|
||||||
|
'$' => dollar = inx,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
inx = inx + 1;
|
||||||
|
}
|
||||||
|
if (hashtag == null) {
|
||||||
|
std.debug.print("no hashtag found on id: {s}\n", .{id});
|
||||||
|
return SmithyParseError.NoHashtagFound;
|
||||||
|
}
|
||||||
|
const namespace = id[0..hashtag.?];
|
||||||
|
var end = id.len;
|
||||||
|
var member: ?[]const u8 = null;
|
||||||
|
if (dollar) |d| {
|
||||||
|
member = id[dollar.? + 1 .. end];
|
||||||
|
end = d;
|
||||||
|
}
|
||||||
|
const name = id[hashtag.? + 1 .. end];
|
||||||
|
return IdInfo{
|
||||||
|
.id = id,
|
||||||
|
.namespace = namespace,
|
||||||
|
.name = name,
|
||||||
|
.member = member,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fn read_file_to_string(allocator: std.mem.Allocator, file_name: []const u8, max_bytes: usize) ![]const u8 {
|
||||||
|
const file = try std.fs.cwd().openFile(file_name, std.fs.File.OpenFlags{});
|
||||||
|
defer file.close();
|
||||||
|
return file.readToEndAlloc(allocator, max_bytes);
|
||||||
|
}
|
||||||
|
var test_data: ?[]const u8 = null;
|
||||||
|
const intrinsic_type_count: usize = 5; // 5 intrinsic types are added to every model
|
||||||
|
|
||||||
|
fn getTestData(_: *std.mem.Allocator) []const u8 {
|
||||||
|
if (test_data) |d| return d;
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
test_data = read_file_to_string(gpa.allocator, "test.json", 150000) catch @panic("could not read test.json");
|
||||||
|
return test_data.?;
|
||||||
|
}
|
||||||
|
test "read file" {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer if (gpa.deinit()) @panic("leak");
|
||||||
|
const allocator = gpa.allocator;
|
||||||
|
_ = getTestData(allocator);
|
||||||
|
// test stuff
|
||||||
|
}
|
||||||
|
test "parse string" {
|
||||||
|
const test_string =
|
||||||
|
\\ {
|
||||||
|
\\ "smithy": "1.0",
|
||||||
|
\\ "shapes": {
|
||||||
|
\\ "com.amazonaws.sts#AWSSecurityTokenServiceV20110615": {
|
||||||
|
\\ "type": "service",
|
||||||
|
\\ "version": "2011-06-15",
|
||||||
|
\\ "operations": [
|
||||||
|
\\ {
|
||||||
|
\\ "target": "op"
|
||||||
|
\\ }
|
||||||
|
\\ ]
|
||||||
|
\\ }
|
||||||
|
\\ }
|
||||||
|
\\ }
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer if (gpa.deinit()) @panic("leak");
|
||||||
|
const allocator = gpa.allocator;
|
||||||
|
const model = try parse(allocator, test_string);
|
||||||
|
defer model.deinit();
|
||||||
|
try expect(std.mem.eql(u8, model.version, "1.0"));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(intrinsic_type_count + 1, model.shapes.len);
|
||||||
|
try std.testing.expectEqualStrings("com.amazonaws.sts#AWSSecurityTokenServiceV20110615", model.shapes[0].id);
|
||||||
|
try std.testing.expectEqualStrings("com.amazonaws.sts", model.shapes[0].namespace);
|
||||||
|
try std.testing.expectEqualStrings("AWSSecurityTokenServiceV20110615", model.shapes[0].name);
|
||||||
|
try std.testing.expect(model.shapes[0].member == null);
|
||||||
|
try std.testing.expectEqualStrings("2011-06-15", model.shapes[0].shape.service.version);
|
||||||
|
}
|
||||||
|
test "parse shape with member" {
|
||||||
|
const test_string =
|
||||||
|
\\ {
|
||||||
|
\\ "smithy": "1.0",
|
||||||
|
\\ "shapes": {
|
||||||
|
\\ "com.amazonaws.sts#AWSSecurityTokenServiceV20110615$member": {
|
||||||
|
\\ "type": "service",
|
||||||
|
\\ "version": "2011-06-15",
|
||||||
|
\\ "operations": [
|
||||||
|
\\ {
|
||||||
|
\\ "target": "op"
|
||||||
|
\\ }
|
||||||
|
\\ ]
|
||||||
|
\\ }
|
||||||
|
\\ }
|
||||||
|
\\ }
|
||||||
|
\\
|
||||||
|
\\
|
||||||
|
;
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer if (gpa.deinit()) @panic("leak");
|
||||||
|
const allocator = gpa.allocator;
|
||||||
|
const model = try parse(allocator, test_string);
|
||||||
|
defer model.deinit();
|
||||||
|
try expect(std.mem.eql(u8, model.version, "1.0"));
|
||||||
|
try std.testing.expectEqual(intrinsic_type_count + 1, model.shapes.len);
|
||||||
|
try std.testing.expectEqualStrings("com.amazonaws.sts#AWSSecurityTokenServiceV20110615$member", model.shapes[0].id);
|
||||||
|
try std.testing.expectEqualStrings("com.amazonaws.sts", model.shapes[0].namespace);
|
||||||
|
try std.testing.expectEqualStrings("AWSSecurityTokenServiceV20110615", model.shapes[0].name);
|
||||||
|
try std.testing.expectEqualStrings("2011-06-15", model.shapes[0].shape.service.version);
|
||||||
|
try std.testing.expectEqualStrings("member", model.shapes[0].member.?);
|
||||||
|
}
|
||||||
|
test "parse file" {
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer if (gpa.deinit()) @panic("leak");
|
||||||
|
const allocator = gpa.allocator;
|
||||||
|
const test_string = getTestData(allocator);
|
||||||
|
const model = try parse(allocator, test_string);
|
||||||
|
defer model.deinit();
|
||||||
|
try std.testing.expectEqualStrings(model.version, "1.0");
|
||||||
|
// metadata expectations
|
||||||
|
// try expect(std.mem.eql(u8, model.version, "version 1.0"));
|
||||||
|
|
||||||
|
// shape expectations
|
||||||
|
try std.testing.expectEqual(intrinsic_type_count + 81, model.shapes.len);
|
||||||
|
var optsvc: ?ShapeInfo = null;
|
||||||
|
for (model.shapes) |shape| {
|
||||||
|
if (std.mem.eql(u8, shape.id, "com.amazonaws.sts#AWSSecurityTokenServiceV20110615")) {
|
||||||
|
optsvc = shape;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try std.testing.expect(optsvc != null);
|
||||||
|
const svc = optsvc.?;
|
||||||
|
try std.testing.expectEqualStrings("com.amazonaws.sts#AWSSecurityTokenServiceV20110615", svc.id);
|
||||||
|
try std.testing.expectEqualStrings("com.amazonaws.sts", svc.namespace);
|
||||||
|
try std.testing.expectEqualStrings("AWSSecurityTokenServiceV20110615", svc.name);
|
||||||
|
try std.testing.expectEqualStrings("2011-06-15", svc.shape.service.version);
|
||||||
|
// Should be 6, but we don't handle title or xml namespace
|
||||||
|
try std.testing.expectEqual(@as(usize, 4), svc.shape.service.traits.len);
|
||||||
|
try std.testing.expectEqual(@as(usize, 8), svc.shape.service.operations.len);
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user