update code generation without containing struct
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
Instead we will rely here on the implicit struct provided by the file itself. This means that when the service_manifest.zig import is assigned, we don't end up with a construct like "manifest.sts.sts" to get to the sts service. This simplifies greatly the way that servicemodel.zig needs to behave. One down side, however, is that the structure does not seem accessible with the current zig language, making metaInfo unable to access itself as it did before. Or maybe it can, I just can't find it. So, this change also adds a new "service_metadata" public constant with the same declarations being published at the file level, and that is the new return from the metaInfo function. Our aws.zig only really needs the action and that metadata, so we're ok with that even if there is some duplication (we could codegen pointers over, and maybe should to save a little bit of space).
This commit is contained in:
parent
13e43528b5
commit
dfa0be60b9
|
@ -63,7 +63,7 @@ fn generateServicesForFilePath(allocator: *std.mem.Allocator, comptime terminato
|
|||
defer file.close();
|
||||
return try generateServices(allocator, terminator, file, writer);
|
||||
}
|
||||
fn generateServices(allocator: *std.mem.Allocator, comptime terminator: []const u8, file: std.fs.File, writer: anytype) ![][]const u8 {
|
||||
fn generateServices(allocator: *std.mem.Allocator, comptime _: []const u8, file: std.fs.File, writer: anytype) ![][]const u8 {
|
||||
const json = try file.readToEndAlloc(allocator, 1024 * 1024 * 1024);
|
||||
defer allocator.free(json);
|
||||
const model = try smithy.parse(allocator, json);
|
||||
|
@ -108,9 +108,15 @@ fn generateServices(allocator: *std.mem.Allocator, comptime terminator: []const
|
|||
// sdk_id. Not sure this will simple...
|
||||
const constant_name = try snake.fromPascalCase(allocator, sdk_id);
|
||||
try constant_names.append(constant_name);
|
||||
try writer.print("pub const {s}: struct ", .{constant_name});
|
||||
_ = try writer.write("{\n");
|
||||
|
||||
try writer.print("const Self = @This();\n", .{});
|
||||
try writer.print("pub const version: []const u8 = \"{s}\";\n", .{version});
|
||||
try writer.print("pub const sdk_id: []const u8 = \"{s}\";\n", .{sdk_id});
|
||||
try writer.print("pub const arn_namespace: []const u8 = \"{s}\";\n", .{arn_namespace});
|
||||
try writer.print("pub const endpoint_prefix: []const u8 = \"{s}\";\n", .{endpoint_prefix});
|
||||
try writer.print("pub const sigv4_name: []const u8 = \"{s}\";\n", .{sigv4_name});
|
||||
// TODO: This really should just be ".whatevs". We're fully qualifying here, which isn't typical
|
||||
try writer.print("pub const aws_protocol: smithy.AwsProtocol = smithy.{s};\n\n", .{aws_protocol});
|
||||
_ = try writer.write("pub const service_metadata : struct {\n");
|
||||
try writer.print(" version: []const u8 = \"{s}\",\n", .{version});
|
||||
try writer.print(" sdk_id: []const u8 = \"{s}\",\n", .{sdk_id});
|
||||
try writer.print(" arn_namespace: []const u8 = \"{s}\",\n", .{arn_namespace});
|
||||
|
@ -118,14 +124,11 @@ fn generateServices(allocator: *std.mem.Allocator, comptime terminator: []const
|
|||
try writer.print(" sigv4_name: []const u8 = \"{s}\",\n", .{sigv4_name});
|
||||
// TODO: This really should just be ".whatevs". We're fully qualifying here, which isn't typical
|
||||
try writer.print(" aws_protocol: smithy.AwsProtocol = smithy.{s},\n", .{aws_protocol});
|
||||
_ = try writer.write("} = .{};\n");
|
||||
|
||||
// Operations
|
||||
for (service.shape.service.operations) |op|
|
||||
try generateOperation(allocator, shapes.get(op).?, shapes, writer, constant_name);
|
||||
|
||||
// End service
|
||||
_ = try writer.write("} = .{}" ++ terminator ++ " // end of service: ");
|
||||
try writer.print("{s}\n", .{arn_namespace}); // this var needs to match above
|
||||
}
|
||||
return constant_names.toOwnedSlice();
|
||||
}
|
||||
|
@ -133,15 +136,15 @@ fn generateOperation(allocator: *std.mem.Allocator, operation: smithy.ShapeInfo,
|
|||
const snake_case_name = try snake.fromPascalCase(allocator, operation.name);
|
||||
defer allocator.free(snake_case_name);
|
||||
|
||||
const prefix = " ";
|
||||
const prefix = " ";
|
||||
var type_stack = std.ArrayList(*const smithy.ShapeInfo).init(allocator);
|
||||
defer type_stack.deinit();
|
||||
// indent should start at 4 spaces here
|
||||
const operation_name = avoidReserved(snake_case_name);
|
||||
try writer.print(" {s}: struct ", .{operation_name});
|
||||
try writer.print("pub const {s}: struct ", .{operation_name});
|
||||
_ = try writer.write("{\n");
|
||||
try writer.print(" action_name: []const u8 = \"{s}\",\n", .{operation.name});
|
||||
_ = try writer.write(" Request: type = ");
|
||||
try writer.print(" action_name: []const u8 = \"{s}\",\n", .{operation.name});
|
||||
_ = try writer.write(" Request: type = ");
|
||||
if (operation.shape.operation.input) |member| {
|
||||
try generateTypeFor(allocator, member, shapes, writer, prefix, false, &type_stack, false);
|
||||
_ = try writer.write("\n");
|
||||
|
@ -151,24 +154,24 @@ fn generateOperation(allocator: *std.mem.Allocator, operation: smithy.ShapeInfo,
|
|||
try generateMetadataFunction(service, operation_name, prefix, writer);
|
||||
}
|
||||
_ = try writer.write(",\n");
|
||||
_ = try writer.write(" Response: type = ");
|
||||
_ = try writer.write(" Response: type = ");
|
||||
if (operation.shape.operation.output) |member| {
|
||||
try generateTypeFor(allocator, member, shapes, writer, " ", true, &type_stack, true);
|
||||
try generateTypeFor(allocator, member, shapes, writer, " ", true, &type_stack, true);
|
||||
} else _ = try writer.write("struct {}"); // we want to maintain consistency with other ops
|
||||
_ = try writer.write(",\n");
|
||||
|
||||
if (operation.shape.operation.errors) |errors| {
|
||||
_ = try writer.write(" ServiceError: type = error{\n");
|
||||
_ = try writer.write(" ServiceError: type = error{\n");
|
||||
for (errors) |err| {
|
||||
const err_name = getErrorName(shapes.get(err).?.name); // need to remove "exception"
|
||||
try writer.print(" {s},\n", .{err_name});
|
||||
try writer.print(" {s},\n", .{err_name});
|
||||
}
|
||||
_ = try writer.write(" },\n");
|
||||
_ = try writer.write(" },\n");
|
||||
}
|
||||
_ = try writer.write(" } = .{},\n");
|
||||
_ = try writer.write("} = .{};\n");
|
||||
}
|
||||
|
||||
fn generateMetadataFunction(service: []const u8, operation_name: []const u8, comptime prefix: []const u8, writer: anytype) !void {
|
||||
fn generateMetadataFunction(_: []const u8, operation_name: []const u8, comptime prefix: []const u8, writer: anytype) !void {
|
||||
// TODO: Shove these lines in here, and also the else portion
|
||||
// pub fn metaInfo(self: @This()) struct { service: @TypeOf(sts), action: @TypeOf(sts.get_caller_identity) } {
|
||||
// return .{ .service = sts, .action = sts.get_caller_identity };
|
||||
|
@ -176,9 +179,9 @@ fn generateMetadataFunction(service: []const u8, operation_name: []const u8, com
|
|||
// We want to add a short "get my parents" function into the response
|
||||
try writer.print("{s} ", .{prefix});
|
||||
_ = try writer.write("pub fn metaInfo(_: @This()) struct { ");
|
||||
try writer.print("service: @TypeOf({s}), action: @TypeOf({s}.{s})", .{ service, service, operation_name });
|
||||
_ = try writer.write(" } {\n" ++ prefix ++ " return .{ ");
|
||||
try writer.print(".service = {s}, .action = {s}.{s}", .{ service, service, operation_name });
|
||||
try writer.print("service_metadata: @TypeOf(service_metadata), action: @TypeOf({s})", .{operation_name});
|
||||
_ = try writer.write(" } {\n" ++ prefix ++ " return .{ .service_metadata = service_metadata, ");
|
||||
try writer.print(".action = {s}", .{operation_name});
|
||||
_ = try writer.write(" };\n" ++ prefix ++ " }\n" ++ prefix ++ "}");
|
||||
}
|
||||
fn getErrorName(err_name: []const u8) []const u8 {
|
||||
|
|
22
src/aws.zig
22
src/aws.zig
|
@ -43,16 +43,16 @@ pub const Aws = struct {
|
|||
// every codegenned request object includes a metaInfo function to get
|
||||
// pointers to service and action
|
||||
const meta_info = request.metaInfo();
|
||||
const service = meta_info.service;
|
||||
const service_meta = meta_info.service_metadata;
|
||||
const action = meta_info.action;
|
||||
|
||||
log.debug("call: prefix {s}, sigv4 {s}, version {s}, action {s}", .{
|
||||
service.endpoint_prefix,
|
||||
service.sigv4_name,
|
||||
service.version,
|
||||
service_meta.endpoint_prefix,
|
||||
service_meta.sigv4_name,
|
||||
service_meta.version,
|
||||
action.action_name,
|
||||
});
|
||||
log.debug("proto: {s}", .{service.aws_protocol});
|
||||
log.debug("proto: {s}", .{service_meta.aws_protocol});
|
||||
|
||||
// It seems as though there are 3 major branches of the 6 protocols.
|
||||
// 1. query/ec2_query, which are identical until you get to complex
|
||||
|
@ -62,8 +62,8 @@ pub const Aws = struct {
|
|||
// for empty body serialization), but differ in error handling.
|
||||
// We're not doing a lot of error handling here, though.
|
||||
// 3. rest_xml: This is a one-off for S3, never used since
|
||||
switch (service.aws_protocol) {
|
||||
.query, .ec2_query => return self.callQuery(request, service, action, options),
|
||||
switch (service_meta.aws_protocol) {
|
||||
.query, .ec2_query => return self.callQuery(request, service_meta, action, options),
|
||||
.rest_json_1, .json_1_0, .json_1_1 => @compileError("REST Json, Json 1.0/1.1 protocol not yet supported"),
|
||||
.rest_xml => @compileError("REST XML protocol not yet supported"),
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ pub const Aws = struct {
|
|||
// Query, so we'll handle both here. Realistically we probably don't effectively
|
||||
// handle lists and maps properly anyway yet, so we'll go for it and see
|
||||
// where it breaks. PRs and/or failing test cases appreciated.
|
||||
fn callQuery(self: Self, comptime request: anytype, service: anytype, action: anytype, options: Options) !FullResponse(request) {
|
||||
fn callQuery(self: Self, comptime request: anytype, service_meta: anytype, action: anytype, options: Options) !FullResponse(request) {
|
||||
var buffer = std.ArrayList(u8).init(self.allocator);
|
||||
defer buffer.deinit();
|
||||
const writer = buffer.writer();
|
||||
|
@ -84,16 +84,16 @@ pub const Aws = struct {
|
|||
});
|
||||
const continuation = if (buffer.items.len > 0) "&" else "";
|
||||
|
||||
const body = try std.fmt.allocPrint(self.allocator, "Action={s}&Version={s}{s}{s}\n", .{ action.action_name, service.version, continuation, buffer.items });
|
||||
const body = try std.fmt.allocPrint(self.allocator, "Action={s}&Version={s}{s}{s}\n", .{ action.action_name, service_meta.version, continuation, buffer.items });
|
||||
defer self.allocator.free(body);
|
||||
const FullR = FullResponse(request);
|
||||
const response = try self.aws_http.callApi(
|
||||
service.endpoint_prefix,
|
||||
service_meta.endpoint_prefix,
|
||||
body,
|
||||
.{
|
||||
.region = options.region,
|
||||
.dualstack = options.dualstack,
|
||||
.sigv4_service_name = service.sigv4_name,
|
||||
.sigv4_service_name = service_meta.sigv4_name,
|
||||
},
|
||||
);
|
||||
// TODO: Can response handling be reused?
|
||||
|
|
|
@ -3,20 +3,12 @@ const service_list = @import("models/service_manifest.zig");
|
|||
const expectEqualStrings = std.testing.expectEqualStrings;
|
||||
|
||||
pub fn Services(service_imports: anytype) type {
|
||||
if (service_imports.len == 0)
|
||||
return service_list;
|
||||
|
||||
if (service_imports.len == 0) return services;
|
||||
// From here, the fields of our structure can be generated at comptime...
|
||||
var fields: [serviceCount(service_imports)]std.builtin.TypeInfo.StructField = undefined;
|
||||
|
||||
// This is run at comptime with multiple nested loops and a large (267 at
|
||||
// time of writing) number of services. 4 was chosen by trial and error,
|
||||
// but otherwise the branch count will be the product of field length,
|
||||
// service list length and the number of imports requested
|
||||
// @setEvalBranchQuota(4 * fields.len * service_list.len * std.math.min(service_imports.len, 1));
|
||||
for (fields) |*item, i| {
|
||||
const import_service = @field(service_list, @tagName(service_imports[i]));
|
||||
const import_field = @field(import_service, @tagName(service_imports[i]));
|
||||
const import_field = @field(service_list, @tagName(service_imports[i]));
|
||||
item.* = .{
|
||||
.name = @tagName(service_imports[i]),
|
||||
.field_type = @TypeOf(import_field),
|
||||
|
@ -44,7 +36,7 @@ fn serviceCount(desired_services: anytype) usize {
|
|||
|
||||
/// Using this constant may blow up build times. Recommed using Services()
|
||||
/// function directly, e.g. const services = Services(.{.sts, .ec2, .s3, .ddb}){};
|
||||
pub const services = Services(.{}){};
|
||||
pub const services = service_list;
|
||||
|
||||
test "services includes sts" {
|
||||
try expectEqualStrings("2011-06-15", services.sts.version);
|
||||
|
@ -55,13 +47,10 @@ test "sts includes get_caller_identity" {
|
|||
test "can get service and action name from request" {
|
||||
// get request object. This call doesn't have parameters
|
||||
const req = services.sts.get_caller_identity.Request{};
|
||||
// const metadata = @TypeOf(req).metaInfo();
|
||||
const metadata = req.metaInfo();
|
||||
try expectEqualStrings("2011-06-15", metadata.service.version);
|
||||
// expectEqualStrings("GetCallerIdentity", metadata.action.action_name);
|
||||
try expectEqualStrings("2011-06-15", metadata.service_metadata.version);
|
||||
}
|
||||
test "can filter services" {
|
||||
const filtered_services = Services(.{ .sts, .waf_v2 }){};
|
||||
// const filtered_services = Services(.{.sts}){};
|
||||
try expectEqualStrings("2011-06-15", filtered_services.sts.version);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user