You do not have sufficient access to perform this action.
", 30 | )); 31 | 32 | try extension.appendError(try buildError( 33 | arena, 34 | "IncompleteSignature", 35 | .bad_request, 36 | "The request signature does not conform to AWS standards.
", 37 | )); 38 | 39 | try extension.appendError(try buildError( 40 | arena, 41 | "InternalFailure", 42 | .internal_server_error, 43 | "The request processing has failed because of an unknown error, exception or failure.
", 44 | )); 45 | 46 | try extension.appendError(try buildError( 47 | arena, 48 | "InvalidAction", 49 | .bad_request, 50 | "The action or operation requested is invalid. Verify that the action is typed correctly.
", 51 | )); 52 | 53 | try extension.appendError(try buildError( 54 | arena, 55 | "InvalidClientTokenId", 56 | .forbidden, 57 | "The X.509 certificate or AWS access key ID provided does not exist in our records.
", 58 | )); 59 | 60 | try extension.appendError(try buildError( 61 | arena, 62 | "NotAuthorized", 63 | .bad_request, 64 | "You do not have permission to perform this action.
", 65 | )); 66 | 67 | try extension.appendError(try buildError( 68 | arena, 69 | "OptInRequired", 70 | .forbidden, 71 | "The AWS access key ID needs a subscription for the service.
", 72 | )); 73 | 74 | try extension.appendError(try buildError( 75 | arena, 76 | "RequestExpired", 77 | .bad_request, 78 | "The request reached the service more than 15 minutes after the date stamp on the request or more than 15 minutes after the request expiration date (such as for pre-signed URLs), or the date stamp on the request is more than 15 minutes in the future.
", 79 | )); 80 | 81 | try extension.appendError(try buildError( 82 | arena, 83 | "ServiceUnavailable", 84 | .service_unavailable, 85 | "The request has failed due to a temporary failure of the server.
", 86 | )); 87 | 88 | try extension.appendError(try buildError( 89 | arena, 90 | "ThrottlingException", 91 | .bad_request, 92 | "The request was denied due to request throttling.
", 93 | )); 94 | 95 | try extension.appendError(try buildError( 96 | arena, 97 | "ValidationError", 98 | .bad_request, 99 | "The input fails to satisfy the constraints specified by an AWS service.
", 100 | )); 101 | } 102 | 103 | fn buildError( 104 | arena: Allocator, 105 | name: []const u8, 106 | http_status: std.http.Status, 107 | html_docs: []const u8, 108 | ) !SymbolsProvider.Error { 109 | return .{ 110 | .id = null, 111 | .name_api = name, 112 | .name_zig = try smithy.name_util.formatCase(arena, .snake, name), 113 | .retryable = false, 114 | .http_status = http_status, 115 | .source = .server, 116 | .html_docs = html_docs, 117 | }; 118 | } 119 | -------------------------------------------------------------------------------- /aws/codegen/jobs/config_region.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const jobz = @import("jobz"); 4 | const Delegate = jobz.Delegate; 5 | const zig = @import("codmod").zig; 6 | const codegen_jobs = @import("codmod/jobs").codegen; 7 | const name_util = @import("smithy/codegen").name_util; 8 | 9 | pub const RegionDef = struct { 10 | code: []const u8, 11 | description: ?[]const u8, 12 | }; 13 | 14 | const Context = struct { 15 | arena: Allocator, 16 | defs: []const RegionDef, 17 | }; 18 | 19 | pub const RegionsCodegen = codegen_jobs.ZigScript.Task("AWS Config Regions", regionsCodegenTask, .{}); 20 | fn regionsCodegenTask(self: *const Delegate, bld: *zig.ContainerBuild, defs: []const RegionDef) anyerror!void { 21 | try bld.constant("std").assign(bld.x.import("std")); 22 | 23 | const context = Context{ .arena = self.alloc(), .defs = defs }; 24 | try bld.public().constant("Region").assign(bld.x.@"enum"().bodyWith(context, writeEnum)); 25 | } 26 | 27 | fn writeEnum(ctx: Context, bld: *zig.ContainerBuild) !void { 28 | var map = try std.ArrayList(zig.ExprBuild).initCapacity(ctx.arena, ctx.defs.len); 29 | for (ctx.defs) |def| { 30 | const field = try name_util.formatCase(ctx.arena, .snake, def.code); 31 | const pair = &.{ bld.x.valueOf(def.code), bld.x.dot().id(field) }; 32 | try map.append(bld.x.structLiteral(null, pair)); 33 | 34 | if (def.description) |doc| try bld.comment(.doc, doc); 35 | try bld.field(field).end(); 36 | } 37 | 38 | const map_init = bld.x.raw("std.StaticStringMap(Region)").dot().call( 39 | "initComptime", 40 | &.{bld.x.structLiteral(null, try map.toOwnedSlice())}, 41 | ); 42 | try bld.constant("map").assign(map_init); 43 | 44 | try bld.public().function("parse") 45 | .arg("code", bld.x.typeOf([]const u8)) 46 | .returns(bld.x.raw("?Region")).body(struct { 47 | fn f(b: *zig.BlockBuild) !void { 48 | try b.raw("return map.get(code)"); 49 | } 50 | }.f); 51 | 52 | try bld.public().function("toString") 53 | .arg("self", bld.x.raw("Region")) 54 | .returns(bld.x.typeOf([]const u8)).bodyWith(ctx, struct { 55 | fn f(c: Context, b: *zig.BlockBuild) !void { 56 | try b.returns().switchWith(b.x.id("self"), c, writeToString).end(); 57 | } 58 | }.f); 59 | } 60 | 61 | fn writeToString(ctx: Context, bld: *zig.SwitchBuild) !void { 62 | for (ctx.defs) |def| { 63 | const field = try name_util.formatCase(ctx.arena, .snake, def.code); 64 | try bld.branch().case(bld.x.dot().id(field)).body(bld.x.valueOf(def.code)); 65 | } 66 | } 67 | 68 | test "RegionsCodegen" { 69 | var arena = std.heap.ArenaAllocator.init(std.testing.allocator); 70 | const arena_alloc = arena.allocator(); 71 | defer arena.deinit(); 72 | 73 | var tester = try jobz.PipelineTester.init(.{}); 74 | defer tester.deinit(); 75 | 76 | const output = try codegen_jobs.evaluateZigScript(arena_alloc, tester.pipeline, RegionsCodegen, .{TEST_DEFS}); 77 | try codegen_jobs.expectEqualZigScript(TEST_OUT, output); 78 | } 79 | 80 | const TEST_DEFS = &[_]RegionDef{ 81 | .{ .code = "il-central-1", .description = "Israel (Tel Aviv)" }, 82 | .{ .code = "us-east-1", .description = "US East (N. Virginia)" }, 83 | .{ .code = "aws-cn-global", .description = "AWS China global region" }, 84 | .{ .code = "cn-northwest-1", .description = "China (Ningxia)" }, 85 | .{ .code = "us-gov-west-1", .description = "AWS GovCloud (US-West)" }, 86 | }; 87 | 88 | const TEST_OUT: []const u8 = 89 | \\const std = @import("std"); 90 | \\ 91 | \\pub const Region = enum { 92 | \\ /// Israel (Tel Aviv) 93 | \\ il_central_1, 94 | \\ /// US East (N. Virginia) 95 | \\ us_east_1, 96 | \\ /// AWS China global region 97 | \\ aws_cn_global, 98 | \\ /// China (Ningxia) 99 | \\ cn_northwest_1, 100 | \\ /// AWS GovCloud (US-West) 101 | \\ us_gov_west_1, 102 | \\ 103 | \\ const map = std.StaticStringMap(Region).initComptime(.{ 104 | \\ .{ "il-central-1", .il_central_1 }, 105 | \\ .{ "us-east-1", .us_east_1 }, 106 | \\ .{ "aws-cn-global", .aws_cn_global }, 107 | \\ .{ "cn-northwest-1", .cn_northwest_1 }, 108 | \\ .{ "us-gov-west-1", .us_gov_west_1 }, 109 | \\ }); 110 | \\ 111 | \\ pub fn parse(code: []const u8) ?Region { 112 | \\ return map.get(code); 113 | \\ } 114 | \\ 115 | \\ pub fn toString(self: Region) []const u8 { 116 | \\ return switch (self) { 117 | \\ .il_central_1 => "il-central-1", 118 | \\ .us_east_1 => "us-east-1", 119 | \\ .aws_cn_global => "aws-cn-global", 120 | \\ .cn_northwest_1 => "cn-northwest-1", 121 | \\ .us_gov_west_1 => "us-gov-west-1", 122 | \\ }; 123 | \\ } 124 | \\}; 125 | ; 126 | -------------------------------------------------------------------------------- /aws/codegen/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const fs = std.fs; 3 | const jobz = @import("jobz"); 4 | const Task = jobz.Task; 5 | const Delegate = jobz.Delegate; 6 | const Pipeline = jobz.Pipeline; 7 | const files_jobs = @import("codmod/jobs").files; 8 | const sdk_client = @import("jobs/sdk_client.zig"); 9 | const conf_region = @import("jobs/config_region.zig"); 10 | const conf_partition = @import("jobs/config_partitions.zig"); 11 | 12 | pub fn main() !void { 13 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 14 | const alloc = gpa.allocator(); 15 | defer _ = gpa.deinit(); 16 | 17 | const args = try std.process.argsAlloc(alloc); 18 | defer std.process.argsFree(alloc, args); 19 | if (args.len < 4) { 20 | return error.MissingPathsArgs; 21 | } 22 | 23 | var src_dir = try fs.openDirAbsolute(args[1], .{ .iterate = true }); 24 | defer src_dir.close(); 25 | 26 | var out_aws_dir = try fs.cwd().makeOpenPath(args[2], .{}); 27 | defer out_aws_dir.close(); 28 | 29 | var out_sdk_dir = try fs.cwd().makeOpenPath(args[3], .{}); 30 | defer out_sdk_dir.close(); 31 | 32 | var pipeline = try Pipeline.init(alloc, .{ .invoker = sdk_client.pipeline_invoker }); 33 | defer pipeline.deinit(); 34 | 35 | const whitelist = args[4..args.len]; 36 | try pipeline.runTask(Aws, .{ src_dir, out_aws_dir, out_sdk_dir, whitelist }); 37 | } 38 | 39 | const Aws = Task.Define("AWS", awsTask, .{}); 40 | fn awsTask( 41 | self: *const Delegate, 42 | src_dir: fs.Dir, 43 | out_aws_dir: fs.Dir, 44 | out_sdk_dir: fs.Dir, 45 | whitelist: []const []const u8, 46 | ) !void { 47 | try files_jobs.defineWorkDir(self, out_aws_dir); 48 | 49 | var region_defs = std.ArrayList(conf_region.RegionDef).init(self.alloc()); 50 | 51 | try self.evaluate(conf_partition.Partitions, .{ "partitions.gen.zig", files_jobs.FileOptions{ 52 | .delete_on_error = true, 53 | }, src_dir, ®ion_defs }); 54 | 55 | const RegionsCodegen = files_jobs.WriteFile.Chain(conf_region.RegionsCodegen, .sync); 56 | try self.evaluate(RegionsCodegen, .{ "region.gen.zig", files_jobs.FileOptions{ 57 | .delete_on_error = true, 58 | }, try region_defs.toOwnedSlice() }); 59 | 60 | try files_jobs.overrideWorkDir(self, out_sdk_dir); 61 | try self.evaluate(sdk_client.Sdk, .{ src_dir, whitelist }); 62 | } 63 | 64 | test { 65 | _ = @import("traits.zig"); 66 | _ = @import("integrate/auth.zig"); 67 | _ = @import("integrate/rules.zig"); 68 | _ = @import("integrate/errors.zig"); 69 | _ = @import("integrate/protocols.zig"); 70 | _ = conf_partition; 71 | _ = conf_region; 72 | _ = sdk_client; 73 | } 74 | -------------------------------------------------------------------------------- /aws/codegen/template/README.footer.md.template: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | _The {[title]s} SDK for Zig is auto-generated, **do not modify the source code directly!** 4 | To learn how to contribute see the main [AWS SDK for Zig](/README.md#contributing) repository._ 5 | 6 | ## License 7 | 8 | The author and contributors are not responsible for any issues or damages caused 9 | by the use of this software, part of it, or its derivatives. See [LICENSE](/LICENSE) 10 | for the complete terms of use. 11 | 12 | > [!NOTE] 13 | > _AWS SDK for Zig_ is not an official _Amazon Web Services_ software, nor is it 14 | > affiliated with _Amazon Web Services, Inc_. 15 | 16 | The SDKs code is generated based on a dataset of _Smithy models_ created by 17 | _Amazon Web Services_. The models are extracted from the official [AWS SDK for Rust](https://github.com/awslabs/aws-sdk-rust) 18 | and [licensed](https://github.com/awslabs/aws-sdk-rust/blob/main/LICENSE) as 19 | declared by Amazon Web Services, Inc. at the source repository. 20 | This auto-generated codebase is covered by a [standalone license](/LICENSE). -------------------------------------------------------------------------------- /aws/codegen/template/README.head.md.template: -------------------------------------------------------------------------------- 1 | # {[title]s} – AWS SDK for Zig 2 | 3 | -black?logo=zig&logoColor=F7A41D "Zig v0.14 – master branch") 4 | [](https://github.com/by-nir/aws-sdk-zig/LICENSE) 5 | 6 | For general SDK guidance and setup see the [AWS SDK for Zig](/README.md#getting-started) documentation. 7 | 8 | ## Usage -------------------------------------------------------------------------------- /aws/codegen/template/README.install.md.template: -------------------------------------------------------------------------------- 1 | 2 | ### Installation 3 | 4 | TODO 5 | -------------------------------------------------------------------------------- /aws/codegen/traits.zig: -------------------------------------------------------------------------------- 1 | const smithy = @import("smithy/codegen"); 2 | const trt_iam = @import("traits/iam.zig"); 3 | const trt_auth = @import("traits/auth.zig"); 4 | const trt_core = @import("traits/core.zig"); 5 | const trt_gateway = @import("traits/gateway.zig"); 6 | const trt_endpoint = @import("traits/endpoints.zig"); 7 | const trt_protocol = @import("traits/protocols.zig"); 8 | const trt_cloudformation = @import("traits/cloudformation.zig"); 9 | 10 | pub const aws_traits: smithy.TraitsRegistry = 11 | trt_iam.traits ++ 12 | trt_auth.traits ++ 13 | trt_core.traits ++ 14 | trt_gateway.traits ++ 15 | trt_endpoint.traits ++ 16 | trt_protocol.traits ++ 17 | trt_cloudformation.traits; 18 | 19 | test { 20 | _ = trt_iam; 21 | _ = trt_auth; 22 | _ = trt_core; 23 | _ = trt_gateway; 24 | _ = trt_endpoint; 25 | _ = trt_protocol; 26 | _ = trt_cloudformation; 27 | } 28 | -------------------------------------------------------------------------------- /aws/codegen/traits/auth.zig: -------------------------------------------------------------------------------- 1 | //! AWS Authentication Traits 2 | //! 3 | //! [Smithy Spec](https://smithy.io/2.0/aws/aws-auth.html#aws-authentication-traits) 4 | const std = @import("std"); 5 | const mem = std.mem; 6 | const Allocator = mem.Allocator; 7 | const testing = std.testing; 8 | const test_alloc = testing.allocator; 9 | const smithy = @import("smithy/codegen"); 10 | const SmithyId = smithy.SmithyId; 11 | const JsonReader = smithy.JsonReader; 12 | const TraitsRegistry = smithy.TraitsRegistry; 13 | const SymbolsProvider = smithy.SymbolsProvider; 14 | 15 | // TODO: Remainig traits 16 | pub const traits: TraitsRegistry = &.{ 17 | // aws.auth#cognitoUserPools 18 | .{ SigV4.id, SigV4.parse }, 19 | .{ SigV4A.id, SigV4A.parse }, 20 | .{ unsigned_payload_id, null }, 21 | }; 22 | 23 | /// Adds support for [AWS signature version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) 24 | /// to a service. 25 | /// 26 | /// [Smithy Spec](https://smithy.io/2.0/aws/aws-auth.html#aws-auth-sigv4-trait) 27 | pub const SigV4 = AuthTrait("aws.auth#sigv4"); 28 | 29 | /// Adds support for AWS Signature Version 4 Asymmetric (SigV4A) extension. 30 | /// 31 | /// [Smithy Spec](https://smithy.io/2.0/aws/aws-auth.html#aws-auth-sigv4a-trait) 32 | pub const SigV4A = AuthTrait("aws.auth#sigv4a"); 33 | 34 | /// Indicates that the payload of an operation is not to be part of the signature 35 | /// computed for the request of an operation. 36 | /// 37 | /// [Smithy Spec](https://smithy.io/2.0/aws/aws-auth.html#aws-auth-unsignedpayload-trait) 38 | pub const unsigned_payload_id = SmithyId.of("aws.auth#unsignedPayload"); 39 | 40 | fn AuthTrait(comptime trait_id: []const u8) type { 41 | return struct { 42 | pub const id = SmithyId.of(trait_id); 43 | pub const auth_id = smithy.traits.auth.AuthId.of(trait_id); 44 | 45 | pub const Value = struct { 46 | /// The signing name to use in the [credential scope](https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html) 47 | /// when signing requests. 48 | name: []const u8, 49 | }; 50 | 51 | pub fn parse(arena: Allocator, reader: *JsonReader) !*const anyopaque { 52 | const value = try arena.create(Value); 53 | errdefer arena.destroy(value); 54 | 55 | var required: usize = 1; 56 | try reader.nextObjectBegin(); 57 | while (try reader.peek() != .object_end) { 58 | const prop = try reader.nextString(); 59 | if (mem.eql(u8, prop, "name")) { 60 | value.name = try reader.nextStringAlloc(arena); 61 | required -= 1; 62 | } else { 63 | std.log.warn("Unknown `" ++ trait_id ++ "` trait property `{s}`", .{prop}); 64 | try reader.skipValueOrScope(); 65 | } 66 | } 67 | try reader.nextObjectEnd(); 68 | 69 | if (required > 0) return error.AuthTraitMissingRequiredProperties; 70 | return value; 71 | } 72 | 73 | pub fn get(symbols: *SymbolsProvider, shape_id: SmithyId) ?*const Value { 74 | return symbols.getTrait(Value, shape_id, id); 75 | } 76 | }; 77 | } 78 | 79 | test AuthTrait { 80 | var arena = std.heap.ArenaAllocator.init(test_alloc); 81 | const arena_alloc = arena.allocator(); 82 | defer arena.deinit(); 83 | 84 | var reader = try JsonReader.initFixed(arena_alloc, 85 | \\{ "name": "Foo" } 86 | ); 87 | errdefer reader.deinit(); 88 | 89 | const TestAuth = AuthTrait("smithy.api#testAuth"); 90 | const auth: *const TestAuth.Value = @ptrCast(@alignCast(try TestAuth.parse(arena_alloc, &reader))); 91 | reader.deinit(); 92 | try testing.expectEqualDeep(&TestAuth.Value{ .name = "Foo" }, auth); 93 | } 94 | -------------------------------------------------------------------------------- /aws/codegen/traits/cloudformation.zig: -------------------------------------------------------------------------------- 1 | //! CloudFormation traits are used to describe Smithy resources and their components 2 | //! so they can be converted to [CloudFormation Resource Schemas](https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-schema.html). 3 | //! 4 | //! [Smithy Spec](https://smithy.io/2.0/aws/aws-cloudformation.html#aws-cloudformation-traits) 5 | const smithy = @import("smithy/codegen"); 6 | const TraitsRegistry = smithy.TraitsRegistry; 7 | 8 | // TODO: Remainig traits 9 | pub const traits: TraitsRegistry = &.{ 10 | // aws.cloudformation#cfnAdditionalIdentifier 11 | // aws.cloudformation#cfnDefaultValue 12 | // aws.cloudformation#cfnExcludeProperty 13 | // aws.cloudformation#cfnMutability 14 | // aws.cloudformation#cfnName 15 | // aws.cloudformation#cfnResource 16 | }; 17 | -------------------------------------------------------------------------------- /aws/codegen/traits/core.zig: -------------------------------------------------------------------------------- 1 | //! Various AWS-specific traits are used to integrate Smithy models with other 2 | //! AWS products like AWS CloudFormation and tools like the AWS SDKs. 3 | //! 4 | //! [Smithy Spec](https://smithy.io/2.0/aws/aws-core.html#aws-core-specification) 5 | const std = @import("std"); 6 | const mem = std.mem; 7 | const Allocator = mem.Allocator; 8 | const testing = std.testing; 9 | const test_alloc = testing.allocator; 10 | const smithy = @import("smithy/codegen"); 11 | const SmithyId = smithy.SmithyId; 12 | const SymbolsProvider = smithy.SymbolsProvider; 13 | const TraitsRegistry = smithy.TraitsRegistry; 14 | const JsonReader = smithy.JsonReader; 15 | 16 | // TODO: Remainig traits 17 | pub const traits: TraitsRegistry = &.{ 18 | // aws.api#arn 19 | // aws.api#arnReference 20 | // aws.api#clientDiscoveredEndpoint 21 | // aws.api#clientEndpointDiscovery 22 | // aws.api#clientEndpointDiscoveryId 23 | // aws.api#controlPlane 24 | // aws.api#data 25 | // aws.api#dataPlane 26 | .{ Service.id, Service.parse }, 27 | // aws.api#tagEnabled 28 | // aws.api#taggable 29 | }; 30 | 31 | /// This trait provides information about the service like the name used to 32 | /// generate AWS SDK client classes and the namespace used in ARNs. 33 | /// 34 | /// [Smithy Spec](https://smithy.io/2.0/aws/aws-core.html#aws-api-service-trait) 35 | pub const Service = struct { 36 | pub const id = SmithyId.of("aws.api#service"); 37 | 38 | pub const Value = struct { 39 | /// Specifies the AWS SDK service ID. This value is used for generating 40 | /// client names in SDKs and for linking between services. 41 | sdk_id: []const u8, 42 | /// Specifies the AWS CloudFormation service name. 43 | cloudformation_name: ?[]const u8 = null, 44 | /// Defines the [ARN service namespace](http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html#genref-aws-service-namespaces) 45 | /// of the service. 46 | arn_namespace: ?[]const u8 = null, 47 | /// Defines the AWS customer-facing eventSource property contained in 48 | /// CloudTrail [event records](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-record-contents.html) 49 | /// emitted by the service. 50 | cloud_trail_source: ?[]const u8 = null, 51 | /// Used to implement linking between service and SDK documentation for 52 | /// AWS services. 53 | doc_id: ?[]const u8 = null, 54 | /// Identifies which endpoint in a given region should be used to 55 | /// connect to the service. 56 | endpoint_prefix: ?[]const u8 = null, 57 | }; 58 | 59 | pub fn parse(arena: Allocator, reader: *JsonReader) !*const anyopaque { 60 | const service = try arena.create(Value); 61 | service.* = Value{ .sdk_id = "" }; 62 | 63 | try reader.nextObjectBegin(); 64 | while (try reader.peek() == .string) { 65 | const prop = try reader.nextString(); 66 | const val = try reader.nextStringAlloc(arena); 67 | if (mem.eql(u8, prop, "sdkId")) { 68 | service.sdk_id = val; 69 | } else if (mem.eql(u8, prop, "cloudFormationName")) { 70 | service.cloudformation_name = val; 71 | } else if (mem.eql(u8, prop, "arnNamespace")) { 72 | service.arn_namespace = val; 73 | } else if (mem.eql(u8, prop, "cloudTrailEventSource")) { 74 | service.cloud_trail_source = val; 75 | } else if (mem.eql(u8, prop, "docId")) { 76 | service.doc_id = val; 77 | } else if (mem.eql(u8, prop, "endpointPrefix")) { 78 | service.endpoint_prefix = val; 79 | } else { 80 | unreachable; 81 | } 82 | } 83 | try reader.nextObjectEnd(); 84 | 85 | return service; 86 | } 87 | 88 | pub fn get(symbols: *SymbolsProvider, shape_id: SmithyId) ?*const Value { 89 | return symbols.getTrait(Value, shape_id, id); 90 | } 91 | }; 92 | 93 | test Service { 94 | var arena = std.heap.ArenaAllocator.init(test_alloc); 95 | const arena_alloc = arena.allocator(); 96 | defer arena.deinit(); 97 | 98 | var reader = try JsonReader.initFixed(arena_alloc, 99 | \\{ 100 | \\ "sdkId": "foo", 101 | \\ "cloudFormationName": "bar", 102 | \\ "arnNamespace": "baz", 103 | \\ "cloudTrailEventSource": "qux", 104 | \\ "docId": "108", 105 | \\ "endpointPrefix": "109" 106 | \\} 107 | ); 108 | errdefer reader.deinit(); 109 | 110 | const service: *const Service.Value = @alignCast(@ptrCast(Service.parse(arena_alloc, &reader) catch |e| { 111 | reader.deinit(); 112 | return e; 113 | })); 114 | reader.deinit(); 115 | try testing.expectEqualDeep(&Service.Value{ 116 | .sdk_id = "foo", 117 | .cloudformation_name = "bar", 118 | .arn_namespace = "baz", 119 | .cloud_trail_source = "qux", 120 | .doc_id = "108", 121 | .endpoint_prefix = "109", 122 | }, service); 123 | } 124 | -------------------------------------------------------------------------------- /aws/codegen/traits/endpoints.zig: -------------------------------------------------------------------------------- 1 | //! AWS Declarative Endpoint Traits 2 | //! 3 | //! [Smithy Spec](https://smithy.io/2.0/aws/aws-endpoints-region.html#aws-declarative-endpoint-traits) 4 | const smithy = @import("smithy/codegen"); 5 | const TraitsRegistry = smithy.TraitsRegistry; 6 | 7 | // TODO: Remainig traits 8 | pub const traits: TraitsRegistry = &.{ 9 | // aws.endpoints#dualStackOnlyEndpoints 10 | // aws.endpoints#endpointsModifier 11 | // aws.endpoints#rulesBasedEndpoints 12 | // aws.endpoints#standardPartitionalEndpoints 13 | // aws.endpoints#standardRegionalEndpoints 14 | }; 15 | -------------------------------------------------------------------------------- /aws/codegen/traits/gateway.zig: -------------------------------------------------------------------------------- 1 | //! Smithy can integrate with Amazon API Gateway using traits, authentication 2 | //! schemes, and OpenAPI specifications. 3 | //! 4 | //! [Smithy Spec](https://smithy.io/2.0/aws/amazon-apigateway.html#amazon-api-gateway-traits) 5 | const smithy = @import("smithy/codegen"); 6 | const TraitsRegistry = smithy.TraitsRegistry; 7 | 8 | // TODO: Remainig traits 9 | pub const traits: TraitsRegistry = &.{ 10 | // aws.apigateway#apiKeySource 11 | // aws.apigateway#authorizer 12 | // aws.apigateway#authorizers 13 | // aws.apigateway#integration 14 | // aws.apigateway#mockIntegration 15 | // aws.apigateway#requestValidator 16 | }; 17 | -------------------------------------------------------------------------------- /aws/codegen/traits/iam.zig: -------------------------------------------------------------------------------- 1 | //! IAM Policy Traits are used to describe the permission structure of a service 2 | //! in relation to AWS IAM. Services integrated with AWS IAM define resource types, 3 | //! actions, and condition keys that IAM users can use to construct IAM policies. 4 | //! 5 | //! [Smithy Spec](https://smithy.io/2.0/aws/aws-iam.html#aws-iam-traits) 6 | const smithy = @import("smithy/codegen"); 7 | const TraitsRegistry = smithy.TraitsRegistry; 8 | 9 | // TODO: Remainig traits 10 | pub const traits: TraitsRegistry = &.{ 11 | // aws.iam#actionName 12 | // aws.iam#actionPermissionDescription 13 | // aws.iam#conditionKeyValue 14 | // aws.iam#conditionKeys 15 | // aws.iam#defineConditionKeys 16 | // aws.iam#disableConditionKeyInference 17 | // aws.iam#iamAction 18 | // aws.iam#iamResource 19 | // aws.iam#requiredActions 20 | // aws.iam#serviceResolvedConditionKeys 21 | // aws.iam#supportedPrincipalTypes 22 | }; 23 | -------------------------------------------------------------------------------- /aws/runtime/auth/schemes.zig: -------------------------------------------------------------------------------- 1 | //! https://github.com/awslabs/aws-c-auth 2 | const std = @import("std"); 3 | const mem = std.mem; 4 | const testing = std.testing; 5 | const smithy = @import("smithy/runtime"); 6 | const sig = @import("sigv4.zig"); 7 | const http = @import("../http.zig"); 8 | const hashing = @import("../utils/hashing.zig"); 9 | const Region = @import("../infra/region.gen.zig").Region; 10 | const Credentials = @import("identity.zig").Credentials; 11 | 12 | const log = std.log.scoped(.aws_sdk); 13 | 14 | pub const SigV4Scheme = struct { 15 | /// The _service_ value to use when creating a signing string for this endpoint. 16 | signing_name: []const u8, 17 | /// The _region_ value to use when creating a signing string for this endpoint. 18 | signing_region: []const u8, 19 | /// When `true` clients must not double-escape the path during signing. 20 | disable_double_encoding: bool = false, 21 | /// When `true` clients must not perform any path normalization during signing. 22 | disable_normalize_path: bool = false, 23 | 24 | pub fn evaluate(service: []const u8, region: []const u8, endpoint: ?smithy.AuthScheme) SigV4Scheme { 25 | var scheme = SigV4Scheme{ 26 | .signing_name = service, 27 | .signing_region = region, 28 | }; 29 | 30 | const override = endpoint orelse return scheme; 31 | 32 | std.debug.assert(override.id == smithy.AuthId.of("sigv4")); 33 | for (override.properties) |prop| { 34 | if (mem.eql(u8, "signingName", prop.key)) { 35 | scheme.signing_name = prop.document.getString(); 36 | } else if (mem.eql(u8, "signingRegion", prop.key)) { 37 | scheme.signing_region = prop.document.getString(); 38 | } else if (mem.eql(u8, "disableDoubleEncoding", prop.key)) { 39 | scheme.disable_double_encoding = prop.document.boolean; 40 | } else if (mem.eql(u8, "disableNormalizePath", prop.key)) { 41 | scheme.disable_normalize_path = prop.document.boolean; 42 | } else { 43 | log.warn("Unknown property in the resolved endpoint’s 'sigv4' auth scheme: {s}.", .{prop.key}); 44 | } 45 | } 46 | return scheme; 47 | } 48 | }; 49 | 50 | // TODO: SigV4Scheme.disable_double_encoding, SigV4Scheme.disable_normalize_path 51 | pub fn signV4( 52 | buffer: *sig.SignBuffer, 53 | op: *http.Operation, 54 | scheme: SigV4Scheme, 55 | creds: Credentials, 56 | skip_payload: bool, 57 | ) !void { 58 | const req = &op.request; 59 | const path = if (req.endpoint.path.isEmpty()) "/" else req.endpoint.path.raw; 60 | 61 | var values_buff: http.HeadersRawBuffer = undefined; 62 | const headers = try req.stringifyHeaders(&values_buff); 63 | 64 | var names_buff: http.QueryBuffer = undefined; 65 | const names = try req.stringifyHeadNames(&names_buff); 66 | 67 | var query_buff: http.QueryBuffer = undefined; 68 | const query = try req.stringifyQuery(&query_buff); 69 | 70 | var payload_hash: hashing.HashStr = undefined; 71 | hashing.hashString(&payload_hash, if (skip_payload) "UNSIGNED-PAYLOAD" else req.payload); 72 | 73 | const target = sig.Target{ 74 | .service = scheme.signing_name, 75 | .region = scheme.signing_region, 76 | }; 77 | 78 | const content = sig.Content{ 79 | .method = req.method, 80 | .path = path, 81 | .query = query, 82 | .headers = headers, 83 | .headers_names = names, 84 | .payload_hash = &payload_hash, 85 | }; 86 | 87 | const signature = try sig.signV4(buffer, creds, op.time, target, content); 88 | try req.putHeader(op.allocator, "authorization", signature); 89 | 90 | if (creds.session_token) |token| { 91 | try req.putHeader(op.allocator, "x-amz-security-token", token); 92 | } 93 | } 94 | 95 | pub const SigV4AScheme = struct { 96 | /// The _service_ value to use when creating a signing string for this endpoint. 97 | signing_name: []const u8, 98 | /// The set of signing regions to use when creating a signing string for this endpoint. 99 | signing_region_set: []const []const u8, 100 | /// When `true` clients must not double-escape the path during signing. 101 | disable_double_encoding: bool = false, 102 | /// When `true` clients must not perform any path normalization during signing. 103 | disable_normalize_path: bool = false, 104 | 105 | pub fn evaluate(endpoint: smithy.AuthScheme, service: []const u8, region: []const u8) SigV4AScheme { 106 | _ = region; // autofix 107 | std.debug.assert(endpoint.id == smithy.AuthId.of("sigv4a")); 108 | var scheme = SigV4AScheme{ 109 | .signing_name = service, 110 | .signing_region_set = &.{}, 111 | }; 112 | 113 | var visited_regions = false; 114 | for (endpoint.properties) |prop| { 115 | if (mem.eql(u8, "signingName", prop.key)) { 116 | scheme.signing_name = prop.document.getString(); 117 | } else if (mem.eql(u8, "signingRegionSet", prop.key)) { 118 | visited_regions = true; 119 | unreachable; // TODO: scheme.signing_region_set = ; 120 | } else if (mem.eql(u8, "disableDoubleEncoding", prop.key)) { 121 | scheme.disable_double_encoding = prop.document.boolean; 122 | } else if (mem.eql(u8, "disableNormalizePath", prop.key)) { 123 | scheme.disable_normalize_path = prop.document.boolean; 124 | } else { 125 | log.warn("Unknown property in the resolved endpoint’s 'sigv4a' auth scheme: {s}.", .{prop.key}); 126 | } 127 | } 128 | 129 | std.debug.assert(visited_regions); 130 | return scheme; 131 | } 132 | }; 133 | -------------------------------------------------------------------------------- /aws/runtime/config/env.zig: -------------------------------------------------------------------------------- 1 | //! https://docs.aws.amazon.com/sdkref/latest/guide/settings-reference.html#EVarSettings 2 | const std = @import("std"); 3 | const ZigType = std.builtin.Type; 4 | const Allocator = std.mem.Allocator; 5 | const testing = std.testing; 6 | const test_alloc = testing.allocator; 7 | const Entry = @import("entries.zig").Entry; 8 | const entries = @import("entries.zig").env_entries; 9 | const SharedResource = @import("../utils/SharedResource.zig"); 10 | 11 | var tracker = SharedResource{}; 12 | var shared_aws: AwsEnv = undefined; 13 | var shared_raw: std.process.EnvMap = undefined; 14 | 15 | pub fn loadEnvironment(allocator: Allocator) !AwsEnv { 16 | try tracker.retainCallback(onLoad, allocator); 17 | return shared_aws; 18 | } 19 | 20 | pub fn releaseEnvironment() void { 21 | tracker.releaseCallback(onRelease, {}); 22 | } 23 | 24 | /// Assumes values were previously loaded. 25 | pub fn readValue(comptime field: std.meta.FieldEnum(AwsEnv)) std.meta.FieldType(AwsEnv, field) { 26 | std.debug.assert(tracker.countSafe() > 0); 27 | return @field(shared_aws, @tagName(field)); 28 | } 29 | 30 | /// Assumes values were previously loaded. 31 | pub fn overrideValue(comptime field: std.meta.FieldEnum(AwsEnv), value: std.meta.FieldType(AwsEnv, field)) void { 32 | std.debug.assert(tracker.countSafe() > 0); 33 | @field(shared_aws, @tagName(field)) = value; 34 | } 35 | 36 | fn onRelease(_: void) void { 37 | shared_raw.deinit(); 38 | shared_raw = undefined; 39 | shared_aws = undefined; 40 | } 41 | 42 | fn onLoad(allocator: Allocator) !void { 43 | shared_raw = try std.process.getEnvMap(allocator); 44 | errdefer { 45 | shared_raw.deinit(); 46 | shared_raw = undefined; 47 | } 48 | 49 | shared_aws = .{}; 50 | errdefer shared_aws = undefined; 51 | 52 | var it = shared_raw.iterator(); 53 | while (it.next()) |pair| { 54 | const key = pair.key_ptr.*; 55 | const entry = entries.get(key) orelse continue; 56 | 57 | inline for (comptime entries.values()) |e| { 58 | if (std.mem.eql(u8, e.field, entry.field)) { 59 | const str_val = pair.value_ptr.*; 60 | @field(shared_aws, e.field) = try e.parse(str_val); 61 | break; 62 | } 63 | } 64 | } 65 | } 66 | 67 | const AwsEnv: type = blk: { 68 | var fields_len: usize = 0; 69 | var fields: [entries.kvs.len]ZigType.StructField = undefined; 70 | 71 | for (0..entries.kvs.len) |i| { 72 | const entry = entries.kvs.values[i]; 73 | 74 | var name: [entry.field.len:0]u8 = undefined; 75 | @memcpy(name[0..entry.field.len], entry.field); 76 | 77 | const T = entry.Type(); 78 | const default_value: ?T = null; 79 | fields[fields_len] = ZigType.StructField{ 80 | .name = &name, 81 | .type = ?T, 82 | .default_value = &default_value, 83 | .is_comptime = false, 84 | .alignment = @alignOf(?T), 85 | }; 86 | fields_len += 1; 87 | } 88 | 89 | break :blk @Type(.{ .@"struct" = .{ 90 | .layout = .auto, 91 | .fields = fields[0..fields_len], 92 | .decls = &.{}, 93 | .is_tuple = false, 94 | } }); 95 | }; 96 | 97 | test { 98 | _ = try loadEnvironment(test_alloc); 99 | defer releaseEnvironment(); 100 | 101 | try testing.expectEqual(null, readValue(.ua_app_id)); 102 | overrideValue(.ua_app_id, "foo"); 103 | try testing.expectEqual("foo", readValue(.ua_app_id)); 104 | 105 | const env = loadEnvironment(test_alloc); 106 | defer releaseEnvironment(); 107 | 108 | try testing.expectEqualDeep(AwsEnv{ 109 | .ua_app_id = "foo", 110 | }, env); 111 | } 112 | -------------------------------------------------------------------------------- /aws/runtime/root.zig: -------------------------------------------------------------------------------- 1 | const smithy = @import("smithy/runtime"); 2 | pub const Set = smithy.Set; 3 | pub const Map = smithy.Map; 4 | pub const Result = smithy.Result; 5 | pub const Timestamp = smithy.Timestamp; 6 | pub const ResultError = smithy.ResultError; 7 | pub const ErrorSource = smithy.ErrorSource; 8 | 9 | const endpoint = @import("infra/endpoint.zig"); 10 | const partition = @import("infra/partitions.gen.zig"); 11 | const region = @import("infra/region.gen.zig"); 12 | pub const Region = region.Region; 13 | 14 | const conf = @import("config.zig"); 15 | pub const Config = conf.Config; 16 | pub const SharedConfig = conf.SharedConfig; 17 | pub const ConfigOptions = conf.ConfigOptions; 18 | 19 | const http = @import("http.zig"); 20 | pub const HttpClient = http.SharedClient; 21 | 22 | const identity = @import("auth/identity.zig"); 23 | pub const IdentityManager = identity.SharedManager; 24 | pub const StandardIdentity = identity.StandardCredsProvider; 25 | pub const StaticIdentity = identity.StaticCredsProvider; 26 | pub const EnvIdentity = identity.EnvironmentCredsProvider; 27 | pub const SharedFilesIdentity = identity.SharedFilesProvider; 28 | 29 | const auth_sign = @import("auth/sigv4.zig"); 30 | const auth_schemes = @import("auth/schemes.zig"); 31 | 32 | const protocol_http = @import("protocols/http.zig"); 33 | const protocol_json = @import("protocols/json.zig"); 34 | const protocol_xml = @import("protocols/xml.zig"); 35 | const protocol_query = @import("protocols/query.zig"); 36 | 37 | pub const _private_ = struct { 38 | pub const ClientConfig = conf.ClientConfig; 39 | pub const ClientRequest = http.Request; 40 | pub const ClientResponse = http.Response; 41 | pub const ClientOperation = http.Operation; 42 | pub const HttpClient = http.Client; 43 | pub const IdentityManager = identity.Manager; 44 | pub const Arn = endpoint.Arn; 45 | pub const Partition = endpoint.Partition; 46 | pub const isVirtualHostableS3Bucket = endpoint.isVirtualHostableS3Bucket; 47 | pub const resolvePartition = partition.resolve; 48 | pub const SignBuffer = auth_sign.SignBuffer; 49 | pub const auth = auth_schemes; 50 | pub const protocol = struct { 51 | pub const http = protocol_http; 52 | pub const json = protocol_json; 53 | pub const xml = protocol_xml; 54 | pub const query = protocol_query; 55 | }; 56 | }; 57 | 58 | test { 59 | _ = @import("utils/url.zig"); 60 | _ = @import("utils/hashing.zig"); 61 | _ = @import("utils/TimeStr.zig"); 62 | _ = @import("utils/SharedResource.zig"); 63 | _ = @import("config/entries.zig"); 64 | _ = @import("config/env.zig"); 65 | _ = @import("config/profile.zig"); 66 | _ = region; 67 | _ = endpoint; 68 | _ = partition; 69 | _ = _private_.ClientOperation; 70 | _ = identity; 71 | _ = auth_schemes; 72 | _ = @import("auth/sigv4.zig"); 73 | _ = protocol_http; 74 | _ = protocol_json; 75 | _ = protocol_xml; 76 | _ = protocol_query; 77 | _ = http; 78 | _ = conf; 79 | } 80 | -------------------------------------------------------------------------------- /aws/runtime/testing/aws_config: -------------------------------------------------------------------------------- 1 | aws_session_token=IQoJb3JpZ2luX2IQoJb3JpZ2luX2IQoJb3JpZ2luX2IQoJb3JpZ2luX2IQoJb3JpZVERYLONGSTRINGEXAMPLE 2 | -------------------------------------------------------------------------------- /aws/runtime/testing/aws_credentials: -------------------------------------------------------------------------------- 1 | [default] 2 | aws_access_key_id=AKIAIOSFODNN7EXAMPLE 3 | aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 4 | -------------------------------------------------------------------------------- /aws/runtime/utils/SharedResource.zig: -------------------------------------------------------------------------------- 1 | //! Thread safe reference counter. 2 | const std = @import("std"); 3 | const testing = std.testing; 4 | 5 | const Self = @This(); 6 | 7 | count: usize = 0, 8 | mutex: std.Thread.Mutex = .{}, 9 | 10 | /// Returns the current reference count. 11 | /// Waits for all mutations to complete before reading the value. 12 | pub fn countSafe(self: *Self) usize { 13 | self.mutex.lock(); 14 | defer self.mutex.unlock(); 15 | 16 | return self.count; 17 | } 18 | 19 | /// Returns `true` when the first reference is created. 20 | pub fn retain(self: *Self) bool { 21 | self.mutex.lock(); 22 | defer self.mutex.unlock(); 23 | std.debug.assert(self.count < std.math.maxInt(usize)); 24 | 25 | defer self.count += 1; 26 | return self.count == 0; 27 | } 28 | 29 | /// Returns `true` when the last reference is released. 30 | pub fn release(self: *Self) bool { 31 | self.mutex.lock(); 32 | defer self.mutex.unlock(); 33 | std.debug.assert(self.count > 0); 34 | 35 | defer self.count -= 1; 36 | return self.count == 1; 37 | } 38 | 39 | /// Runs a callback when the first reference is created. 40 | /// The resource is locked during the callback invocation. 41 | /// The counter will not increase if the callback returns an error. 42 | pub fn retainCallback(self: *Self, cb: anytype, ctx: Cb.of(cb).Arg) Cb.of(cb).Return { 43 | self.mutex.lock(); 44 | defer self.mutex.unlock(); 45 | std.debug.assert(self.count < std.math.maxInt(usize)); 46 | 47 | if (self.count == 0) switch (Cb.of(cb).Return) { 48 | void => cb(ctx), 49 | else => try cb(ctx), 50 | }; 51 | 52 | self.count += 1; 53 | } 54 | 55 | /// Runs a callback when the last reference is released. 56 | /// The resource is locked during the callback invocation. 57 | /// The counter will not decrease if the callback returns an error. 58 | pub fn releaseCallback(self: *Self, cb: anytype, ctx: Cb.of(cb).Arg) Cb.of(cb).Return { 59 | self.mutex.lock(); 60 | defer self.mutex.unlock(); 61 | std.debug.assert(self.count > 0); 62 | 63 | if (self.count == 1) switch (Cb.of(cb).Return) { 64 | void => cb(ctx), 65 | else => try cb(ctx), 66 | }; 67 | 68 | self.count -= 1; 69 | } 70 | 71 | const Cb = struct { 72 | Arg: type, 73 | Return: type, 74 | 75 | fn of(comptime cb: anytype) Cb { 76 | const meta = switch (@typeInfo(@TypeOf(cb))) { 77 | .@"fn" => |m| m, 78 | .pointer => |m| blk: { 79 | const child = @typeInfo(m.child); 80 | if (m.size != .One or child != .@"fn") @compileError("Callback must be a function."); 81 | break :blk child.Fn; 82 | }, 83 | else => @compileError("Callback must be a function."), 84 | }; 85 | 86 | if (meta.params.len != 1) @compileError("Callback function must have exactly a single argument."); 87 | 88 | const Return = meta.return_type.?; 89 | const valid = switch (@typeInfo(Return)) { 90 | .void => true, 91 | .error_union => |m| m.payload == void, 92 | else => false, 93 | }; 94 | if (!valid) @compileError("Callback function must return `void` or an error union with `void` payload."); 95 | 96 | return .{ 97 | .Arg = meta.params[0].type.?, 98 | .Return = Return, 99 | }; 100 | } 101 | }; 102 | 103 | test { 104 | const Demo = struct { 105 | var value: usize = 0; 106 | var should_fail = false; 107 | 108 | fn cbSafe(val: usize) void { 109 | value = val; 110 | } 111 | 112 | fn cbFailable(val: usize) !void { 113 | if (should_fail) return error.Fail; 114 | value = val; 115 | } 116 | }; 117 | 118 | var resource: Self = .{}; 119 | 120 | try testing.expectEqual(true, resource.retain()); 121 | try testing.expectEqual(1, resource.countSafe()); 122 | try testing.expectEqual(false, resource.retain()); 123 | try testing.expectEqual(false, resource.retain()); 124 | try testing.expectEqual(3, resource.countSafe()); 125 | try testing.expectEqual(false, resource.release()); 126 | try testing.expectEqual(2, resource.countSafe()); 127 | try testing.expectEqual(false, resource.release()); 128 | try testing.expectEqual(true, resource.release()); 129 | try testing.expectEqual(0, resource.countSafe()); 130 | 131 | Demo.value = 0; 132 | Demo.should_fail = false; 133 | 134 | resource.retainCallback(Demo.cbSafe, 1); 135 | try testing.expectEqual(1, resource.countSafe()); 136 | try testing.expectEqual(1, Demo.value); 137 | resource.retainCallback(Demo.cbSafe, 2); 138 | resource.retainCallback(Demo.cbSafe, 3); 139 | try testing.expectEqual(3, resource.countSafe()); 140 | try testing.expectEqual(1, Demo.value); 141 | 142 | resource.releaseCallback(Demo.cbSafe, 4); 143 | try testing.expectEqual(2, resource.countSafe()); 144 | try testing.expectEqual(1, Demo.value); 145 | resource.releaseCallback(Demo.cbSafe, 5); 146 | resource.releaseCallback(Demo.cbSafe, 6); 147 | try testing.expectEqual(0, resource.countSafe()); 148 | try testing.expectEqual(6, Demo.value); 149 | 150 | Demo.should_fail = true; 151 | try testing.expectError(error.Fail, resource.retainCallback(Demo.cbFailable, 7)); 152 | try testing.expectEqual(0, resource.countSafe()); 153 | try testing.expectEqual(6, Demo.value); 154 | 155 | Demo.should_fail = false; 156 | try resource.retainCallback(Demo.cbFailable, 8); 157 | try testing.expectEqual(1, resource.countSafe()); 158 | try testing.expectEqual(8, Demo.value); 159 | } 160 | -------------------------------------------------------------------------------- /aws/runtime/utils/TimeStr.zig: -------------------------------------------------------------------------------- 1 | //! UTC time string. 2 | const std = @import("std"); 3 | const testing = std.testing; 4 | 5 | const Self = @This(); 6 | pub const Date = *const [8]u8; 7 | pub const Timestamp = *const [16]u8; 8 | 9 | value: [16]u8, 10 | 11 | pub fn now() Self { 12 | const secs: u64 = @intCast(std.time.timestamp()); 13 | return sinceEpoch(secs); 14 | } 15 | 16 | /// Seconds since the Unix epoch. 17 | pub fn sinceEpoch(seconds: u64) Self { 18 | const epoch_sec = std.time.epoch.EpochSeconds{ .secs = seconds }; 19 | const year_day = epoch_sec.getEpochDay().calculateYearDay(); 20 | const month_day = year_day.calculateMonthDay(); 21 | const day_secs = epoch_sec.getDaySeconds(); 22 | 23 | var self: Self = undefined; 24 | _ = std.fmt.bufPrint(&self.value, "{d:0>4}{d:0>2}{d:0>2}T{d:0>2}{d:0>2}{d:0>2}Z", .{ 25 | year_day.year, 26 | month_day.month.numeric(), 27 | month_day.day_index + 1, 28 | day_secs.getHoursIntoDay(), 29 | day_secs.getMinutesIntoHour(), 30 | day_secs.getSecondsIntoMinute(), 31 | }) catch unreachable; 32 | 33 | return self; 34 | } 35 | 36 | /// Format: `yyyymmdd` 37 | pub fn date(self: *const Self) Date { 38 | return self.value[0..8]; 39 | } 40 | 41 | /// Format: `yyyymmddThhmmssZ` 42 | pub fn timestamp(self: *const Self) Timestamp { 43 | return &self.value; 44 | } 45 | 46 | test { 47 | const time = Self.sinceEpoch(1373321335); 48 | try testing.expectEqualStrings("20130708", time.date()); 49 | try testing.expectEqualStrings("20130708T220855Z", time.timestamp()); 50 | } 51 | -------------------------------------------------------------------------------- /aws/runtime/utils/hashing.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | pub const Sha256 = std.crypto.hash.sha2.Sha256; 5 | pub const Hmac256 = std.crypto.auth.hmac.sha2.HmacSha256; 6 | 7 | const HASH_LEN = Sha256.digest_length; 8 | pub const HashBytes = [HASH_LEN]u8; 9 | pub const HashStr = [HASH_LEN * 2]u8; 10 | 11 | const EMPTY_HASH = [_]u8{ 12 | 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24, 13 | 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55, 14 | }; 15 | const EMPTY_HASH_STR = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; 16 | 17 | /// Hash a payload into a 256-bit hash (SHA-256). 18 | pub fn hash256(out: *HashBytes, payload: []const u8) void { 19 | if (payload.len > 0) { 20 | Sha256.hash(payload, out, .{}); 21 | } else { 22 | @memcpy(out, &EMPTY_HASH); 23 | } 24 | } 25 | 26 | test "hash256" { 27 | var buffer: HashBytes = undefined; 28 | hash256(&buffer, &.{}); 29 | try testing.expectEqualStrings(&EMPTY_HASH, &buffer); 30 | 31 | hash256(&buffer, "foo-bar-baz"); 32 | try testing.expectEqualSlices(u8, &.{ 33 | 0x26, 0x9D, 0xCE, 0x1A, 0x5B, 0xB9, 0x01, 0x88, 0xB2, 0xD9, 0xCF, 0x54, 0x2A, 0x7C, 0x30, 0xE4, 34 | 0x10, 0xC7, 0xD8, 0x25, 0x1E, 0x34, 0xA9, 0x7B, 0xFE, 0xA5, 0x60, 0x62, 0xDF, 0x51, 0xAE, 0x23, 35 | }, &buffer); 36 | } 37 | 38 | /// Hash a payload into a 64 lower-case hexdecimal characters. 39 | pub fn hashString(out: *HashStr, payload: ?[]const u8) void { 40 | std.debug.assert(out.len >= 2 * HASH_LEN); 41 | if (payload) |pld| if (pld.len > 0) { 42 | var s256: HashBytes = undefined; 43 | hash256(&s256, pld); 44 | _ = hexString(out, .lower, &s256) catch unreachable; 45 | return; 46 | }; 47 | 48 | // Null or empty 49 | @memcpy(out, EMPTY_HASH_STR); 50 | } 51 | 52 | test "hashString" { 53 | var buffer: HashStr = undefined; 54 | hashString(&buffer, ""); 55 | try testing.expectEqualStrings(EMPTY_HASH_STR, &buffer); 56 | 57 | hashString(&buffer, "foo-bar-baz"); 58 | try testing.expectEqualStrings( 59 | "269dce1a5bb90188b2d9cf542a7c30e410c7d8251e34a97bfea56062df51ae23", 60 | &buffer, 61 | ); 62 | } 63 | 64 | /// The output buffer must be at-least double the length of the input payload. 65 | pub fn hexString(buffer: []u8, case: std.fmt.Case, payload: []const u8) ![]const u8 { 66 | var stream = std.io.fixedBufferStream(buffer); 67 | switch (case) { 68 | .lower => try stream.writer().print("{}", .{std.fmt.fmtSliceHexLower(payload)}), 69 | .upper => try stream.writer().print("{}", .{std.fmt.fmtSliceHexUpper(payload)}), 70 | } 71 | return stream.getWritten(); 72 | } 73 | 74 | test "hexString" { 75 | const payload: []const u8 = &.{ 76 | 0x26, 0x9D, 0xCE, 0x1A, 0x5B, 0xB9, 0x01, 0x88, 0xB2, 0xD9, 0xCF, 0x54, 0x2A, 0x7C, 0x30, 0xE4, 77 | 0x10, 0xC7, 0xD8, 0x25, 0x1E, 0x34, 0xA9, 0x7B, 0xFE, 0xA5, 0x60, 0x62, 0xDF, 0x51, 0xAE, 0x23, 78 | }; 79 | 80 | var buffer: HashStr = undefined; 81 | try testing.expectEqualStrings( 82 | "269dce1a5bb90188b2d9cf542a7c30e410c7d8251e34a97bfea56062df51ae23", 83 | try hexString(&buffer, .lower, payload), 84 | ); 85 | try testing.expectEqualStrings( 86 | "269DCE1A5BB90188B2D9CF542A7C30E410C7D8251E34A97BFEA56062DF51AE23", 87 | try hexString(&buffer, .upper, payload), 88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /aws/runtime/utils/url.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const Allocator = std.mem.Allocator; 4 | 5 | /// Applies URI encoding and replaces all reserved characters with their respective %XX code. 6 | /// 7 | /// Based on an older Zig implementation: 8 | /// https://github.com/ziglang/zig/blob/4e2570baafb587c679ee0fc5e113ddeb36522a5d/lib/std/Uri.zig 9 | pub fn escapeUri(allocator: Allocator, input: []const u8) Allocator.Error![]u8 { 10 | var outsize: usize = 0; 11 | for (input) |c| { 12 | outsize += if (isUnreserved(c)) @as(usize, 1) else 3; 13 | } 14 | var output = try allocator.alloc(u8, outsize); 15 | var outptr: usize = 0; 16 | 17 | for (input) |c| { 18 | if (isUnreserved(c)) { 19 | output[outptr] = c; 20 | outptr += 1; 21 | } else { 22 | var buf: [2]u8 = undefined; 23 | _ = std.fmt.bufPrint(&buf, "{X:0>2}", .{c}) catch unreachable; 24 | 25 | output[outptr + 0] = '%'; 26 | output[outptr + 1] = buf[0]; 27 | output[outptr + 2] = buf[1]; 28 | outptr += 3; 29 | } 30 | } 31 | return output; 32 | } 33 | 34 | /// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 35 | fn isUnreserved(c: u8) bool { 36 | return switch (c) { 37 | 'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_', '~' => true, 38 | else => false, 39 | }; 40 | } 41 | 42 | /// Applies URI encoding and replaces all reserved characters with their respective %XX code. 43 | /// 44 | /// Based on an older Zig implementation: 45 | /// https://github.com/ziglang/zig/blob/4e2570baafb587c679ee0fc5e113ddeb36522a5d/lib/std/Uri.zig 46 | pub const UrlEncodeFormat = struct { 47 | value: []const u8, 48 | 49 | pub fn format(self: UrlEncodeFormat, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { 50 | for (self.value) |c| { 51 | if (isUnreserved(c)) { 52 | try writer.writeByte(c); 53 | } else { 54 | try writer.print("%{X:0>2}", .{c}); 55 | } 56 | } 57 | } 58 | }; 59 | 60 | test UrlEncodeFormat { 61 | try testing.expectFmt("foo%20bar", "{}", .{UrlEncodeFormat{ .value = "foo bar" }}); 62 | } 63 | -------------------------------------------------------------------------------- /build.codegen.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const default_whitelist = [_][]const u8{ "cloudcontrol", "cloudfront", "sns", "sqs" }; 3 | 4 | pub fn build(b: *std.Build) void { 5 | const whitelist = b.option( 6 | []const []const u8, 7 | "filter", 8 | "Whitelist the services to generate", 9 | ); 10 | 11 | const codegen_artifact = b.dependency("aws", .{ 12 | .target = b.graph.host, 13 | .optimize = .Debug, 14 | }).artifact("aws-codegen"); 15 | 16 | const aws_codegen = b.addRunArtifact(codegen_artifact); 17 | if (b.lazyDependency("aws-models", .{})) |models| { 18 | const src_dir = models.path("sdk"); 19 | aws_codegen.addDirectoryArg(src_dir); 20 | } 21 | 22 | const aws_out_dir = aws_codegen.addOutputDirectoryArg("aws"); 23 | const sdk_out_dir = aws_codegen.addOutputDirectoryArg("sdk"); 24 | aws_codegen.addArgs(whitelist orelse &default_whitelist); 25 | b.getInstallStep().dependOn(&aws_codegen.step); 26 | b.installDirectory(.{ 27 | .source_dir = aws_out_dir, 28 | .install_dir = .prefix, 29 | .install_subdir = "../aws/runtime/infra", 30 | }); 31 | b.installDirectory(.{ 32 | .source_dir = sdk_out_dir, 33 | .install_dir = .prefix, 34 | .install_subdir = "../sdk", 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Build = std.Build; 3 | 4 | const Options = struct { 5 | target: Build.ResolvedTarget, 6 | optimize: std.builtin.OptimizeMode, 7 | }; 8 | 9 | pub fn build(b: *std.Build) void { 10 | const target = b.standardTargetOptions(.{}); 11 | const optimize = b.standardOptimizeOption(.{}); 12 | 13 | const smithy = b.dependency("smithy", .{ 14 | .target = target, 15 | .optimize = optimize, 16 | }).module("runtime"); 17 | 18 | const aws_runtime = b.dependency("aws", .{ 19 | .target = target, 20 | .optimize = optimize, 21 | }).module("runtime"); 22 | 23 | // 24 | // SDK 25 | // 26 | 27 | b.modules.put("aws", aws_runtime) catch {}; 28 | 29 | const sdk_path = "sdk"; 30 | var sdk_dir = std.fs.openDirAbsolute(b.path(sdk_path).getPath(b), .{ .iterate = true }) catch { 31 | @panic("Open dir error"); 32 | }; 33 | defer sdk_dir.close(); 34 | 35 | // Services 36 | var it = sdk_dir.iterateAssumeFirstIteration(); 37 | while (it.next() catch @panic("Dir iterator error")) |entry| { 38 | if (entry.kind != .directory) continue; 39 | addSdkClient(b, .{ 40 | .target = target, 41 | .optimize = optimize, 42 | }, sdk_path, entry.name, smithy, aws_runtime); 43 | } 44 | } 45 | 46 | fn addSdkClient( 47 | b: *std.Build, 48 | options: Options, 49 | dir: []const u8, 50 | name: []const u8, 51 | smithy: *Build.Module, 52 | runtime: *Build.Module, 53 | ) void { 54 | // Client 55 | const path = b.path(b.fmt("{s}/{s}/client.zig", .{ dir, name })); 56 | _ = b.addModule( 57 | b.fmt("sdk/{s}", .{name}), 58 | .{ 59 | .target = options.target, 60 | .optimize = options.optimize, 61 | .root_source_file = path, 62 | .imports = &.{ 63 | .{ .name = "smithy", .module = smithy }, 64 | .{ .name = "aws-runtime", .module = runtime }, 65 | }, 66 | }, 67 | ); 68 | 69 | // Tests 70 | const test_step = b.step( 71 | b.fmt("test:sdk-{s}", .{name}), 72 | b.fmt("Run `{s}` SDK unit tests", .{name}), 73 | ); 74 | const unit_tests = b.addTest(.{ 75 | .target = options.target, 76 | .optimize = options.optimize, 77 | .root_source_file = path, 78 | }); 79 | unit_tests.root_module.addImport("smithy", smithy); 80 | unit_tests.root_module.addImport("aws-runtime", runtime); 81 | test_step.dependOn(&b.addRunArtifact(unit_tests).step); 82 | } 83 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "aws-sdk", 3 | .version = "0.0.0", 4 | .minimum_zig_version = "0.14.0", 5 | 6 | .dependencies = .{ 7 | .smithy = .{ .path = "smithy" }, 8 | .aws = .{ .path = "aws" }, 9 | .@"aws-models" = .{ 10 | .url = "git+https://github.com/by-nir/aws-models.git#1b147145c53dab1e7910c6de81a3749e6e71457b", 11 | .hash = "12209766b332481318f5afa9c2df3c798117913e8551055b98d9806e18c117e63b4f", 12 | }, 13 | }, 14 | 15 | .paths = .{ 16 | "build.zig", 17 | "build.zig.zon", 18 | "build.codegen.zig", 19 | "aws", 20 | "sdk", 21 | "smithy", 22 | "LICENSE", 23 | "README.md", 24 | }, 25 | } 26 | -------------------------------------------------------------------------------- /sdk/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # AWS SDK for Zig 4 | 5 | **The _AWS SDK for Zig_ provides an interface for _Amazon Web Services (AWS)_.** 6 | 7 | Building upon Zig’s strong foundation, this following services provides a **performant** and 8 | fully functioning SDKs, while **minimizing dependencies** and increased **platform portability**. 9 | 10 | > [!CAUTION] 11 | > This project is in early development, DO NOT USE IN PRODUCTION! 12 | > 13 | > Support for the remaining services and features will be added as the project 14 | > matures and stabilize. Till then, **breaking changes are imminent!**. 15 | 16 | 📒 For setup instructions and guidance see the [_AWS SDK for Zig_ documentation](/README.md#getting-started). 17 | 18 | ## Services SDKs 19 | 20 | Support for the remaining services and features will be added as the project matures and stabilize. 21 | 22 | > [!WARNING] 23 | > The following SDKs source code is [auto-generated](/README.md#contributing), **do not modify it directly!** 24 | -------------------------------------------------------------------------------- /smithy/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Nir Lahad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /smithy/README.md: -------------------------------------------------------------------------------- 1 | # Zig Smithy -------------------------------------------------------------------------------- /smithy/build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Build = std.Build; 3 | 4 | pub fn build(b: *std.Build) void { 5 | const target = b.standardTargetOptions(.{}); 6 | const optimize = b.standardOptimizeOption(.{}); 7 | 8 | // 9 | // Dependencies 10 | // 11 | 12 | const jobz = b.dependency("bitz", .{ 13 | .target = target, 14 | .optimize = optimize, 15 | }).module("jobz"); 16 | 17 | const cdmd = b.dependency("codmod", .{ 18 | .target = target, 19 | .optimize = optimize, 20 | }); 21 | const codmod = cdmd.module("codmod"); 22 | const codmod_jobs = cdmd.module("jobs"); 23 | 24 | const mvzr = b.dependency("mvzr", .{ 25 | .target = target, 26 | .optimize = optimize, 27 | }).module("mvzr"); 28 | 29 | // 30 | // Modules 31 | // 32 | 33 | const runtime = b.addModule("runtime", .{ 34 | .target = target, 35 | .optimize = optimize, 36 | .root_source_file = b.path("runtime/root.zig"), 37 | .imports = &.{ 38 | .{ .name = "mvzr", .module = mvzr }, 39 | }, 40 | }); 41 | 42 | _ = b.addModule("codegen", .{ 43 | .target = target, 44 | .optimize = optimize, 45 | .root_source_file = b.path("codegen/root.zig"), 46 | .imports = &.{ 47 | .{ .name = "jobz", .module = jobz }, 48 | .{ .name = "codmod", .module = codmod }, 49 | .{ .name = "codmod/jobs", .module = codmod_jobs }, 50 | .{ .name = "runtime", .module = runtime }, 51 | }, 52 | }); 53 | 54 | // 55 | // Tests 56 | // 57 | 58 | const test_all_step = b.step("test", "Run all unit tests"); 59 | 60 | // Runtime 61 | 62 | const test_runtime_step = b.step("test:runtime", "Run runtime unit tests"); 63 | test_all_step.dependOn(test_runtime_step); 64 | 65 | const test_runtime_exe = b.addTest(.{ 66 | .target = target, 67 | .optimize = optimize, 68 | .root_source_file = b.path("runtime/root.zig"), 69 | }); 70 | test_runtime_step.dependOn(&b.addRunArtifact(test_runtime_exe).step); 71 | test_runtime_exe.root_module.addImport("mvzr", mvzr); 72 | 73 | const debug_runtime_step = b.step("lldb:runtime", "Install runtime LLDB binary"); 74 | debug_runtime_step.dependOn(&b.addInstallArtifact(test_runtime_exe, .{ 75 | .dest_dir = .{ .override = .{ .custom = "lldb" } }, 76 | .dest_sub_path = "runtime", 77 | }).step); 78 | 79 | // Codegen 80 | 81 | const test_codegen_step = b.step("test:codegen", "Run codegen unit tests"); 82 | test_all_step.dependOn(test_codegen_step); 83 | const test_codegen_exe = b.addTest(.{ 84 | .target = target, 85 | .optimize = optimize, 86 | .root_source_file = b.path("codegen/root.zig"), 87 | }); 88 | test_codegen_step.dependOn(&b.addRunArtifact(test_codegen_exe).step); 89 | test_codegen_exe.root_module.addImport("jobz", jobz); 90 | test_codegen_exe.root_module.addImport("codmod", codmod); 91 | test_codegen_exe.root_module.addImport("codmod/jobs", codmod_jobs); 92 | test_codegen_exe.root_module.addImport("runtime", runtime); 93 | 94 | const debug_codegen_step = b.step("lldb:codegen", "Install codegen LLDB binary"); 95 | debug_codegen_step.dependOn(&b.addInstallArtifact(test_codegen_exe, .{ 96 | .dest_dir = .{ .override = .{ .custom = "lldb" } }, 97 | .dest_sub_path = "codegen", 98 | }).step); 99 | } 100 | -------------------------------------------------------------------------------- /smithy/build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | .name = "smithy", 3 | .version = "0.0.0", 4 | .minimum_zig_version = "0.14.0", 5 | 6 | .dependencies = .{ 7 | .bitz = .{ .path = "../_bitz" }, 8 | .codmod = .{ .path = "../_codmod" }, 9 | .mvzr = .{ 10 | .url = "https://github.com/mnemnion/mvzr/archive/refs/tags/v0.2.2.tar.gz", 11 | .hash = "12207d2c8c583108fec606c6855d4b681e120af4b2a1948afd9aa18976bdf70335dc", 12 | }, 13 | }, 14 | 15 | .paths = .{ 16 | "build.zig", 17 | "build.zig.zon", 18 | "codegen", 19 | "runtime", 20 | "LICENSE", 21 | "README.md", 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /smithy/codegen/config.zig: -------------------------------------------------------------------------------- 1 | /// Name of the allocator identifier 2 | pub const alloc_param = "allocator"; 3 | 4 | /// Name of the stack allocator identifier 5 | pub const scratch_alloc = "scratch_alloc"; 6 | 7 | /// Service client type name 8 | pub const service_client_type = "Client"; 9 | 10 | /// Service client filename 11 | pub const service_client_filename = "client.zig"; 12 | 13 | /// Runtime public scope 14 | pub const runtime_scope = "smithy"; 15 | 16 | /// Endpoint scope constant 17 | pub const endpoint_scope = "srvc_endpoint"; 18 | 19 | /// Endpoint scope filename 20 | pub const endpoint_filename = "endpoint.zig"; 21 | 22 | /// Endpoint configuration type name 23 | pub const endpoint_config_type = "EndpointConfig"; 24 | 25 | /// Endpoint resolve function identifier 26 | pub const endpoint_resolve_fn = "resolve"; 27 | 28 | /// Named types scope constant 29 | pub const types_scope = "srvc_types"; 30 | 31 | /// Named types scope filename 32 | pub const types_filename = "data_types.zig"; 33 | 34 | /// Named schemas scope constant 35 | pub const schemas_scope = "srvc_schemas"; 36 | 37 | /// Named schemas scope filename 38 | pub const schemas_filename = "data_schemas.zig"; 39 | 40 | /// Operations directory name 41 | pub const dir_operations = "operation"; 42 | -------------------------------------------------------------------------------- /smithy/codegen/model.zig: -------------------------------------------------------------------------------- 1 | pub const prelude = @import("model/prelude.zig"); 2 | 3 | const id = @import("model/smithy_id.zig"); 4 | pub const SmithyId = id.SmithyId; 5 | 6 | const typ = @import("model/smithy_type.zig"); 7 | pub const SmithyType = typ.SmithyType; 8 | 9 | const meta = @import("model/meta.zig"); 10 | pub const SmithyMeta = meta.SmithyMeta; 11 | 12 | const srvc = @import("model/service.zig"); 13 | pub const SmithyService = srvc.SmithyService; 14 | pub const SmithyResource = srvc.SmithyResource; 15 | pub const SmithyOperation = srvc.SmithyOperation; 16 | 17 | const mapping = @import("model/mapping.zig"); 18 | pub const SmithyTaggedValue = mapping.SmithyTaggedValue; 19 | pub const SmithyRefMapValue = mapping.SmithyRefMapValue; 20 | 21 | test { 22 | _ = prelude; 23 | _ = mapping; 24 | _ = id; 25 | _ = typ; 26 | _ = meta; 27 | _ = srvc; 28 | } 29 | -------------------------------------------------------------------------------- /smithy/codegen/model/mapping.zig: -------------------------------------------------------------------------------- 1 | const SmithyId = @import("smithy_id.zig").SmithyId; 2 | 3 | pub const SmithyTaggedValue = struct { 4 | id: SmithyId, 5 | value: ?*const anyopaque, 6 | }; 7 | 8 | pub const SmithyRefMapValue = struct { 9 | name: []const u8, 10 | shape: SmithyId, 11 | }; 12 | -------------------------------------------------------------------------------- /smithy/codegen/model/meta.zig: -------------------------------------------------------------------------------- 1 | const SmithyId = @import("smithy_id.zig").SmithyId; 2 | 3 | /// Node values are JSON-like values used to define metadata and the value of an applied trait. 4 | /// 5 | /// [Smithy Spec](https://smithy.io/2.0/spec/model.html#node-values) 6 | pub const SmithyMeta = union(enum) { 7 | /// The lack of a value. 8 | null, 9 | /// A UTF-8 string. 10 | string: []const u8, 11 | /// A double precision integer number. 12 | /// 13 | /// _Note: The original spec does not distinguish between number types._ 14 | integer: i64, 15 | /// A double precision floating point number. 16 | /// 17 | /// _Note: The original spec does not distinguish between number types._ 18 | float: f64, 19 | /// A Boolean, true or false value. 20 | boolean: bool, 21 | /// An array of heterogeneous node values. 22 | list: []const SmithyMeta, 23 | /// A object mapping string keys to heterogeneous node values. 24 | map: []const Pair, 25 | 26 | /// A key-value pair in a Map node. 27 | pub const Pair = struct { 28 | key: SmithyId, 29 | value: SmithyMeta, 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /smithy/codegen/model/prelude.zig: -------------------------------------------------------------------------------- 1 | pub const TYPE_UNIT = "smithy.api#Unit"; 2 | pub const TYPE_BLOB = "smithy.api#Blob"; 3 | pub const TYPE_BOOL = "smithy.api#Boolean"; 4 | pub const TYPE_STRING = "smithy.api#String"; 5 | pub const TYPE_BYTE = "smithy.api#Byte"; 6 | pub const TYPE_SHORT = "smithy.api#Short"; 7 | pub const TYPE_INT = "smithy.api#Integer"; 8 | pub const TYPE_LONG = "smithy.api#Long"; 9 | pub const TYPE_FLOAT = "smithy.api#Float"; 10 | pub const TYPE_DOUBLE = "smithy.api#Double"; 11 | pub const TYPE_BIGINT = "smithy.api#BigInteger"; 12 | pub const TYPE_BIGDEC = "smithy.api#BigDecimal"; 13 | pub const TYPE_TIMESTAMP = "smithy.api#Timestamp"; 14 | pub const TYPE_DOCUMENT = "smithy.api#Document"; 15 | 16 | pub const PRIMITIVE_BOOL = "smithy.api#PrimitiveBoolean"; 17 | pub const PRIMITIVE_BYTE = "smithy.api#PrimitiveByte"; 18 | pub const PRIMITIVE_SHORT = "smithy.api#PrimitiveShort"; 19 | pub const PRIMITIVE_INT = "smithy.api#PrimitiveInteger"; 20 | pub const PRIMITIVE_LONG = "smithy.api#PrimitiveLong"; 21 | pub const PRIMITIVE_FLOAT = "smithy.api#PrimitiveFloat"; 22 | pub const PRIMITIVE_DOUBLE = "smithy.api#PrimitiveDouble"; 23 | -------------------------------------------------------------------------------- /smithy/codegen/model/service.zig: -------------------------------------------------------------------------------- 1 | const SmithyId = @import("smithy_id.zig").SmithyId; 2 | const SmithyRefMapValue = @import("mapping.zig").SmithyRefMapValue; 3 | 4 | /// A service is the entry point of an API that aggregates resources and operations together. 5 | /// 6 | /// [Smithy Spec](https://smithy.io/2.0/spec/service-types.html#service) 7 | pub const SmithyService = struct { 8 | /// Defines the optional version of the service. 9 | version: ?[]const u8 = null, 10 | /// Binds a set of operation shapes to the service. 11 | operations: []const SmithyId = &.{}, 12 | /// Binds a set of resource shapes to the service. 13 | resources: []const SmithyId = &.{}, 14 | /// Defines a list of common errors that every operation bound within the closure of the service can return. 15 | errors: []const SmithyId = &.{}, 16 | /// Disambiguates shape name conflicts in the 17 | /// [service closure](https://smithy.io/2.0/spec/service-types.html#service-closure). 18 | rename: []const SmithyRefMapValue = &.{}, 19 | }; 20 | 21 | /// The operation type represents the input, output, and possible errors of an API operation. 22 | /// 23 | /// [Smithy Spec](https://smithy.io/2.0/spec/service-types.html#resource) 24 | pub const SmithyResource = struct { 25 | /// Defines a map of identifier string names to Shape IDs used to identify the resource. 26 | identifiers: []const SmithyRefMapValue = &.{}, 27 | /// Defines a map of property string names to Shape IDs that enumerate the properties of the resource. 28 | properties: []const SmithyRefMapValue = &.{}, 29 | /// Defines the lifecycle operation used to create a resource using one or more identifiers created by the service. 30 | create: ?SmithyId = null, 31 | /// Defines an idempotent lifecycle operation used to create a resource using identifiers provided by the client. 32 | put: ?SmithyId = null, 33 | /// Defines the lifecycle operation used to retrieve the resource. 34 | read: ?SmithyId = null, 35 | /// Defines the lifecycle operation used to update the resource. 36 | update: ?SmithyId = null, 37 | /// Defines the lifecycle operation used to delete the resource. 38 | delete: ?SmithyId = null, 39 | /// Defines the lifecycle operation used to list resources of this type. 40 | list: ?SmithyId = null, 41 | /// Binds a list of non-lifecycle instance operations to the resource. 42 | operations: []const SmithyId = &.{}, 43 | /// Binds a list of non-lifecycle collection operations to the resource. 44 | collection_ops: []const SmithyId = &.{}, 45 | /// Binds a list of resources to this resource as a child resource, forming a containment relationship. 46 | resources: []const SmithyId = &.{}, 47 | }; 48 | 49 | /// Smithy defines a resource as an entity with an identity that has a set of operations. 50 | /// 51 | /// [Smithy Spec](https://smithy.io/2.0/spec/service-types.html#operation) 52 | pub const SmithyOperation = struct { 53 | /// The input of the operation defined using a shape ID that MUST target a structure. 54 | input: ?SmithyId = null, 55 | /// The output of the operation defined using a shape ID that MUST target a structure. 56 | output: ?SmithyId = null, 57 | /// The errors that an operation can return. 58 | errors: []const SmithyId = &.{}, 59 | }; 60 | -------------------------------------------------------------------------------- /smithy/codegen/model/smithy_id.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const idHash = std.hash.CityHash32.hash; 4 | const prelude = @import("prelude.zig"); 5 | 6 | /// A 32-bit hash of a Shape ID. 7 | /// 8 | /// [Smithy Spec](https://smithy.io/2.0/spec/model.html#shape-id) 9 | /// ``` 10 | /// smithy.example.foo#ExampleShapeName$memberName 11 | /// └────────┬───────┘ └───────┬──────┘ └────┬───┘ 12 | /// Namespace Shape Member 13 | /// └────────────┬────────────┘ 14 | /// Relative shape ID 15 | /// └─────────────────────┬──────────────────────┘ 16 | /// Absolute shape ID 17 | /// ``` 18 | pub const SmithyId = enum(u32) { 19 | /// We use a constant to avoid handling the NULL case in the switch. 20 | pub const NULL: SmithyId = @enumFromInt(0); 21 | 22 | unit = idHash("unitType"), 23 | blob = idHash("blob"), 24 | boolean = idHash("boolean"), 25 | string = idHash("string"), 26 | str_enum = idHash("enum"), 27 | byte = idHash("byte"), 28 | short = idHash("short"), 29 | integer = idHash("integer"), 30 | int_enum = idHash("intEnum"), 31 | long = idHash("long"), 32 | float = idHash("float"), 33 | double = idHash("double"), 34 | big_integer = idHash("bigInteger"), 35 | big_decimal = idHash("bigDecimal"), 36 | timestamp = idHash("timestamp"), 37 | document = idHash("document"), 38 | list = idHash("list"), 39 | map = idHash("map"), 40 | structure = idHash("structure"), 41 | tagged_union = idHash("union"), 42 | operation = idHash("operation"), 43 | resource = idHash("resource"), 44 | service = idHash("service"), 45 | apply = idHash("apply"), 46 | _, 47 | 48 | /// Type name or absalute shape id. 49 | pub fn of(shape_id: []const u8) SmithyId { 50 | return switch (idHash(shape_id)) { 51 | idHash(prelude.TYPE_UNIT) => .unit, 52 | idHash(prelude.TYPE_BLOB) => .blob, 53 | idHash(prelude.TYPE_STRING) => .string, 54 | idHash(prelude.TYPE_BOOL), idHash(prelude.PRIMITIVE_BOOL) => .boolean, 55 | idHash(prelude.TYPE_BYTE), idHash(prelude.PRIMITIVE_BYTE) => .byte, 56 | idHash(prelude.TYPE_SHORT), idHash(prelude.PRIMITIVE_SHORT) => .short, 57 | idHash(prelude.TYPE_INT), idHash(prelude.PRIMITIVE_INT) => .integer, 58 | idHash(prelude.TYPE_LONG), idHash(prelude.PRIMITIVE_LONG) => .long, 59 | idHash(prelude.TYPE_FLOAT), idHash(prelude.PRIMITIVE_FLOAT) => .float, 60 | idHash(prelude.TYPE_DOUBLE), idHash(prelude.PRIMITIVE_DOUBLE) => .double, 61 | idHash(prelude.TYPE_BIGINT) => .big_integer, 62 | idHash(prelude.TYPE_BIGDEC) => .big_decimal, 63 | idHash(prelude.TYPE_TIMESTAMP) => .timestamp, 64 | idHash(prelude.TYPE_DOCUMENT) => .document, 65 | else => |h| @enumFromInt(h), 66 | }; 67 | } 68 | 69 | /// `smithy.example.foo#ExampleShapeName$memberName` 70 | pub fn compose(shape: []const u8, member: []const u8) SmithyId { 71 | var buffer: [128]u8 = undefined; 72 | const len = shape.len + member.len + 1; 73 | std.debug.assert(len <= buffer.len); 74 | @memcpy(buffer[0..shape.len], shape); 75 | buffer[shape.len] = '$'; 76 | @memcpy(buffer[shape.len + 1 ..][0..member.len], member); 77 | return @enumFromInt(idHash(buffer[0..len])); 78 | } 79 | }; 80 | 81 | test SmithyId { 82 | try testing.expectEqual(.boolean, SmithyId.of("boolean")); 83 | try testing.expectEqual(.boolean, SmithyId.of("smithy.api#Boolean")); 84 | try testing.expectEqual(.boolean, SmithyId.of("smithy.api#PrimitiveBoolean")); 85 | try testing.expectEqual(.list, SmithyId.of("list")); 86 | try testing.expectEqual( 87 | @as(SmithyId, @enumFromInt(0x6f8b5d99)), 88 | SmithyId.of("smithy.example.foo#ExampleShapeName$memberName"), 89 | ); 90 | try testing.expectEqual( 91 | SmithyId.of("smithy.example.foo#ExampleShapeName$memberName"), 92 | SmithyId.compose("smithy.example.foo#ExampleShapeName", "memberName"), 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /smithy/codegen/model/smithy_type.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const hash = std.hash.CityHash32.hash; 4 | const srvc = @import("service.zig"); 5 | const SmithyId = @import("smithy_id.zig").SmithyId; 6 | 7 | /// A Smithy shape’s type. 8 | pub const SmithyType = union(enum) { 9 | /// A reference to a shape that is not a member of the prelude. 10 | target: SmithyId, 11 | 12 | /// The singular unit type in Smithy is similar to Void and None in other languages. 13 | /// It is used when the input or output of an operation has no meaningful value 14 | /// or if a union member has no meaningful value. It MUST NOT be referenced 15 | /// in any other context. 16 | /// 17 | /// [Smithy Spec](https://smithy.io/2.0/spec/model.html#unit-type) 18 | unit, 19 | 20 | // 21 | // Simple types are types that do not contain nested types or shape references. 22 | // https://smithy.io/2.0/spec/simple-types.html#simple-types 23 | // 24 | 25 | /// Uninterpreted binary data. 26 | blob, 27 | /// Boolean value type. 28 | boolean, 29 | /// UTF-8 encoded string. 30 | string, 31 | /// A string with a fixed set of values. 32 | str_enum: []const SmithyId, 33 | /// Deprecated implementation of a string-based enum. 34 | trt_enum, 35 | /// 8-bit signed integer ranging from -128 to 127 (inclusive). 36 | byte, 37 | /// 16-bit signed integer ranging from -32,768 to 32,767 (inclusive). 38 | short, 39 | /// 32-bit signed integer ranging from -2^31 to (2^31)-1 (inclusive). 40 | integer, 41 | /// An integer with a fixed set of values. 42 | int_enum: []const SmithyId, 43 | /// 64-bit signed integer ranging from -2^63 to (2^63)-1 (inclusive). 44 | long, 45 | /// Single precision IEEE-754 floating point number. 46 | float, 47 | /// Double precision IEEE-754 floating point number. 48 | double, 49 | /// Arbitrarily large signed integer. 50 | big_integer, 51 | /// Arbitrary precision signed decimal number. 52 | big_decimal, 53 | /// An instant in time with no UTC offset or timezone. 54 | timestamp, 55 | /// Open content that functions as a kind of "any" type. 56 | document, 57 | 58 | // 59 | // Aggregate types contain configurable member references to others shapes. 60 | // https://smithy.io/2.0/spec/aggregate-types.html#aggregate-types 61 | // 62 | 63 | /// Ordered collection of homogeneous values. 64 | list: SmithyId, 65 | /// Map data structure that maps string keys to homogeneous values. 66 | map: [2]SmithyId, 67 | /// Fixed set of named heterogeneous members. 68 | structure: []const SmithyId, 69 | /// Tagged union data structure that can take on one of several different, but fixed, types. 70 | tagged_union: []const SmithyId, 71 | 72 | // 73 | // Service types have specific semantics and define services, resources, and operations. 74 | // https://smithy.io/2.0/spec/service-types.html#service-types 75 | // 76 | 77 | /// The operation type represents the input, output, and possible errors of an API operation. 78 | operation: *const srvc.SmithyOperation, 79 | /// Smithy defines a resource as an entity with an identity that has a set of operations. 80 | resource: *const srvc.SmithyResource, 81 | /// A service is the entry point of an API that aggregates resources and operations together. 82 | service: *const srvc.SmithyService, 83 | }; 84 | -------------------------------------------------------------------------------- /smithy/codegen/parse/Model.zig: -------------------------------------------------------------------------------- 1 | //! Raw symbols (shapes and metadata) of a Smithy model. 2 | const std = @import("std"); 3 | const Allocator = std.mem.Allocator; 4 | const testing = std.testing; 5 | const test_alloc = testing.allocator; 6 | const mdl = @import("../model.zig"); 7 | const SmithyId = mdl.SmithyId; 8 | const SmithyType = mdl.SmithyType; 9 | const SmithyMeta = mdl.SmithyMeta; 10 | const TaggedValue = mdl.SmithyTaggedValue; 11 | 12 | const Self = @This(); 13 | 14 | allocator: Allocator, 15 | service_id: SmithyId = SmithyId.NULL, 16 | meta: std.AutoHashMapUnmanaged(SmithyId, SmithyMeta) = .{}, 17 | shapes: std.AutoHashMapUnmanaged(SmithyId, SmithyType) = .{}, 18 | names: std.AutoHashMapUnmanaged(SmithyId, []const u8) = .{}, 19 | /// Includes the namespace (`com.provider.namespace#Shape`). 20 | full_names: std.AutoHashMapUnmanaged(SmithyId, []const u8) = .{}, 21 | traits: std.AutoHashMapUnmanaged(SmithyId, []const TaggedValue) = .{}, 22 | mixins: std.AutoHashMapUnmanaged(SmithyId, []const SmithyId) = .{}, 23 | 24 | pub fn init(allocator: Allocator) Self { 25 | return .{ .allocator = allocator }; 26 | } 27 | 28 | pub fn deinit(self: *Self) void { 29 | self.meta.deinit(self.allocator); 30 | self.shapes.deinit(self.allocator); 31 | self.traits.deinit(self.allocator); 32 | self.mixins.deinit(self.allocator); 33 | self.names.deinit(self.allocator); 34 | self.full_names.deinit(self.allocator); 35 | } 36 | 37 | pub fn putMeta(self: *Self, key: SmithyId, value: SmithyMeta) !void { 38 | try self.meta.put(self.allocator, key, value); 39 | } 40 | 41 | pub fn putShape(self: *Self, id: SmithyId, shape: SmithyType) !void { 42 | try self.shapes.put(self.allocator, id, shape); 43 | } 44 | 45 | pub fn putName(self: *Self, id: SmithyId, name: []const u8) !void { 46 | try self.names.put(self.allocator, id, name); 47 | } 48 | 49 | /// Includes the namespace (`com.provider.namespace#Shape`). 50 | /// If a the un-namespaced name is also required, call `putName` as well. 51 | pub fn putFullName(self: *Self, id: SmithyId, name: []const u8) !void { 52 | try self.full_names.put(self.allocator, id, name); 53 | } 54 | 55 | pub fn putMixins(self: *Self, id: SmithyId, mixins: []const SmithyId) !void { 56 | try self.mixins.put(self.allocator, id, mixins); 57 | } 58 | 59 | /// Returns `true` if expanded an existing traits list. 60 | pub fn putTraits(self: *Self, id: SmithyId, traits: []const mdl.SmithyTaggedValue) !bool { 61 | const result = try self.traits.getOrPut(self.allocator, id); 62 | if (!result.found_existing) { 63 | result.value_ptr.* = traits; 64 | return false; 65 | } 66 | 67 | const current = result.value_ptr.*; 68 | const all = try self.allocator.alloc(TaggedValue, current.len + traits.len); 69 | @memcpy(all[0..current.len], current); 70 | @memcpy(all[current.len..][0..traits.len], traits); 71 | self.allocator.free(current); 72 | result.value_ptr.* = all; 73 | return true; 74 | } 75 | 76 | test "putTraits" { 77 | const foo = "Foo"; 78 | const bar = "Bar"; 79 | const baz = "Baz"; 80 | 81 | var model = Self.init(test_alloc); 82 | defer model.deinit(); 83 | 84 | const traits = try test_alloc.alloc(TaggedValue, 2); 85 | traits[0] = .{ .id = SmithyId.of("Foo"), .value = foo }; 86 | traits[1] = .{ .id = SmithyId.of("Bar"), .value = bar }; 87 | { 88 | errdefer test_alloc.free(traits); 89 | try testing.expectEqual(false, try model.putTraits(SmithyId.of("Traits"), traits)); 90 | try testing.expectEqualDeep(&[_]TaggedValue{ 91 | .{ .id = SmithyId.of("Foo"), .value = foo }, 92 | .{ .id = SmithyId.of("Bar"), .value = bar }, 93 | }, model.traits.get(SmithyId.of("Traits"))); 94 | 95 | try testing.expectEqual(true, try model.putTraits( 96 | SmithyId.of("Traits"), 97 | &.{.{ .id = SmithyId.of("Baz"), .value = baz }}, 98 | )); 99 | } 100 | defer test_alloc.free(model.traits.get(SmithyId.of("Traits")).?); 101 | 102 | try testing.expectEqualDeep(&[_]TaggedValue{ 103 | .{ .id = SmithyId.of("Foo"), .value = foo }, 104 | .{ .id = SmithyId.of("Bar"), .value = bar }, 105 | .{ .id = SmithyId.of("Baz"), .value = baz }, 106 | }, model.traits.get(SmithyId.of("Traits"))); 107 | } 108 | -------------------------------------------------------------------------------- /smithy/codegen/parse/issues.zig: -------------------------------------------------------------------------------- 1 | const IssueBehavior = @import("../systems/issues.zig").IssueBehavior; 2 | 3 | pub const ParseBehavior = struct { 4 | property: IssueBehavior = .abort, 5 | trait: IssueBehavior = .abort, 6 | }; 7 | -------------------------------------------------------------------------------- /smithy/codegen/parse/props.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | 4 | /// All known Smithy properties. 5 | // NOTICE: If adding more properties, make sure their first 8 characters are unique. 6 | pub const SmithyProperty = enum(u64) { 7 | collection_ops = parse("collectionOperations"), 8 | create = parse("create"), 9 | delete = parse("delete"), 10 | errors = parse("errors"), 11 | identifiers = parse("identifiers"), 12 | input = parse("input"), 13 | key = parse("key"), 14 | list = parse("list"), 15 | member = parse("member"), 16 | members = parse("members"), 17 | metadata = parse("metadata"), 18 | mixins = parse("mixins"), 19 | operations = parse("operations"), 20 | output = parse("output"), 21 | properties = parse("properties"), 22 | put = parse("put"), 23 | read = parse("read"), 24 | rename = parse("rename"), 25 | resources = parse("resources"), 26 | shapes = parse("shapes"), 27 | smithy = parse("smithy"), 28 | target = parse("target"), 29 | traits = parse("traits"), 30 | type = parse("type"), 31 | update = parse("update"), 32 | value = parse("value"), 33 | version = parse("version"), 34 | _, 35 | 36 | fn parse(str: []const u8) u64 { 37 | var code: u64 = 0; 38 | const len = @min(8, str.len); 39 | @memcpy(std.mem.asBytes(&code)[0..len], str[0..len]); 40 | return code; 41 | } 42 | 43 | pub fn of(str: []const u8) SmithyProperty { 44 | return @enumFromInt(parse(str)); 45 | } 46 | }; 47 | 48 | test SmithyProperty { 49 | try testing.expectEqual(.collection_ops, SmithyProperty.of("collectionOperations")); 50 | try testing.expectEqual(.create, SmithyProperty.of("create")); 51 | try testing.expectEqual(.delete, SmithyProperty.of("delete")); 52 | try testing.expectEqual(.errors, SmithyProperty.of("errors")); 53 | try testing.expectEqual(.identifiers, SmithyProperty.of("identifiers")); 54 | try testing.expectEqual(.input, SmithyProperty.of("input")); 55 | try testing.expectEqual(.key, SmithyProperty.of("key")); 56 | try testing.expectEqual(.list, SmithyProperty.of("list")); 57 | try testing.expectEqual(.member, SmithyProperty.of("member")); 58 | try testing.expectEqual(.members, SmithyProperty.of("members")); 59 | try testing.expectEqual(.metadata, SmithyProperty.of("metadata")); 60 | try testing.expectEqual(.mixins, SmithyProperty.of("mixins")); 61 | try testing.expectEqual(.operations, SmithyProperty.of("operations")); 62 | try testing.expectEqual(.output, SmithyProperty.of("output")); 63 | try testing.expectEqual(.properties, SmithyProperty.of("properties")); 64 | try testing.expectEqual(.put, SmithyProperty.of("put")); 65 | try testing.expectEqual(.read, SmithyProperty.of("read")); 66 | try testing.expectEqual(.rename, SmithyProperty.of("rename")); 67 | try testing.expectEqual(.resources, SmithyProperty.of("resources")); 68 | try testing.expectEqual(.shapes, SmithyProperty.of("shapes")); 69 | try testing.expectEqual(.smithy, SmithyProperty.of("smithy")); 70 | try testing.expectEqual(.target, SmithyProperty.of("target")); 71 | try testing.expectEqual(.traits, SmithyProperty.of("traits")); 72 | try testing.expectEqual(.type, SmithyProperty.of("type")); 73 | try testing.expectEqual(.update, SmithyProperty.of("update")); 74 | try testing.expectEqual(.value, SmithyProperty.of("value")); 75 | try testing.expectEqual(.version, SmithyProperty.of("version")); 76 | try testing.expectEqual( 77 | SmithyProperty.parse("U4K4OW4"), 78 | @intFromEnum(SmithyProperty.of("U4K4OW4")), 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /smithy/codegen/pipeline.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const fs = std.fs; 3 | const jobz = @import("jobz"); 4 | const DirOptions = @import("codmod/jobs").files.DirOptions; 5 | const prelude = @import("traits.zig"); 6 | const rls = @import("systems/rules.zig"); 7 | const trt = @import("systems/traits.zig"); 8 | const isu = @import("systems/issues.zig"); 9 | const SymbolsProvider = @import("systems/SymbolsProvider.zig"); 10 | const Model = @import("parse/Model.zig"); 11 | const ParseModel = @import("parse/parse.zig").ParseModel; 12 | const ParseBehavior = @import("parse/issues.zig").ParseBehavior; 13 | const CodegenService = @import("render/service.zig").CodegenService; 14 | const JsonReader = @import("utils/JsonReader.zig"); 15 | 16 | pub const ScopeTag = enum { 17 | slug, 18 | parse_behavior, 19 | codegen_behavior, 20 | }; 21 | 22 | pub const PipelineBehavior = struct { 23 | process: isu.IssueBehavior = .abort, 24 | parse: isu.IssueBehavior = .abort, 25 | codegen: isu.IssueBehavior = .abort, 26 | }; 27 | 28 | pub const PipelineOptions = struct { 29 | traits: ?trt.TraitsRegistry = null, 30 | rules_builtins: rls.BuiltInsRegistry = &.{}, 31 | rules_funcs: rls.FunctionsRegistry = &.{}, 32 | behavior_service: PipelineBehavior = .{}, 33 | behavior_parse: ParseBehavior = .{}, 34 | }; 35 | 36 | pub const PipelineServiceFilterHook = jobz.Task.Hook("Smithy Service Filter", bool, &.{[]const u8}); 37 | 38 | pub const Pipeline = jobz.Task.Define("Smithy Service Pipeline", smithyTask, .{}); 39 | fn smithyTask(self: *const jobz.Delegate, src_dir: fs.Dir, options: PipelineOptions) anyerror!void { 40 | const behavior = options.behavior_service; 41 | try self.defineValue(ParseBehavior, ScopeTag.parse_behavior, options.behavior_parse); 42 | 43 | const traits_manager: *trt.TraitsManager = try self.provide(trt.TraitsManager{}, null); 44 | try prelude.registerTraits(self.alloc(), traits_manager); 45 | if (options.traits) |registry| { 46 | try traits_manager.registerAll(self.alloc(), registry); 47 | } 48 | 49 | _ = try self.provide(try rls.RulesEngine.init(self.alloc(), options.rules_builtins, options.rules_funcs), null); 50 | 51 | var it = src_dir.iterate(); 52 | while (try it.next()) |entry| { 53 | if (entry.kind != .file or !std.mem.endsWith(u8, entry.name, ".json")) continue; 54 | processService(self, src_dir, entry.name, behavior) catch |err| switch (behavior.process) { 55 | .abort => { 56 | std.log.err("Processing model '{s}' failed: {s}", .{ entry.name, @errorName(err) }); 57 | if (@errorReturnTrace()) |t| std.debug.dumpStackTrace(t.*); 58 | return isu.AbortError; 59 | }, 60 | .skip => { 61 | std.log.err("Skipped model '{s}': {s}", .{ entry.name, @errorName(err) }); 62 | return; 63 | }, 64 | }; 65 | } 66 | } 67 | 68 | fn processService(self: *const jobz.Delegate, src_dir: fs.Dir, filename: []const u8, behavior: PipelineBehavior) !void { 69 | if (self.hasOverride(PipelineServiceFilterHook)) { 70 | const allowed = try self.evaluate(PipelineServiceFilterHook, .{filename}); 71 | if (!allowed) return; 72 | } 73 | 74 | try self.evaluate(SmithyService, .{ src_dir, filename, behavior }); 75 | } 76 | 77 | const SmithyService = jobz.Task.Define("Smithy Service", smithyServiceTask, .{}); 78 | fn smithyServiceTask(self: *const jobz.Delegate, src_dir: fs.Dir, json_name: []const u8, behavior: PipelineBehavior) anyerror!void { 79 | std.debug.assert(std.mem.endsWith(u8, json_name, ".json")); 80 | const slug = json_name[0 .. json_name.len - ".json".len]; 81 | try self.defineValue([]const u8, ScopeTag.slug, slug); 82 | 83 | const issues: *isu.IssuesBag = try self.provide(isu.IssuesBag.init(self.alloc()), null); 84 | 85 | var symbols = serviceReadAndParse(self, src_dir, json_name) catch |err| { 86 | return handleIssue(issues, behavior.parse, err, .parse_error, "Parsing failed", @errorReturnTrace()); 87 | }; 88 | _ = try self.provide(&symbols, null); 89 | 90 | self.evaluate(CodegenService, .{ slug, DirOptions{ 91 | .create_on_not_found = true, 92 | .delete_on_error = true, 93 | } }) catch |err| { 94 | return handleIssue(issues, behavior.codegen, err, .codegen_error, "Codegen failed", @errorReturnTrace()); 95 | }; 96 | } 97 | 98 | fn serviceReadAndParse(self: *const jobz.Delegate, src_dir: fs.Dir, json_name: []const u8) !SymbolsProvider { 99 | const json_file: fs.File = try src_dir.openFile(json_name, .{}); 100 | defer json_file.close(); 101 | 102 | var reader = try JsonReader.initPersist(self.alloc(), json_file); 103 | defer reader.deinit(); 104 | 105 | var model: Model = try self.evaluate(ParseModel, .{&reader}); 106 | return SymbolsProvider.consumeModel(self.alloc(), &model); 107 | } 108 | 109 | fn handleIssue( 110 | issues: *isu.IssuesBag, 111 | behavior: isu.IssueBehavior, 112 | err: anyerror, 113 | comptime tag: anytype, 114 | message: []const u8, 115 | stack_trace: ?*std.builtin.StackTrace, 116 | ) !void { 117 | switch (err) { 118 | isu.AbortError => return err, 119 | else => switch (behavior) { 120 | .abort => { 121 | std.log.err("{s}: {s}", .{ message, @errorName(err) }); 122 | if (stack_trace) |trace| std.debug.dumpStackTrace(trace.*); 123 | return isu.AbortError; 124 | }, 125 | .skip => { 126 | issues.add(@unionInit(isu.Issue, @tagName(tag), err)) catch {}; 127 | return; 128 | }, 129 | }, 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /smithy/codegen/render/client_endpoint.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const test_alloc = std.testing.allocator; 4 | const jobz = @import("jobz"); 5 | const zig = @import("codmod").zig; 6 | const srvc = @import("service.zig"); 7 | const cfg = @import("../config.zig"); 8 | const SmithyId = @import("../model.zig").SmithyId; 9 | const IssuesBag = @import("../systems/issues.zig").IssuesBag; 10 | const RulesEngine = @import("../systems/rules.zig").RulesEngine; 11 | const SymbolsProvider = @import("../systems/SymbolsProvider.zig"); 12 | const trt_rules = @import("../traits/rules.zig"); 13 | const test_symbols = @import("../testing/symbols.zig"); 14 | 15 | pub const EndpointScriptHeadHook = jobz.Task.Hook("Smithy Endpoint Script Head", anyerror!void, &.{*zig.ContainerBuild}); 16 | 17 | pub const ClientEndpoint = srvc.ScriptCodegen.Task("Smithy Client Endpoint Codegen", clientEndpointTask, .{ 18 | .injects = &.{ SymbolsProvider, RulesEngine }, 19 | }); 20 | fn clientEndpointTask( 21 | self: *const jobz.Delegate, 22 | symbols: *SymbolsProvider, 23 | rules_engine: *RulesEngine, 24 | bld: *zig.ContainerBuild, 25 | ) anyerror!void { 26 | const rule_set = trt_rules.EndpointRuleSet.get(symbols, symbols.service_id) orelse { 27 | return error.MissingEndpointRuleSet; 28 | }; 29 | 30 | try bld.constant("IS_TEST").assign(bld.x.import("builtin").dot().id("is_test")); 31 | 32 | if (self.hasOverride(EndpointScriptHeadHook)) { 33 | try self.evaluate(EndpointScriptHeadHook, .{bld}); 34 | } 35 | 36 | var rulesgen = try rules_engine.getGenerator(self.alloc(), rule_set.parameters); 37 | 38 | const context = .{ .alloc = self.alloc(), .rulesgen = &rulesgen }; 39 | try bld.public().constant(cfg.endpoint_config_type).assign(bld.x.@"struct"().bodyWith(context, struct { 40 | fn f(ctx: @TypeOf(context), b: *zig.ContainerBuild) !void { 41 | try ctx.rulesgen.generateParametersFields(b); 42 | } 43 | }.f)); 44 | 45 | try rulesgen.generateResolver(bld, rule_set.rules); 46 | 47 | if (trt_rules.EndpointTests.get(symbols, symbols.service_id)) |cases| { 48 | try rulesgen.generateTests(bld, cases); 49 | } 50 | } 51 | 52 | test ClientEndpoint { 53 | var tester = try jobz.PipelineTester.init(.{}); 54 | defer tester.deinit(); 55 | 56 | var issues = IssuesBag.init(test_alloc); 57 | defer issues.deinit(); 58 | _ = try tester.provideService(&issues, null); 59 | 60 | var symbols = try test_symbols.setup(tester.alloc(), .service); 61 | defer symbols.deinit(); 62 | _ = try tester.provideService(&symbols, null); 63 | 64 | var rules_engine = try RulesEngine.init(test_alloc, &.{}, &.{}); 65 | defer rules_engine.deinit(test_alloc); 66 | _ = try tester.provideService(&rules_engine, null); 67 | 68 | symbols.service_id = SmithyId.of("test.serve#Service"); 69 | try srvc.expectServiceScript( 70 | \\const IS_TEST = @import("builtin").is_test; 71 | \\ 72 | \\pub const EndpointConfig = struct { 73 | \\ foo: ?bool = null, 74 | \\}; 75 | \\ 76 | \\pub fn resolve(allocator: Allocator, config: EndpointConfig) !smithy.Endpoint { 77 | \\ var local_buffer: [512]u8 = undefined; 78 | \\ 79 | \\ var fixed_buffer = std.heap.FixedBufferAllocator.init(&local_buffer); 80 | \\ 81 | \\ const scratch_alloc = fixed_buffer.allocator(); 82 | \\ 83 | \\ _ = scratch_alloc; 84 | \\ 85 | \\ var did_pass = false; 86 | \\ 87 | \\ if (!IS_TEST) std.log.err("baz", .{}); 88 | \\ 89 | \\ return error.ReachedErrorRule; 90 | \\} 91 | \\ 92 | \\test "Foo" { 93 | \\ const config = EndpointConfig{}; 94 | \\ 95 | \\ const endpoint = resolve(std.testing.allocator, config); 96 | \\ 97 | \\ try std.testing.expectError(error.ReachedErrorRule, endpoint); 98 | \\} 99 | , ClientEndpoint, tester.pipeline, .{}); 100 | } 101 | -------------------------------------------------------------------------------- /smithy/codegen/root.zig: -------------------------------------------------------------------------------- 1 | pub const config = @import("config.zig"); 2 | 3 | // Model 4 | const model = @import("model.zig"); 5 | pub const SmithyId = model.SmithyId; 6 | pub const SmithyType = model.SmithyType; 7 | pub const SmithyMeta = model.SmithyMeta; 8 | pub const SmithyService = model.SmithyService; 9 | pub const SmithyResource = model.SmithyResource; 10 | pub const SmithyOperation = model.SmithyOperation; 11 | pub const SmithyTaggedValue = model.SmithyTaggedValue; 12 | pub const SmithyRefMapValue = model.SmithyRefMapValue; 13 | pub const traits = @import("traits.zig").prelude; 14 | 15 | // Pipeline 16 | const pipeline = @import("pipeline.zig"); 17 | pub const PipelineTask = pipeline.Pipeline; 18 | pub const PipelineOptions = pipeline.PipelineOptions; 19 | pub const PipelineBehavior = pipeline.PipelineBehavior; 20 | pub const PipelineServiceFilterHook = pipeline.PipelineServiceFilterHook; 21 | 22 | // Parse 23 | const parse_issues = @import("parse/issues.zig"); 24 | pub const ParseBehavior = parse_issues.ParseBehavior; 25 | 26 | // Render 27 | const gen_service = @import("render/service.zig"); 28 | pub const ServiceScriptHeadHook = gen_service.ScriptHeadHook; 29 | pub const ServiceReadmeHook = gen_service.ServiceReadmeHook; 30 | pub const ServiceExtensionHook = gen_service.ServiceExtensionHook; 31 | pub const ServiceExtension = gen_service.ServiceExtension; 32 | pub const ServiceReadmeMetadata = gen_service.ServiceReadmeMetadata; 33 | const gen_client = @import("render/client.zig"); 34 | pub const ClientScriptHeadHook = gen_client.ClientScriptHeadHook; 35 | pub const ClientShapeHeadHook = gen_client.ClientShapeHeadHook; 36 | pub const ClientSendSyncFuncHook = gen_client.ClientSendSyncFuncHook; 37 | const gen_endpoint = @import("render/client_endpoint.zig"); 38 | pub const EndpointScriptHeadHook = gen_endpoint.EndpointScriptHeadHook; 39 | const gen_operation = @import("render/client_operation.zig"); 40 | pub const OperationMetaHook = gen_operation.OperationMetaHook; 41 | pub const OperationScriptHeadHook = gen_operation.OperationScriptHeadHook; 42 | pub const OperationCustomErrorHook = gen_operation.OperationCustomErrorHook; 43 | 44 | // Systems 45 | const isu = @import("systems/issues.zig"); 46 | pub const IssueBehavior = isu.IssueBehavior; 47 | const trt = @import("systems/traits.zig"); 48 | pub const StringTrait = trt.StringTrait; 49 | pub const TraitsRegistry = trt.TraitsRegistry; 50 | const rls = @import("systems/rules.zig"); 51 | pub const RuleSet = rls.RuleSet; 52 | pub const RulesFunc = rls.Function; 53 | pub const RulesBuiltIn = rls.BuiltIn; 54 | pub const RulesGenerator = rls.Generator; 55 | pub const RulesArgValue = rls.ArgValue; 56 | pub const RulesFuncsRegistry = rls.FunctionsRegistry; 57 | pub const RulesBuiltInsRegistry = rls.BuiltInsRegistry; 58 | pub const RulesParamKV = rls.StringKV(rls.Parameter); 59 | pub const SymbolsProvider = @import("systems/SymbolsProvider.zig"); 60 | 61 | // Utils 62 | pub const JsonReader = @import("utils/JsonReader.zig"); 63 | pub const name_util = @import("utils/names.zig"); 64 | 65 | test { 66 | // Utils 67 | _ = name_util; 68 | _ = JsonReader; 69 | 70 | // Model 71 | _ = model; 72 | _ = @import("traits.zig"); 73 | 74 | // Systems 75 | _ = SymbolsProvider; 76 | _ = isu; 77 | _ = trt; 78 | _ = rls; 79 | 80 | // Parse 81 | _ = parse_issues; 82 | _ = @import("parse/Model.zig"); 83 | _ = @import("parse/props.zig"); 84 | _ = @import("parse/parse.zig"); 85 | 86 | // Render 87 | _ = @import("render/shape.zig"); 88 | _ = @import("render/schema.zig"); 89 | _ = gen_operation; 90 | _ = gen_endpoint; 91 | _ = gen_client; 92 | _ = gen_service; 93 | 94 | // Pipeline 95 | _ = pipeline; 96 | } 97 | -------------------------------------------------------------------------------- /smithy/codegen/systems/rules.zig: -------------------------------------------------------------------------------- 1 | const model = @import("rules/model.zig"); 2 | pub const Rule = model.Rule; 3 | pub const RuleSet = model.RuleSet; 4 | pub const ArgValue = model.ArgValue; 5 | pub const Parameter = model.Parameter; 6 | pub const StringKV = model.StringKV; 7 | pub const TestCase = model.TestCase; 8 | 9 | const parsing = @import("rules/parsing.zig"); 10 | pub const parseRuleSet = parsing.parseRuleSet; 11 | pub const parseTests = parsing.parseTests; 12 | 13 | const library = @import("rules/library.zig"); 14 | pub const BuiltIn = library.BuiltIn; 15 | pub const Function = library.Function; 16 | pub const BuiltInsRegistry = library.BuiltInsRegistry; 17 | pub const FunctionsRegistry = library.FunctionsRegistry; 18 | 19 | pub const std_builtins = library.std_builtins; 20 | pub const RulesEngine = @import("rules/RulesEngine.zig"); 21 | pub const Generator = @import("rules/Generator.zig"); 22 | 23 | test { 24 | _ = model; 25 | _ = parsing; 26 | _ = library; 27 | _ = RulesEngine; 28 | _ = Generator; 29 | } 30 | -------------------------------------------------------------------------------- /smithy/codegen/systems/rules/RulesEngine.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const testing = std.testing; 4 | const test_alloc = testing.allocator; 5 | const mdl = @import("model.zig"); 6 | const lib = @import("library.zig"); 7 | const Generator = @import("Generator.zig"); 8 | 9 | const Self = @This(); 10 | 11 | fn Map(comptime T: type) type { 12 | return std.AutoHashMapUnmanaged(T.Id, T); 13 | } 14 | 15 | built_ins: Map(lib.BuiltIn), 16 | functions: Map(lib.Function), 17 | 18 | pub fn init(allocator: Allocator, built_ins: lib.BuiltInsRegistry, functions: lib.FunctionsRegistry) !Self { 19 | var map_bi = try initMap(lib.BuiltIn, allocator, lib.std_builtins, built_ins); 20 | const map_fn = initMap(lib.Function, allocator, lib.std_functions, functions) catch |err| { 21 | map_bi.deinit(allocator); 22 | return err; 23 | }; 24 | 25 | return .{ 26 | .built_ins = map_bi, 27 | .functions = map_fn, 28 | }; 29 | } 30 | 31 | pub fn deinit(self: *Self, allocator: Allocator) void { 32 | self.built_ins.deinit(allocator); 33 | self.functions.deinit(allocator); 34 | } 35 | 36 | fn initMap(comptime T: type, allocator: Allocator, reg1: lib.Registry(T), reg2: lib.Registry(T)) !Map(T) { 37 | var map = Map(T){}; 38 | try map.ensureTotalCapacity(allocator, @intCast(reg1.len + reg2.len)); 39 | for (reg1) |kv| map.putAssumeCapacity(kv[0], kv[1]); 40 | for (reg2) |kv| map.putAssumeCapacity(kv[0], kv[1]); 41 | return map; 42 | } 43 | 44 | pub fn getBuiltIn(self: Self, id: lib.BuiltIn.Id) !lib.BuiltIn { 45 | return self.built_ins.get(id) orelse error.RulesBuiltInUnknown; 46 | } 47 | 48 | pub fn getFunc(self: Self, id: lib.Function.Id) !lib.Function { 49 | return self.functions.get(id) orelse error.RulesFuncUnknown; 50 | } 51 | 52 | pub fn getGenerator(self: Self, arena: Allocator, params: Generator.ParamsList) !Generator { 53 | return Generator.init(arena, self, params); 54 | } 55 | -------------------------------------------------------------------------------- /smithy/codegen/systems/rules/model.zig: -------------------------------------------------------------------------------- 1 | // https://github.com/awslabs/aws-c-sdkutils/blob/main/source/endpoints_ruleset.c 2 | // https://github.com/awslabs/aws-c-sdkutils/blob/main/source/endpoints_types_impl.c 3 | 4 | const std = @import("std"); 5 | const testing = std.testing; 6 | const lib = @import("library.zig"); 7 | const JsonReader = @import("../../utils/JsonReader.zig"); 8 | 9 | pub const Rule = union(enum) { 10 | endpoint: EndpointRule, 11 | err: ErrorRule, 12 | tree: TreeRule, 13 | }; 14 | 15 | pub const RuleSet = struct { 16 | /// A map of zero or more endpoint parameter names to their parameter 17 | /// configuration. 18 | parameters: []const StringKV(Parameter) = &.{}, 19 | /// One or more endpoint rule definitions of any rule type. 20 | rules: []const Rule = &.{}, 21 | }; 22 | 23 | pub const ParamValue = union(enum) { 24 | string: ?[]const u8, 25 | boolean: ?bool, 26 | string_array: ?[]const []const u8, 27 | 28 | pub fn hasDefault(self: ParamValue) bool { 29 | return switch (self) { 30 | inline else => |t| t != null, 31 | }; 32 | } 33 | }; 34 | 35 | pub const ArgValue = union(enum) { 36 | boolean: bool, 37 | integer: i32, 38 | string: []const u8, 39 | array: []const ArgValue, 40 | reference: []const u8, 41 | function: FunctionCall, 42 | }; 43 | 44 | /// The parameter typing is statically analyzed by the rules engine to validate 45 | /// correct usage within the rule set. 46 | /// 47 | /// [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/specification.html#parameter-object) 48 | pub const Parameter = struct { 49 | /// Optionally specifies the default value for the parameter if not set. 50 | type: ParamValue, 51 | /// Specifies a named built-in value that is sourced and provided to the 52 | /// endpoint provider by a caller. 53 | built_in: ?lib.BuiltIn.Id = null, 54 | /// Specifies that the parameter is required to be provided to the endpoint 55 | /// provider. 56 | required: bool = false, 57 | /// Specifies a string that will be used to generate API reference 58 | /// documentation for the endpoint parameter. 59 | documentation: []const u8 = "", 60 | /// Specifies whether an endpoint parameter has been deprecated. 61 | deprecated: ?Deprecated = null, 62 | }; 63 | 64 | /// [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/specification.html#deprecated-object) 65 | pub const Deprecated = struct { 66 | /// Specifies an optional message that can be used in documentation to provide recourse options to a user. 67 | message: ?[]const u8 = null, 68 | /// A date string that indicates when the parameter field was deprecated. 69 | since: ?[]const u8 = null, 70 | }; 71 | 72 | /// Defines an endpoint selected based on successful evaluation of rule 73 | /// conditions to that point. 74 | /// 75 | /// [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/specification.html#endpoint-object) 76 | pub const Endpoint = struct { 77 | /// The endpoint url. 78 | url: StringValue, 79 | /// A map containing zero or more key value property pairs. 80 | properties: ?[]const JsonReader.Value.KV = null, 81 | /// A map of transport header names to their respective values. 82 | headers: ?[]const StringKV([]const StringValue) = null, 83 | }; 84 | 85 | /// [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/specification.html#endpoint-rule-object) 86 | pub const EndpointRule = struct { 87 | /// Zero or more conditions used to determine whether the endpoint rule 88 | /// should be selected. 89 | conditions: []const Condition = &.{}, 90 | /// The endpoint to return if this rule is selected. 91 | endpoint: Endpoint, 92 | /// A description of the rule. 93 | documentation: ?[]const u8 = null, 94 | }; 95 | 96 | /// If all condition clauses evaluate successfully or zero conditions are 97 | /// defined, then the error rule _must_ be selected. 98 | /// 99 | /// [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/specification.html#error-rule-object) 100 | pub const ErrorRule = struct { 101 | /// Zero or more conditions used to determine whether the endpoint rule 102 | /// should be selected. 103 | conditions: []const Condition = &.{}, 104 | /// A descriptive message describing the error for consumption by the caller. 105 | message: StringValue, 106 | /// A description of the rule. 107 | documentation: ?[]const u8 = null, 108 | }; 109 | 110 | /// If all condition clauses evaluate successfully, the tree rule is selected. If a condition fails, evaluation of the rule MUST be terminated and evaluation proceeds to any subsequent rules. 111 | /// 112 | /// [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/specification.html#tree-rule-object) 113 | pub const TreeRule = struct { 114 | /// Zero or more conditions used to determine whether the endpoint rule 115 | /// should be selected. 116 | conditions: []const Condition = &.{}, 117 | /// One or more endpoint rule definitions of any rule type. 118 | rules: []const Rule = &.{}, 119 | /// A description of the rule. 120 | documentation: ?[]const u8 = null, 121 | }; 122 | 123 | /// Conditions are requirements for continuing to evaluate the rules within. 124 | /// Conditions are evaluated in-order by their positional index in the array, 125 | /// starting from zero. 126 | pub const Condition = struct { 127 | /// The name of the function to be executed. 128 | function: lib.Function.Id = lib.Function.Id.NULL, 129 | /// The arguments for the function. 130 | args: []const ArgValue = &.{}, 131 | /// The destination variable to assign the functions result to. 132 | assign: ?[]const u8 = null, 133 | }; 134 | 135 | /// [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/specification.html#function-object) 136 | pub const FunctionCall = struct { 137 | /// The name of the function to be executed. 138 | id: lib.Function.Id = lib.Function.Id.NULL, 139 | /// The arguments for the function. 140 | args: []const ArgValue = &.{}, 141 | }; 142 | 143 | pub fn StringKV(comptime T: type) type { 144 | return struct { 145 | key: []const u8, 146 | value: T, 147 | }; 148 | } 149 | 150 | pub const StringValue = union(enum) { 151 | string: []const u8, 152 | reference: []const u8, 153 | function: FunctionCall, 154 | }; 155 | 156 | pub const TestCase = struct { 157 | documentation: []const u8 = "", 158 | expect: Expect = .invalid, 159 | params: []const StringKV(ParamValue) = &.{}, 160 | 161 | pub const Expect = union(enum) { 162 | invalid: void, 163 | endpoint: Endpoint, 164 | err: []const u8, 165 | }; 166 | }; 167 | -------------------------------------------------------------------------------- /smithy/codegen/systems/traits.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const testing = std.testing; 4 | const test_alloc = std.testing.allocator; 5 | const mdl = @import("../model.zig"); 6 | const SmithyId = mdl.SmithyId; 7 | const TaggedValue = mdl.SmithyTaggedValue; 8 | const SymbolsProvider = @import("SymbolsProvider.zig"); 9 | const JsonReader = @import("../utils/JsonReader.zig"); 10 | 11 | /// Parse the trait’s value from the source JSON AST, which will be used 12 | /// during the source generation. 13 | const TraitParser = *const fn ( 14 | arena: Allocator, 15 | reader: *JsonReader, 16 | ) anyerror!*const anyopaque; 17 | 18 | pub const TraitsRegistry = []const struct { SmithyId, ?TraitParser }; 19 | 20 | /// Traits are model components that can be attached to shapes to describe additional 21 | /// information about the shape; shapes provide the structure and layout of an API, 22 | /// while traits provide refinement and style. 23 | /// 24 | /// [Smithy Spec](https://smithy.io/2.0/spec/model.html#traits) 25 | pub const TraitsManager = struct { 26 | traits: std.AutoHashMapUnmanaged(SmithyId, ?TraitParser) = .{}, 27 | 28 | pub fn deinit(self: *TraitsManager, allocator: Allocator) void { 29 | self.traits.deinit(allocator); 30 | self.* = undefined; 31 | } 32 | 33 | pub fn register(self: *TraitsManager, allocator: Allocator, id: SmithyId, parser: ?TraitParser) !void { 34 | try self.traits.put(allocator, id, parser); 35 | } 36 | 37 | pub fn registerAll(self: *TraitsManager, allocator: Allocator, traits: TraitsRegistry) !void { 38 | try self.traits.ensureUnusedCapacity(allocator, @truncate(traits.len)); 39 | for (traits) |t| { 40 | const id, const trait = t; 41 | self.traits.putAssumeCapacity(id, trait); 42 | } 43 | } 44 | 45 | /// Parse the trait’s value from the source JSON AST, which will be used 46 | /// during the source generation. 47 | pub fn parse(self: TraitsManager, trait_id: SmithyId, arena: Allocator, reader: *JsonReader) !?*const anyopaque { 48 | const trait = self.traits.get(trait_id) orelse return error.UnknownTrait; 49 | if (trait) |parseFn| { 50 | // Parse trait’s value 51 | return parseFn(arena, reader); 52 | } else { 53 | // Annotation trait – skip the empty `{}` 54 | try reader.skipValueOrScope(); 55 | return null; 56 | } 57 | } 58 | }; 59 | 60 | test "TraitManager" { 61 | const SkipTwoTrait = struct { 62 | fn parse(arena: Allocator, reader: *JsonReader) !*const anyopaque { 63 | for (0..2) |_| try reader.skipValueOrScope(); 64 | const dupe = try arena.dupe(u8, try reader.nextString()); 65 | return dupe.ptr; 66 | } 67 | 68 | pub fn translate(value: ?*const anyopaque) []const u8 { 69 | const ptr = @as([*]const u8, @ptrCast(value)); 70 | return ptr[0..3]; 71 | } 72 | }; 73 | 74 | var manager = TraitsManager{}; 75 | defer manager.deinit(test_alloc); 76 | 77 | const test_id = SmithyId.of("test"); 78 | try manager.register(test_alloc, test_id, SkipTwoTrait.parse); 79 | 80 | var reader = try JsonReader.initFixed(test_alloc, 81 | \\["foo", "bar", "baz", "qux"] 82 | ); 83 | defer reader.deinit(); 84 | 85 | _ = try reader.next(); 86 | const value = SkipTwoTrait.translate( 87 | try manager.parse(test_id, test_alloc, &reader), 88 | ); 89 | defer test_alloc.free(value); 90 | try testing.expectEqualStrings("baz", value); 91 | } 92 | 93 | pub const TraitsProvider = struct { 94 | values: []const TaggedValue, 95 | 96 | pub fn has(self: TraitsProvider, id: SmithyId) bool { 97 | for (self.values) |trait| { 98 | if (trait.id == id) return true; 99 | } 100 | return false; 101 | } 102 | 103 | pub fn TraitReturn(comptime T: type) type { 104 | return switch (@typeInfo(T)) { 105 | .bool, .int, .float, .@"enum", .@"union", .pointer => T, 106 | else => *const T, 107 | }; 108 | } 109 | 110 | pub fn get(self: TraitsProvider, comptime T: type, id: SmithyId) ?TraitReturn(T) { 111 | const trait = self.getOpaque(id) orelse return null; 112 | const ptr: *const T = @alignCast(@ptrCast(trait)); 113 | return switch (@typeInfo(T)) { 114 | .bool, .int, .float, .@"enum", .@"union", .pointer => ptr.*, 115 | else => ptr, 116 | }; 117 | } 118 | 119 | pub fn getOpaque(self: TraitsProvider, id: SmithyId) ?*const anyopaque { 120 | for (self.values) |trait| { 121 | if (trait.id == id) return trait.value; 122 | } 123 | return null; 124 | } 125 | }; 126 | 127 | test "TraitsProvider" { 128 | const int: u8 = 108; 129 | const traits = TraitsProvider{ .values = &.{ 130 | .{ .id = SmithyId.of("foo"), .value = null }, 131 | .{ .id = SmithyId.of("bar"), .value = &int }, 132 | } }; 133 | 134 | try testing.expect(traits.has(SmithyId.of("foo"))); 135 | try testing.expect(traits.has(SmithyId.of("bar"))); 136 | try testing.expect(!traits.has(SmithyId.of("baz"))); 137 | try testing.expectEqual( 138 | @intFromPtr(&int), 139 | @intFromPtr(traits.getOpaque(SmithyId.of("bar")).?), 140 | ); 141 | try testing.expectEqual(108, traits.get(u8, SmithyId.of("bar"))); 142 | } 143 | 144 | pub fn StringTrait(trait_id: []const u8) type { 145 | return struct { 146 | pub const id = SmithyId.of(trait_id); 147 | 148 | pub fn parse(arena: Allocator, reader: *JsonReader) !*const anyopaque { 149 | const value = try arena.create([]const u8); 150 | value.* = try reader.nextStringAlloc(arena); 151 | return @ptrCast(value); 152 | } 153 | 154 | pub fn get(symbols: *SymbolsProvider, shape_id: SmithyId) ?[]const u8 { 155 | return symbols.getTrait([]const u8, shape_id, id); 156 | } 157 | }; 158 | } 159 | 160 | test "StringTrait" { 161 | var arena = std.heap.ArenaAllocator.init(test_alloc); 162 | const arena_alloc = arena.allocator(); 163 | defer arena.deinit(); 164 | 165 | const TestTrait = StringTrait("smithy.api#test"); 166 | 167 | var reader = try JsonReader.initFixed(arena_alloc, "\"Foo Bar\""); 168 | const val: *const []const u8 = @alignCast(@ptrCast(TestTrait.parse(arena_alloc, &reader) catch |e| { 169 | reader.deinit(); 170 | return e; 171 | })); 172 | reader.deinit(); 173 | try testing.expectEqualStrings("Foo Bar", val.*); 174 | } 175 | -------------------------------------------------------------------------------- /smithy/codegen/testing/rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0", 3 | "parameters": { 4 | "Foo": { 5 | "builtIn": "Foo", 6 | "required": true, 7 | "documentation": "Foo docs...", 8 | "default": "Bar", 9 | "type": "String", 10 | "deprecated": { 11 | "message": "Baz", 12 | "since": "0.8" 13 | } 14 | } 15 | }, 16 | "rules": [ 17 | { 18 | "conditions": [ 19 | { 20 | "fn": "foo", 21 | "assign": "bar", 22 | "argv": [ 23 | "baz", 24 | true, 25 | [], 26 | { "ref": "qux" }, 27 | { "fn": "Bar", "argv": [] } 28 | ] 29 | } 30 | ], 31 | "rules": [ 32 | { 33 | "conditions": [], 34 | "error": "BOOM" 35 | }, 36 | { 37 | "conditions": [], 38 | "endpoint": { 39 | "url": "http://example.com", 40 | "properties": { "foo": null }, 41 | "headers": { "bar": [] } 42 | } 43 | } 44 | ], 45 | "documentation": "Tree docs...", 46 | "type": "tree" 47 | } 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /smithy/codegen/testing/rules_cases.json: -------------------------------------------------------------------------------- 1 | { 2 | "testCases": [ 3 | { 4 | "documentation": "Test 1", 5 | "expect": { 6 | "endpoint": { 7 | "url": "https://example.com", 8 | "headers": { 9 | "foo": ["bar", "baz"] 10 | }, 11 | "properties": { 12 | "qux": null 13 | } 14 | } 15 | }, 16 | "params": { 17 | "Foo": "bar", 18 | "Baz": true 19 | } 20 | }, 21 | { 22 | "documentation": "Test 2", 23 | "expect": { 24 | "error": "Fail..." 25 | }, 26 | "params": { 27 | "Foo": "bar" 28 | } 29 | }, 30 | { 31 | "documentation": "Test 3", 32 | "expect": { 33 | "error": "Boom!" 34 | } 35 | } 36 | ], 37 | "version": "1.0" 38 | } 39 | -------------------------------------------------------------------------------- /smithy/codegen/testing/shapes.json: -------------------------------------------------------------------------------- 1 | { 2 | "smithy": "2.0", 3 | "metadata": { 4 | "nul": null, 5 | "bol": true, 6 | "int": 108, 7 | "flt": 1.08, 8 | "str": "foo", 9 | "lst": [108, 109], 10 | "map": { 11 | "key": 108 12 | } 13 | }, 14 | "shapes": { 15 | "test.simple#Blob": { 16 | "type": "blob", 17 | "traits": { 18 | "test.trait#Void": {} 19 | } 20 | }, 21 | "test.simple#Boolean": { 22 | "type": "boolean", 23 | "mixins": [ 24 | { 25 | "target": "test.mixin#Mixin" 26 | } 27 | ] 28 | }, 29 | "test.simple#Document": { 30 | "type": "document" 31 | }, 32 | "test.simple#String": { 33 | "type": "string" 34 | }, 35 | "test.simple#Byte": { 36 | "type": "byte" 37 | }, 38 | "test.simple#Short": { 39 | "type": "short" 40 | }, 41 | "test.simple#Integer": { 42 | "type": "integer" 43 | }, 44 | "test.simple#Long": { 45 | "type": "long" 46 | }, 47 | "test.simple#Float": { 48 | "type": "float" 49 | }, 50 | "test.simple#Double": { 51 | "type": "double" 52 | }, 53 | "test.simple#BigInteger": { 54 | "type": "bigInteger" 55 | }, 56 | "test.simple#BigDecimal": { 57 | "type": "bigDecimal" 58 | }, 59 | "test.simple#Timestamp": { 60 | "type": "timestamp" 61 | }, 62 | "test.simple#Enum": { 63 | "type": "enum", 64 | "members": { 65 | "FOO": { 66 | "target": "smithy.api#Unit", 67 | "traits": { 68 | "smithy.api#enumValue": "foo" 69 | } 70 | } 71 | } 72 | }, 73 | "test.simple#IntEnum": { 74 | "type": "intEnum", 75 | "members": { 76 | "FOO": { 77 | "target": "smithy.api#Unit", 78 | "traits": { 79 | "smithy.api#enumValue": 1 80 | } 81 | } 82 | } 83 | }, 84 | "test.aggregate#List": { 85 | "type": "list", 86 | "member": { 87 | "target": "smithy.api#String", 88 | "traits": { 89 | "test.trait#Void": {} 90 | } 91 | } 92 | }, 93 | "test.aggregate#Map": { 94 | "type": "map", 95 | "key": { 96 | "target": "smithy.api#String" 97 | }, 98 | "value": { 99 | "target": "smithy.api#Integer" 100 | } 101 | }, 102 | "test.aggregate#Structure": { 103 | "type": "structure", 104 | "members": { 105 | "stringMember": { 106 | "target": "smithy.api#String", 107 | "traits": { 108 | "test.trait#Void": {}, 109 | "test.trait#Int": 108 110 | } 111 | }, 112 | "numberMember": { 113 | "target": "smithy.api#Integer", 114 | "traits": { 115 | "test.trait#Void": {}, 116 | "test.trait#Unknown": {} 117 | } 118 | }, 119 | "primitiveBool": { 120 | "target": "smithy.api#PrimitiveBoolean" 121 | }, 122 | "primitiveByte": { 123 | "target": "smithy.api#PrimitiveByte" 124 | }, 125 | "primitiveShort": { 126 | "target": "smithy.api#PrimitiveShort" 127 | }, 128 | "primitiveInt": { 129 | "target": "smithy.api#PrimitiveInteger" 130 | }, 131 | "primitiveLong": { 132 | "target": "smithy.api#PrimitiveLong" 133 | }, 134 | "primitiveFloat": { 135 | "target": "smithy.api#PrimitiveFloat" 136 | }, 137 | "primitiveDouble": { 138 | "target": "smithy.api#PrimitiveDouble" 139 | } 140 | }, 141 | "unexpected": {} 142 | }, 143 | "test.aggregate#Union": { 144 | "type": "union", 145 | "members": { 146 | "a": { 147 | "target": "smithy.api#String" 148 | }, 149 | "b": { 150 | "target": "smithy.api#Integer" 151 | } 152 | } 153 | }, 154 | "test.serve#Operation": { 155 | "type": "operation", 156 | "input": { 157 | "target": "test.operation#OperationInput" 158 | }, 159 | "output": { 160 | "target": "test.operation#OperationOutput" 161 | }, 162 | "errors": [ 163 | { 164 | "target": "test.error#BadRequestError" 165 | }, 166 | { 167 | "target": "test.error#NotFoundError" 168 | } 169 | ] 170 | }, 171 | "test.serve#Resource": { 172 | "type": "resource", 173 | "identifiers": { 174 | "forecastId": { 175 | "target": "smithy.api#String" 176 | } 177 | }, 178 | "properties": { 179 | "prop": { 180 | "target": "test.resource#prop" 181 | } 182 | }, 183 | "create": { 184 | "target": "test.resource#Create" 185 | }, 186 | "read": { 187 | "target": "test.resource#Get" 188 | }, 189 | "update": { 190 | "target": "test.resource#Update" 191 | }, 192 | "delete": { 193 | "target": "test.resource#Delete" 194 | }, 195 | "list": { 196 | "target": "test.resource#List" 197 | }, 198 | "operations": [ 199 | { 200 | "target": "test.resource#InstanceOperation" 201 | } 202 | ], 203 | "collectionOperations": [ 204 | { 205 | "target": "test.resource#CollectionOperation" 206 | } 207 | ], 208 | "resources": [ 209 | { 210 | "target": "test.resource#OtherResource" 211 | } 212 | ] 213 | }, 214 | "test.serve#Service": { 215 | "type": "service", 216 | "version": "2017-02-11", 217 | "operations": [ 218 | { 219 | "target": "test.serve#Operation" 220 | } 221 | ], 222 | "resources": [ 223 | { 224 | "target": "test.serve#Resource" 225 | } 226 | ], 227 | "errors": [ 228 | { 229 | "target": "test.serve#Error" 230 | } 231 | ], 232 | "traits": { 233 | "test.trait#Int": 108 234 | }, 235 | "rename": { 236 | "foo.example#Foo": "NewFoo", 237 | "bar.example#Bar": "NewBar" 238 | } 239 | }, 240 | "test.aggregate#Structure$numberMember": { 241 | "type": "apply", 242 | "traits": { 243 | "test.trait#Int": 108 244 | } 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /smithy/codegen/traits.zig: -------------------------------------------------------------------------------- 1 | //! All Smithy models automatically include a prelude. 2 | //! 3 | //! The prelude defines various simple shapes and every trait defined in the 4 | //! core specification. When using the IDL, shapes defined in the prelude that 5 | //! are not marked with the private trait can be referenced from within any 6 | //! namespace using a relative shape ID. 7 | //! 8 | //! [Smithy Spec](https://smithy.io/2.0/spec/model.html#prelude) 9 | const std = @import("std"); 10 | const TraitsManager = @import("systems/traits.zig").TraitsManager; 11 | 12 | pub const prelude = struct { 13 | pub const auth = @import("traits/auth.zig"); 14 | pub const behavior = @import("traits/behavior.zig"); 15 | pub const constraint = @import("traits/constraint.zig"); 16 | pub const docs = @import("traits/docs.zig"); 17 | pub const endpoint = @import("traits/endpoint.zig"); 18 | pub const http = @import("traits/http.zig"); 19 | pub const protocol = @import("traits/protocol.zig"); 20 | pub const refine = @import("traits/refine.zig"); 21 | pub const resource = @import("traits/resource.zig"); 22 | pub const stream = @import("traits/stream.zig"); 23 | pub const validate = @import("traits/validate.zig"); 24 | 25 | pub const compliance = @import("traits/compliance.zig"); 26 | pub const smoke = @import("traits/smoke.zig"); 27 | pub const waiters = @import("traits/waiters.zig"); 28 | pub const mqtt = @import("traits/mqtt.zig"); 29 | pub const rules = @import("traits/rules.zig"); 30 | }; 31 | 32 | pub fn registerTraits(allocator: std.mem.Allocator, manager: *TraitsManager) !void { 33 | try manager.registerAll(allocator, prelude.auth.registry); 34 | try manager.registerAll(allocator, prelude.behavior.registry); 35 | try manager.registerAll(allocator, prelude.constraint.registry); 36 | try manager.registerAll(allocator, prelude.docs.registry); 37 | try manager.registerAll(allocator, prelude.endpoint.registry); 38 | try manager.registerAll(allocator, prelude.http.registry); 39 | try manager.registerAll(allocator, prelude.protocol.registry); 40 | try manager.registerAll(allocator, prelude.refine.registry); 41 | try manager.registerAll(allocator, prelude.resource.registry); 42 | try manager.registerAll(allocator, prelude.stream.registry); 43 | 44 | try manager.registerAll(allocator, prelude.validate.registry); 45 | try manager.registerAll(allocator, prelude.compliance.registry); 46 | try manager.registerAll(allocator, prelude.smoke.registry); 47 | try manager.registerAll(allocator, prelude.waiters.registry); 48 | try manager.registerAll(allocator, prelude.mqtt.registry); 49 | try manager.registerAll(allocator, prelude.rules.registry); 50 | } 51 | 52 | test { 53 | _ = prelude.auth; 54 | _ = prelude.behavior; 55 | _ = prelude.constraint; 56 | _ = prelude.docs; 57 | _ = prelude.endpoint; 58 | _ = prelude.http; 59 | _ = prelude.protocol; 60 | _ = prelude.refine; 61 | _ = prelude.resource; 62 | _ = prelude.stream; 63 | _ = prelude.validate; 64 | _ = prelude.compliance; 65 | _ = prelude.smoke; 66 | _ = prelude.waiters; 67 | _ = prelude.mqtt; 68 | _ = prelude.rules; 69 | } 70 | -------------------------------------------------------------------------------- /smithy/codegen/traits/behavior.zig: -------------------------------------------------------------------------------- 1 | //! Behavior traits 2 | //! 3 | //! [Smithy Spec](https://smithy.io/2.0/spec/behavior-traits.html) 4 | const std = @import("std"); 5 | const mem = std.mem; 6 | const Allocator = mem.Allocator; 7 | const testing = std.testing; 8 | const test_alloc = testing.allocator; 9 | const SmithyId = @import("../model.zig").SmithyId; 10 | const JsonReader = @import("../utils/JsonReader.zig"); 11 | const trt = @import("../systems/traits.zig"); 12 | const TraitsRegistry = trt.TraitsRegistry; 13 | const SymbolsProvider = @import("../systems/SymbolsProvider.zig"); 14 | 15 | // TODO: Remainig traits 16 | pub const registry: TraitsRegistry = &.{ 17 | // smithy.api#idempotencyToken 18 | // smithy.api#idempotent 19 | // smithy.api#readonly 20 | .{ retryable_id, null }, 21 | .{ Paginated.id, Paginated.parse }, 22 | // smithy.api#requestCompression 23 | }; 24 | 25 | /// Indicates that an error MAY be retried by the client. 26 | /// 27 | /// [Smithy Spec](https://smithy.io/2.0/spec/behavior-traits.html#retryable-trait) 28 | pub const retryable_id = SmithyId.of("smithy.api#retryable"); 29 | 30 | /// Indicates that an operation intentionally limits the number of results returned in a 31 | /// single response and that multiple invocations might be necessary to retrieve all results. 32 | /// 33 | /// [Smithy Spec](https://smithy.io/2.0/spec/behavior-traits.html#pagination) 34 | pub const Paginated = struct { 35 | pub const id = SmithyId.of("smithy.api#paginated"); 36 | 37 | pub const Val = struct { 38 | /// The name of the operation input member that contains a continuation token. 39 | input_token: ?[]const u8 = null, 40 | /// The path to the operation output member that contains an optional continuation token. 41 | output_token: ?[]const u8 = null, 42 | /// The path to an output member of the operation that contains the data 43 | /// that is being paginated across many responses. 44 | items: ?[]const u8 = null, 45 | /// The name of an operation input member that limits the maximum number 46 | /// of results to include in the operation output. 47 | page_size: ?[]const u8 = null, 48 | 49 | pub fn isPartial(self: Val) bool { 50 | return self.input_token == null or 51 | self.output_token == null or 52 | self.items == null or 53 | self.page_size == null; 54 | } 55 | }; 56 | 57 | pub fn parse(arena: Allocator, reader: *JsonReader) !*const anyopaque { 58 | var val = Val{}; 59 | try reader.nextObjectBegin(); 60 | while (try reader.peek() != .object_end) { 61 | const prop = try reader.nextString(); 62 | if (mem.eql(u8, "inputToken", prop)) { 63 | val.input_token = try reader.nextStringAlloc(arena); 64 | } else if (mem.eql(u8, "outputToken", prop)) { 65 | val.output_token = try reader.nextStringAlloc(arena); 66 | } else if (mem.eql(u8, "items", prop)) { 67 | val.items = try reader.nextStringAlloc(arena); 68 | } else if (mem.eql(u8, "pageSize", prop)) { 69 | val.page_size = try reader.nextStringAlloc(arena); 70 | } else { 71 | std.log.warn("Unknown paginated trait property `{s}`", .{prop}); 72 | try reader.skipValueOrScope(); 73 | } 74 | } 75 | try reader.nextObjectEnd(); 76 | 77 | const value = try arena.create(Val); 78 | value.* = val; 79 | return value; 80 | } 81 | 82 | pub fn get(symbols: *SymbolsProvider, shape_id: SmithyId) ?Val { 83 | const val = symbols.getTrait(Val, shape_id, id) orelse return null; 84 | return val.*; 85 | } 86 | }; 87 | 88 | test Paginated { 89 | var arena = std.heap.ArenaAllocator.init(test_alloc); 90 | const arena_alloc = arena.allocator(); 91 | defer arena.deinit(); 92 | 93 | var reader = try JsonReader.initFixed(arena_alloc, 94 | \\{ 95 | \\ "inputToken": "foo", 96 | \\ "outputToken": "bar", 97 | \\ "items": "baz", 98 | \\ "pageSize": "qux" 99 | \\} 100 | ); 101 | 102 | const val_int: *const Paginated.Val = @alignCast(@ptrCast(Paginated.parse(arena_alloc, &reader) catch |e| { 103 | reader.deinit(); 104 | return e; 105 | })); 106 | reader.deinit(); 107 | try testing.expectEqualDeep(&Paginated.Val{ 108 | .input_token = "foo", 109 | .output_token = "bar", 110 | .items = "baz", 111 | .page_size = "qux", 112 | }, val_int); 113 | } 114 | -------------------------------------------------------------------------------- /smithy/codegen/traits/compliance.zig: -------------------------------------------------------------------------------- 1 | //! HTTP Protocol Compliance Tests 2 | //! 3 | //! Smithy is a protocol-agnostic IDL that tries to abstract the serialization 4 | //! format of request and response messages sent between a client and server. 5 | //! 6 | //! [Smithy Spec](https://smithy.io/2.0/additional-specs/http-protocol-compliance-tests.html) 7 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 8 | 9 | // TODO: Remainig traits 10 | pub const registry: TraitsRegistry = &.{ 11 | // smithy.test#httpMalformedRequestTests 12 | // smithy.test#httpRequestTests 13 | // smithy.test#httpResponseTests 14 | }; 15 | -------------------------------------------------------------------------------- /smithy/codegen/traits/docs.zig: -------------------------------------------------------------------------------- 1 | //! Documentation traits describe shapes in the model in a way that does not 2 | //! materially affect the semantics of the model. 3 | //! 4 | //! [Smithy Spec](https://smithy.io/2.0/spec/documentation-traits.html) 5 | const std = @import("std"); 6 | const Allocator = std.mem.Allocator; 7 | const testing = std.testing; 8 | const test_alloc = testing.allocator; 9 | const trt = @import("../systems/traits.zig"); 10 | const StringTrait = trt.StringTrait; 11 | const TraitsRegistry = trt.TraitsRegistry; 12 | const JsonReader = @import("../utils/JsonReader.zig"); 13 | 14 | // TODO: Remainig traits 15 | pub const registry: TraitsRegistry = &.{ 16 | // smithy.api#deprecated 17 | .{ Documentation.id, Documentation.parse }, 18 | // smithy.api#examples 19 | // smithy.api#externalDocumentation 20 | // smithy.api#internal 21 | // smithy.api#recommended 22 | // smithy.api#sensitive 23 | // smithy.api#since 24 | // smithy.api#tags 25 | .{ Title.id, Title.parse }, 26 | // smithy.api#unstable 27 | }; 28 | 29 | /// Adds documentation to a shape or member using the [CommonMark](https://spec.commonmark.org) format. 30 | /// 31 | /// [Smithy Spec](https://smithy.io/2.0/spec/documentation-traits.html#documentation-trait) 32 | pub const Documentation = StringTrait("smithy.api#documentation"); 33 | 34 | /// Defines a proper name for a service or resource shape. 35 | /// 36 | /// [Smithy Spec](https://smithy.io/2.0/spec/documentation-traits.html#title-trait) 37 | pub const Title = StringTrait("smithy.api#title"); 38 | -------------------------------------------------------------------------------- /smithy/codegen/traits/endpoint.zig: -------------------------------------------------------------------------------- 1 | //! Endpoint traits 2 | //! 3 | //! [Smithy Spec](https://smithy.io/2.0/spec/endpoint-traits.html) 4 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 5 | 6 | // TODO: Remainig traits 7 | pub const registry: TraitsRegistry = &.{ 8 | // smithy.api#endpoint 9 | // smithy.api#hostLabel 10 | }; 11 | -------------------------------------------------------------------------------- /smithy/codegen/traits/mqtt.zig: -------------------------------------------------------------------------------- 1 | //! MQTT Protocol Bindings 2 | //! 3 | //! [Smithy Spec](https://smithy.io/2.0/additional-specs/mqtt.html) 4 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 5 | 6 | // TODO: Remainig traits 7 | pub const registry: TraitsRegistry = &.{ 8 | // smithy.mqtt#publish 9 | // smithy.mqtt#subscribe 10 | // smithy.mqtt#topicLabel 11 | }; 12 | -------------------------------------------------------------------------------- /smithy/codegen/traits/resource.zig: -------------------------------------------------------------------------------- 1 | //! Resource traits 2 | //! 3 | //! [Smithy Spec](https://smithy.io/2.0/spec/resource-traits.html) 4 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 5 | 6 | // TODO: Remainig traits 7 | pub const registry: TraitsRegistry = &.{ 8 | // smithy.api#nestedProperties 9 | // smithy.api#noReplace 10 | // smithy.api#notProperty 11 | // smithy.api#property 12 | // smithy.api#references 13 | // smithy.api#resourceIdentifier 14 | }; 15 | -------------------------------------------------------------------------------- /smithy/codegen/traits/rules.zig: -------------------------------------------------------------------------------- 1 | //! The Smithy rules engine provides service owners with a collection of traits 2 | //! and components to define rule sets. Rule sets specify a type of client 3 | //! behavior to be resolved at runtime, for example rules-based endpoint or 4 | //! authentication scheme resolution. 5 | //! 6 | //! [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/index.html) 7 | const std = @import("std"); 8 | const Allocator = std.mem.Allocator; 9 | const testing = std.testing; 10 | const test_alloc = testing.allocator; 11 | const JsonReader = @import("../utils/JsonReader.zig"); 12 | const rls = @import("../systems/rules.zig"); 13 | const SmithyId = @import("../model.zig").SmithyId; 14 | const SymbolsProvider = @import("../systems/SymbolsProvider.zig"); 15 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 16 | 17 | // TODO: Remainig traits 18 | pub const registry: TraitsRegistry = &.{ 19 | // smithy.rules#clientContextParams 20 | // smithy.rules#contextParam 21 | // smithy.rules#operationContextParams 22 | // smithy.rules#staticContextParams 23 | .{ EndpointRuleSet.id, EndpointRuleSet.parse }, 24 | .{ EndpointTests.id, EndpointTests.parse }, 25 | }; 26 | 27 | /// Defines a rule set for deriving service endpoints at runtime. 28 | /// 29 | /// [Smithy Spec](https://smithy.io/2.0/additional-specs/rules-engine/specification.html#smithy-rules-endpointruleset-trait) 30 | pub const EndpointRuleSet = struct { 31 | pub const id = SmithyId.of("smithy.rules#endpointRuleSet"); 32 | 33 | pub fn get(symbols: *SymbolsProvider, shape_id: SmithyId) ?*const rls.RuleSet { 34 | return symbols.getTrait(rls.RuleSet, shape_id, id); 35 | } 36 | 37 | pub fn parse(arena: Allocator, reader: *JsonReader) !*const anyopaque { 38 | const value = try arena.create(rls.RuleSet); 39 | errdefer arena.destroy(value); 40 | 41 | value.* = try rls.parseRuleSet(arena, reader); 42 | return value; 43 | } 44 | }; 45 | 46 | test EndpointRuleSet { 47 | var arena = std.heap.ArenaAllocator.init(test_alloc); 48 | const arena_alloc = arena.allocator(); 49 | defer arena.deinit(); 50 | 51 | var reader = try JsonReader.initFixed(arena_alloc, 52 | \\{ 53 | \\ "version": "1.0", 54 | \\ "parameters": {}, 55 | \\ "rules": [] 56 | \\} 57 | ); 58 | const value: *const rls.RuleSet = @alignCast(@ptrCast(EndpointRuleSet.parse(arena_alloc, &reader) catch |e| { 59 | reader.deinit(); 60 | return e; 61 | })); 62 | reader.deinit(); 63 | try testing.expectEqualDeep(&rls.RuleSet{}, value); 64 | } 65 | 66 | /// Currently undocumented by the Smithy Spec. 67 | pub const EndpointTests = struct { 68 | pub const id = SmithyId.of("smithy.rules#endpointTests"); 69 | 70 | pub fn parse(arena: Allocator, reader: *JsonReader) !*const anyopaque { 71 | const tests = try rls.parseTests(arena, reader); 72 | return tests.ptr; 73 | } 74 | 75 | pub fn get(symbols: *SymbolsProvider, shape_id: SmithyId) ?[]const rls.TestCase { 76 | const trait = symbols.getTraitOpaque(shape_id, id); 77 | return if (trait) |ptr| cast(ptr) else null; 78 | } 79 | 80 | fn cast(ptr: *const anyopaque) ?[]const rls.TestCase { 81 | const items: [*]const rls.TestCase = @ptrCast(@alignCast(ptr)); 82 | var i: usize = 0; 83 | while (true) : (i += 1) { 84 | const item = items[i]; 85 | if (item.expect == .invalid) return items[0..i]; 86 | } 87 | unreachable; 88 | } 89 | }; 90 | 91 | test EndpointTests { 92 | var arena = std.heap.ArenaAllocator.init(test_alloc); 93 | const arena_alloc = arena.allocator(); 94 | defer arena.deinit(); 95 | 96 | var reader = try JsonReader.initFixed(arena_alloc, 97 | \\{ 98 | \\ "testCases": [], 99 | \\ "version": "1.0" 100 | \\} 101 | ); 102 | const value: ?[]const rls.TestCase = EndpointTests.cast(EndpointTests.parse(arena_alloc, &reader) catch |e| { 103 | reader.deinit(); 104 | return e; 105 | }); 106 | reader.deinit(); 107 | 108 | try testing.expectEqualDeep(&[_]rls.TestCase{}, value); 109 | } 110 | -------------------------------------------------------------------------------- /smithy/codegen/traits/smoke.zig: -------------------------------------------------------------------------------- 1 | //! Smoke tests are small, simple tests intended to uncover large issues by 2 | //! ensuring core functionality works as expected. 3 | //! 4 | //! [Smithy Spec](https://smithy.io/2.0/additional-specs/smoke-tests.html) 5 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 6 | 7 | // TODO: Remainig traits 8 | pub const registry: TraitsRegistry = &.{ 9 | // smithy.test#smokeTests 10 | }; 11 | -------------------------------------------------------------------------------- /smithy/codegen/traits/stream.zig: -------------------------------------------------------------------------------- 1 | //! Smithy operations can send and receive [streams of data](https://smithy.io/2.0/spec/streaming.html#data-streams) 2 | //! or [streams of events](https://smithy.io/2.0/spec/streaming.html#event-streams). 3 | //! 4 | //! [Smithy Spec](https://smithy.io/2.0/spec/streaming.html) 5 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 6 | 7 | // TODO: Remainig traits 8 | pub const registry: TraitsRegistry = &.{ 9 | // smithy.api#eventHeader 10 | // smithy.api#eventPayload 11 | // smithy.api#requiresLength 12 | // smithy.api#streaming 13 | }; 14 | -------------------------------------------------------------------------------- /smithy/codegen/traits/validate.zig: -------------------------------------------------------------------------------- 1 | //! Model validation 2 | //! 3 | //! [Smithy Spec](https://smithy.io/2.0/spec/model-validation.html) 4 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 5 | 6 | // TODO: Remainig traits 7 | pub const registry: TraitsRegistry = &.{ 8 | // smithy.api#suppress 9 | // smithy.api#traitValidators 10 | }; 11 | -------------------------------------------------------------------------------- /smithy/codegen/traits/waiters.zig: -------------------------------------------------------------------------------- 1 | //! Waiters are a client-side abstraction used to poll a resource until a 2 | //! desired state is reached, or until it is determined that the resource will 3 | //! never enter into the desired state. 4 | //! 5 | //! [Smithy Spec](https://smithy.io/2.0/additional-specs/waiters.html) 6 | const TraitsRegistry = @import("../systems/traits.zig").TraitsRegistry; 7 | 8 | // TODO: Remainig traits 9 | pub const registry: TraitsRegistry = &.{ 10 | // smithy.waiters#waitable 11 | }; 12 | -------------------------------------------------------------------------------- /smithy/runtime/operation/document.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const testing = std.testing; 3 | const Allocator = std.mem.Allocator; 4 | 5 | pub const Document = union(enum) { 6 | null, 7 | boolean: bool, 8 | integer: i64, 9 | float: f64, 10 | string: []const u8, 11 | string_alloc: []const u8, 12 | array: []const Document, 13 | array_alloc: []const Document, 14 | object: []const KV, 15 | object_alloc: []const KV, 16 | 17 | pub const KV = struct { 18 | key: []const u8, 19 | key_alloc: bool, 20 | document: Document, 21 | 22 | pub fn deinit(self: KV, allocator: Allocator) void { 23 | if (self.key_alloc) allocator.free(self.key); 24 | self.document.deinit(allocator); 25 | } 26 | }; 27 | 28 | pub fn deinit(self: Document, allocator: Allocator) void { 29 | switch (self) { 30 | .string_alloc => |s| allocator.free(s), 31 | inline .array, .object => |scope| { 32 | for (scope) |item| item.deinit(allocator); 33 | }, 34 | inline .array_alloc, .object_alloc => |scope| { 35 | for (scope) |item| item.deinit(allocator); 36 | allocator.free(scope); 37 | }, 38 | else => {}, 39 | } 40 | } 41 | 42 | pub fn getString(self: Document) []const u8 { 43 | return switch (self) { 44 | .string, .string_alloc => |s| s, 45 | else => unreachable, 46 | }; 47 | } 48 | 49 | pub fn getArray(self: Document) []const Document { 50 | return switch (self) { 51 | .array, .array_alloc => |s| s, 52 | else => unreachable, 53 | }; 54 | } 55 | 56 | pub fn getObject(self: Document) []const KV { 57 | return switch (self) { 58 | .object, .object_alloc => |s| s, 59 | else => unreachable, 60 | }; 61 | } 62 | }; 63 | 64 | test "Document.deinit" { 65 | var gpa: std.heap.GeneralPurposeAllocator(.{ 66 | .never_unmap = true, 67 | .retain_metadata = true, 68 | }) = .{}; 69 | const alloc = gpa.allocator(); 70 | 71 | const ary_alloc = try alloc.alloc(Document, 3); 72 | ary_alloc[0] = Document{ .boolean = true }; 73 | ary_alloc[1] = Document{ .integer = 108 }; 74 | ary_alloc[2] = Document{ .float = 1.08 }; 75 | 76 | const obj_alloc = try alloc.alloc(Document.KV, 2); 77 | obj_alloc[0] = Document.KV{ 78 | .key = try alloc.dupe(u8, "null"), 79 | .key_alloc = true, 80 | .document = Document.null, 81 | }; 82 | obj_alloc[1] = Document.KV{ 83 | .key = "obj", 84 | .key_alloc = false, 85 | .document = .{ .object = &.{ 86 | Document.KV{ 87 | .key = "ary", 88 | .key_alloc = false, 89 | .document = Document{ .array = &.{ 90 | Document{ .string = "str" }, 91 | Document{ .string_alloc = try alloc.dupe(u8, "str_alloc") }, 92 | } }, 93 | }, 94 | Document.KV{ 95 | .key = "ary_alloc", 96 | .key_alloc = false, 97 | .document = Document{ .array_alloc = ary_alloc }, 98 | }, 99 | } }, 100 | }; 101 | 102 | const doc = Document{ .object_alloc = obj_alloc }; 103 | doc.deinit(alloc); 104 | 105 | try testing.expectEqual(.ok, gpa.deinit()); 106 | } 107 | -------------------------------------------------------------------------------- /smithy/runtime/operation/request.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const Document = @import("document.zig").Document; 4 | 5 | pub const HttpHeader = struct { 6 | key: []const u8, 7 | values: []const []const u8, 8 | 9 | pub fn deinit(self: HttpHeader, allocator: Allocator) void { 10 | for (self.values) |value| allocator.free(value); 11 | allocator.free(self.values); 12 | allocator.free(self.key); 13 | } 14 | }; 15 | 16 | pub const Endpoint = struct { 17 | url: []const u8, 18 | headers: []const HttpHeader, 19 | properties: []const Document.KV, 20 | auth_schemes: []const AuthScheme, 21 | 22 | pub fn deinit(self: Endpoint, allocator: Allocator) void { 23 | for (self.auth_schemes) |auth_scheme| auth_scheme.deinit(allocator); 24 | for (self.properties) |property| property.deinit(allocator); 25 | for (self.headers) |header| header.deinit(allocator); 26 | allocator.free(self.auth_schemes); 27 | allocator.free(self.properties); 28 | allocator.free(self.headers); 29 | allocator.free(self.url); 30 | } 31 | }; 32 | 33 | pub const AuthScheme = struct { 34 | id: AuthId, 35 | properties: []const Document.KV = &.{}, 36 | 37 | pub fn deinit(self: AuthScheme, allocator: Allocator) void { 38 | for (self.properties) |property| property.deinit(allocator); 39 | allocator.free(self.properties); 40 | } 41 | }; 42 | 43 | pub const AuthId = enum(u64) { 44 | none = std.mem.bytesToValue(u64, "00000000"), 45 | _, 46 | 47 | pub fn of(name: []const u8) AuthId { 48 | var x: u64 = 0; 49 | const len = @min(name.len, @sizeOf(@TypeOf(x))); 50 | @memcpy(std.mem.asBytes(&x)[0..len], name[0..len]); 51 | return @enumFromInt(x); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /smithy/runtime/operation/validate.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const mvzr = @import("mvzr"); 3 | 4 | const base_format = "Field `{s}.{s}` "; 5 | pub const Error = error{InvalidOperationInput}; 6 | 7 | pub fn valueRange( 8 | comptime service: @Type(.enum_literal), 9 | container: []const u8, 10 | field: []const u8, 11 | comptime T: type, 12 | min: ?T, 13 | max: ?T, 14 | value: T, 15 | ) !void { 16 | const log = std.log.scoped(service); 17 | 18 | if (min) |d| if (value < d) { 19 | @branchHint(.unlikely); 20 | log.err(base_format ++ "value is less than {d}", .{ container, field, d }); 21 | return Error.InvalidOperationInput; 22 | }; 23 | 24 | if (max) |d| if (value > d) { 25 | @branchHint(.unlikely); 26 | log.err(base_format ++ "value is more than {d}", .{ container, field, d }); 27 | return Error.InvalidOperationInput; 28 | }; 29 | } 30 | 31 | pub fn collectionLength( 32 | comptime service: @Type(.enum_literal), 33 | container: []const u8, 34 | field: []const u8, 35 | min: ?usize, 36 | max: ?usize, 37 | count: usize, 38 | ) !void { 39 | const log = std.log.scoped(service); 40 | 41 | if (min) |d| if (count < d) { 42 | @branchHint(.unlikely); 43 | log.err(base_format ++ "has less than {d} items", .{ container, field, d }); 44 | return Error.InvalidOperationInput; 45 | }; 46 | 47 | if (max) |d| if (count > d) { 48 | @branchHint(.unlikely); 49 | log.err(base_format ++ "has more than {d} items", .{ container, field, d }); 50 | return Error.InvalidOperationInput; 51 | }; 52 | } 53 | 54 | pub fn bytesLength( 55 | comptime service: @Type(.enum_literal), 56 | container: []const u8, 57 | field: []const u8, 58 | min: ?usize, 59 | max: ?usize, 60 | size: usize, 61 | ) !void { 62 | const log = std.log.scoped(service); 63 | 64 | if (min) |d| if (size < d) { 65 | @branchHint(.unlikely); 66 | log.err(base_format ++ "size is less than {d} bytes", .{ container, field, d }); 67 | return Error.InvalidOperationInput; 68 | }; 69 | 70 | if (max) |d| if (size > d) { 71 | @branchHint(.unlikely); 72 | log.err(base_format ++ "size is more than {d} bytes", .{ container, field, d }); 73 | return Error.InvalidOperationInput; 74 | }; 75 | } 76 | 77 | pub fn stringLength( 78 | comptime service: @Type(.enum_literal), 79 | container: []const u8, 80 | field: []const u8, 81 | min: ?usize, 82 | max: ?usize, 83 | s: []const u8, 84 | ) !void { 85 | const log = std.log.scoped(service); 86 | const len = try std.unicode.utf8CountCodepoints(s); 87 | 88 | if (min) |d| if (len < d) { 89 | @branchHint(.unlikely); 90 | log.err(base_format ++ "length is less than {d} characters", .{ container, field, d }); 91 | return Error.InvalidOperationInput; 92 | }; 93 | 94 | if (max) |d| if (len > d) { 95 | @branchHint(.unlikely); 96 | log.err(base_format ++ "length is more than {d} characters", .{ container, field, d }); 97 | return Error.InvalidOperationInput; 98 | }; 99 | } 100 | 101 | pub fn stringPattern( 102 | comptime service: @Type(.enum_literal), 103 | container: []const u8, 104 | field: []const u8, 105 | comptime pattern: []const u8, 106 | s: []const u8, 107 | ) !void { 108 | const log = std.log.scoped(service); 109 | const regex = comptime mvzr.compile(pattern) orelse unreachable; 110 | if (!regex.isMatch(s)) { 111 | @branchHint(.unlikely); 112 | log.err(base_format ++ "does not match pattern \"{s}\"", .{ container, field, pattern }); 113 | return Error.InvalidOperationInput; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /smithy/runtime/primitives/Timestamp.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const stime = std.time; 3 | const testing = std.testing; 4 | 5 | const Self = @This(); 6 | 7 | /// A calendar timestamp, in milliseconds, relative to UTC 1970-01-01. 8 | /// Precision of timing depends on the hardware and operating system. 9 | /// The return value is signed because it is possible to have a date that is before the epoch. 10 | epoch_ms: i64, 11 | 12 | pub fn asMilliSigned(self: Self) i64 { 13 | return self.epoch_ms; 14 | } 15 | 16 | /// Assumes the timestamp is after UTC 1970-01-01. 17 | pub fn asMilliUnsigned(self: Self) u64 { 18 | return @intCast(self.epoch_ms); 19 | } 20 | 21 | pub fn asSecSigned(self: Self) i64 { 22 | return @divTrunc(self.epoch_ms, stime.ms_per_s); 23 | } 24 | 25 | /// Assumes the timestamp is after UTC 1970-01-01. 26 | pub fn asSecUnsigned(self: Self) u64 { 27 | return @intCast(@divTrunc(self.epoch_ms, stime.ms_per_s)); 28 | } 29 | 30 | pub fn asSecFloat(self: Self) f64 { 31 | return @as(f64, @floatFromInt(self.epoch_ms)) / stime.ms_per_s; 32 | } 33 | 34 | test { 35 | const ts = Self{ .epoch_ms = 1515531081123 }; 36 | try testing.expectEqual(1515531081123, ts.asMilliSigned()); 37 | try testing.expectEqual(1515531081123, ts.asMilliUnsigned()); 38 | try testing.expectEqual(1515531081, ts.asSecSigned()); 39 | try testing.expectEqual(1515531081, ts.asSecUnsigned()); 40 | try testing.expectEqual(1515531081.123, ts.asSecFloat()); 41 | } 42 | -------------------------------------------------------------------------------- /smithy/runtime/primitives/collection.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | const testing = std.testing; 4 | const test_alloc = testing.allocator; 5 | 6 | pub fn Set(comptime T: type) type { 7 | const HashMap = if (T == []const u8) std.StringHashMapUnmanaged(void) else std.AutoHashMapUnmanaged(T, void); 8 | 9 | return struct { 10 | const Self = @This(); 11 | pub const Item = T; 12 | pub const Indexer = HashMap.Size; 13 | pub const Iterator = HashMap.KeyIterator; 14 | 15 | internal: HashMap = .{}, 16 | 17 | pub fn count(self: Self) Indexer { 18 | return self.internal.count(); 19 | } 20 | 21 | pub fn contains(self: Self, item: T) bool { 22 | return self.internal.contains(item); 23 | } 24 | 25 | pub fn iterator(self: Self) Iterator { 26 | return self.internal.keyIterator(); 27 | } 28 | }; 29 | } 30 | 31 | test Set { 32 | var set = Set([]const u8){}; 33 | errdefer set.internal.deinit(test_alloc); 34 | 35 | try testing.expectEqual(0, set.count()); 36 | 37 | try testing.expectEqual(false, set.contains("foo")); 38 | try set.internal.putNoClobber(test_alloc, "foo", {}); 39 | try testing.expectEqual(true, set.contains("foo")); 40 | try testing.expectEqual(1, set.count()); 41 | 42 | try testing.expectEqual(false, set.contains("bar")); 43 | try set.internal.putNoClobber(test_alloc, "bar", {}); 44 | try testing.expectEqual(true, set.contains("bar")); 45 | try testing.expectEqual(2, set.count()); 46 | 47 | set.internal.deinit(test_alloc); 48 | } 49 | 50 | pub fn Map(comptime K: type, comptime V: type) type { 51 | const HashMap = switch (K) { 52 | []const u8 => std.StringHashMapUnmanaged(V), 53 | else => std.AutoHashMapUnmanaged(K, V), 54 | }; 55 | 56 | return struct { 57 | const Self = @This(); 58 | pub const Key = K; 59 | pub const Value = V; 60 | pub const Entry = HashMap.Entry; 61 | pub const Indexer = HashMap.Size; 62 | pub const Iterator = HashMap.Iterator; 63 | 64 | internal: HashMap = .{}, 65 | 66 | pub fn count(self: Self) Indexer { 67 | return self.internal.count(); 68 | } 69 | 70 | pub fn contains(self: Self, key: Key) bool { 71 | return self.internal.contains(key); 72 | } 73 | 74 | pub fn get(self: Self, key: Key) ?Value { 75 | return self.internal.get(key); 76 | } 77 | 78 | pub fn iterator(self: *const Self) Iterator { 79 | return self.internal.iterator(); 80 | } 81 | 82 | pub fn keyIterator(self: Self) Iterator { 83 | return self.internal.keyIterator(); 84 | } 85 | 86 | pub fn valueIterator(self: Self) Iterator { 87 | return self.internal.valueIterator(); 88 | } 89 | }; 90 | } 91 | 92 | test Map { 93 | var map = Map(u32, []const u8){}; 94 | errdefer map.internal.deinit(test_alloc); 95 | 96 | try testing.expectEqual(0, map.count()); 97 | 98 | try testing.expectEqual(false, map.contains(108)); 99 | try map.internal.putNoClobber(test_alloc, 108, "foo"); 100 | try testing.expectEqual(true, map.contains(108)); 101 | try testing.expectEqual(1, map.count()); 102 | 103 | try testing.expectEqual(false, map.contains(109)); 104 | try map.internal.putNoClobber(test_alloc, 109, "bar"); 105 | try testing.expectEqual(true, map.contains(109)); 106 | try testing.expectEqual(2, map.count()); 107 | 108 | try testing.expectEqual(null, map.get(107)); 109 | try testing.expectEqualStrings("foo", map.get(108).?); 110 | try testing.expectEqualStrings("bar", map.get(109).?); 111 | 112 | map.internal.deinit(test_alloc); 113 | } 114 | -------------------------------------------------------------------------------- /smithy/runtime/primitives/result.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const Allocator = std.mem.Allocator; 3 | 4 | pub const ErrorSource = enum { 5 | client, 6 | server, 7 | }; 8 | 9 | pub fn Result(comptime T: type, comptime E: type) type { 10 | return union(enum) { 11 | ok: T, 12 | fail: ResultError(E), 13 | 14 | pub fn deinit(self: @This()) void { 15 | switch (self) { 16 | .ok => |t| if (@typeInfo(T) == .@"struct" and @hasDecl(T, "deinit")) t.deinit(), 17 | .fail => |t| t.deinit(), 18 | } 19 | } 20 | }; 21 | } 22 | 23 | pub fn ResultError(comptime E: type) type { 24 | switch (@typeInfo(E)) { 25 | .@"enum", .@"union" => {}, 26 | else => @compileError("Error type must be an `enum` or `union`"), 27 | } 28 | if (!@hasDecl(E, "httpStatus")) @compileError("Error type missing `httpStatus` method"); 29 | if (!@hasDecl(E, "source")) @compileError("Error type missing `source` method"); 30 | if (!@hasDecl(E, "retryable")) @compileError("Error type missing `retryable` method"); 31 | 32 | return struct { 33 | const Self = @This(); 34 | 35 | kind: E, 36 | message: ?[]const u8 = null, 37 | arena: ?std.heap.ArenaAllocator = null, 38 | 39 | pub fn deinit(self: Self) void { 40 | if (self.arena) |arena| arena.deinit(); 41 | } 42 | 43 | pub fn httpStatus(self: Self) std.http.Status { 44 | return self.kind.httpStatus(); 45 | } 46 | 47 | pub fn source(self: Self) ErrorSource { 48 | return self.kind.source(); 49 | } 50 | 51 | pub fn retryable(self: Self) bool { 52 | return self.kind.retryable(); 53 | } 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /smithy/runtime/root.zig: -------------------------------------------------------------------------------- 1 | const result = @import("primitives/result.zig"); 2 | pub const Result = result.Result; 3 | pub const ResultError = result.ResultError; 4 | pub const ErrorSource = result.ErrorSource; 5 | 6 | const collection = @import("primitives/collection.zig"); 7 | pub const Set = collection.Set; 8 | pub const Map = collection.Map; 9 | 10 | pub const Timestamp = @import("primitives/Timestamp.zig"); 11 | 12 | pub const rules = @import("operation/rules.zig"); 13 | pub const RulesUrl = rules.RulesUrl; 14 | 15 | const request = @import("operation/request.zig"); 16 | pub const AuthId = request.AuthId; 17 | pub const Endpoint = request.Endpoint; 18 | pub const AuthScheme = request.AuthScheme; 19 | pub const HttpHeader = request.HttpHeader; 20 | 21 | const document = @import("operation/document.zig"); 22 | pub const Document = document.Document; 23 | 24 | pub const serial = @import("operation/serial.zig"); 25 | pub const SerialType = serial.SerialType; 26 | pub const MetaLabel = serial.MetaLabel; 27 | pub const MetaParam = serial.MetaParam; 28 | pub const MetaPayload = serial.MetaPayload; 29 | pub const MetaTransport = serial.MetaTransport; 30 | 31 | pub const validate = @import("operation/validate.zig"); 32 | 33 | test { 34 | _ = result; 35 | _ = collection; 36 | _ = Timestamp; 37 | _ = document; 38 | _ = request; 39 | _ = rules; 40 | _ = serial; 41 | _ = validate; 42 | } 43 | --------------------------------------------------------------------------------