diff --git a/codegen/sdk-codegen/build.gradle.kts b/codegen/sdk-codegen/build.gradle.kts index abf28d79d2f10..7178688d35ef9 100644 --- a/codegen/sdk-codegen/build.gradle.kts +++ b/codegen/sdk-codegen/build.gradle.kts @@ -109,6 +109,9 @@ tasks.register("generate-smithy-build") { val useLegacyAuthServices = setOf( // e.g. "S3" - use this as exclusion list if needed. ) + val useSchemaSerde = setOf( + // "CloudWatch Logs" + ) val projectionContents = Node.objectNodeBuilder() .withMember("imports", Node.fromStrings("${models.getAbsolutePath()}${File.separator}${file.name}")) .withMember("plugins", Node.objectNode() @@ -121,6 +124,8 @@ tasks.register("generate-smithy-build") { + clientName + " Client for Node.js, Browser and React Native") .withMember("useLegacyAuth", useLegacyAuthServices.contains(serviceTrait.sdkId)) + .withMember("generateSchemas", + useSchemaSerde.contains(serviceTrait.sdkId)) .build())) .build() projectionsBuilder.withMember(sdkId + "." + version.toLowerCase(), projectionContents) diff --git a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java index c4c88923b0026..9ba8ccafb3f29 100644 --- a/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java +++ b/codegen/smithy-aws-typescript-codegen/src/main/java/software/amazon/smithy/aws/typescript/codegen/AddProtocolConfig.java @@ -37,6 +37,7 @@ public AddProtocolConfig() { SchemaGenerationAllowlist.allow("com.amazonaws.s3#AmazonS3"); SchemaGenerationAllowlist.allow("com.amazonaws.dynamodb#DynamoDB_20120810"); SchemaGenerationAllowlist.allow("com.amazonaws.lambda#AWSGirApiService"); + SchemaGenerationAllowlist.allow("com.amazonaws.cloudwatchlogs#Logs_20140328"); // protocol tests SchemaGenerationAllowlist.allow("aws.protocoltests.json10#JsonRpc10"); @@ -69,6 +70,7 @@ public Map> getRuntimeConfigWriters( return Collections.emptyMap(); } String namespace = settings.getService().getNamespace(); + String rpcTarget = settings.getService().getName(); String xmlns = settings.getService(model) .getTrait(XmlNamespaceTrait.class) .map(XmlNamespaceTrait::getUri) @@ -144,7 +146,11 @@ public Map> getRuntimeConfigWriters( writer.addImportSubmodule( "AwsJson1_0Protocol", null, AwsDependency.AWS_SDK_CORE, "/protocols"); - writer.write("new AwsJson1_0Protocol({ defaultNamespace: $S })", namespace); + writer.write( + "new AwsJson1_0Protocol({ defaultNamespace: $S, serviceTarget: $S })", + namespace, + rpcTarget + ); } ); } else if (Objects.equals(settings.getProtocol(), AwsJson1_1Trait.ID)) { @@ -153,7 +159,11 @@ public Map> getRuntimeConfigWriters( writer.addImportSubmodule( "AwsJson1_1Protocol", null, AwsDependency.AWS_SDK_CORE, "/protocols"); - writer.write("new AwsJson1_1Protocol({ defaultNamespace: $S })", namespace); + writer.write( + "new AwsJson1_1Protocol({ defaultNamespace: $S, serviceTarget: $S })", + namespace, + rpcTarget + ); } ); } diff --git a/packages/core/src/submodules/protocols/json/AwsJson1_0Protocol.spec.ts b/packages/core/src/submodules/protocols/json/AwsJson1_0Protocol.spec.ts index c0941edaf9e47..2fbf124344889 100644 --- a/packages/core/src/submodules/protocols/json/AwsJson1_0Protocol.spec.ts +++ b/packages/core/src/submodules/protocols/json/AwsJson1_0Protocol.spec.ts @@ -30,6 +30,7 @@ describe(AwsJson1_0Protocol.name, () => { it("serializes blobs and timestamps", () => { const protocol = new AwsJson1_0Protocol({ defaultNamespace: "namespace", + serviceTarget: "JsonRpc10", }); protocol.setSerdeContext(serdeContext); const codec = protocol.getPayloadCodec(); @@ -55,6 +56,7 @@ describe(AwsJson1_0Protocol.name, () => { it("deserializes blobs and timestamps", async () => { const protocol = new AwsJson1_0Protocol({ defaultNamespace: "namespace", + serviceTarget: "JsonRpc10", }); protocol.setSerdeContext(serdeContext); const codec = protocol.getPayloadCodec(); @@ -73,6 +75,7 @@ describe(AwsJson1_0Protocol.name, () => { it("ignores JSON name and HTTP bindings", async () => { const protocol = new AwsJson1_0Protocol({ defaultNamespace: "namespace", + serviceTarget: "JsonRpc10", }); protocol.setSerdeContext(serdeContext); diff --git a/packages/core/src/submodules/protocols/json/AwsJson1_0Protocol.ts b/packages/core/src/submodules/protocols/json/AwsJson1_0Protocol.ts index caaf7bc30bfe1..8945f534443ae 100644 --- a/packages/core/src/submodules/protocols/json/AwsJson1_0Protocol.ts +++ b/packages/core/src/submodules/protocols/json/AwsJson1_0Protocol.ts @@ -5,9 +5,10 @@ import { AwsJsonRpcProtocol } from "./AwsJsonRpcProtocol"; * @see https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#differences-between-awsjson1-0-and-awsjson1-1 */ export class AwsJson1_0Protocol extends AwsJsonRpcProtocol { - public constructor({ defaultNamespace }: { defaultNamespace: string }) { + public constructor({ defaultNamespace, serviceTarget }: { defaultNamespace: string; serviceTarget: string }) { super({ defaultNamespace, + serviceTarget, }); } @@ -18,4 +19,11 @@ export class AwsJson1_0Protocol extends AwsJsonRpcProtocol { protected getJsonRpcVersion() { return "1.0" as const; } + + /** + * @override + */ + protected getDefaultContentType(): string { + return "application/x-amz-json-1.0"; + } } diff --git a/packages/core/src/submodules/protocols/json/AwsJson1_1Protocol.spec.ts b/packages/core/src/submodules/protocols/json/AwsJson1_1Protocol.spec.ts index 40c9b37bc7bd1..1d4c5ba602043 100644 --- a/packages/core/src/submodules/protocols/json/AwsJson1_1Protocol.spec.ts +++ b/packages/core/src/submodules/protocols/json/AwsJson1_1Protocol.spec.ts @@ -2,22 +2,24 @@ import { HttpResponse } from "@smithy/protocol-http"; import { describe, expect, test as it } from "vitest"; import { context, deleteObjects } from "../test-schema.spec"; -import { AwsJson1_0Protocol } from "./AwsJson1_0Protocol"; +import { AwsJson1_1Protocol } from "./AwsJson1_1Protocol"; /** * These tests are cursory since most coverage is provided by protocol tests. */ -describe(AwsJson1_0Protocol, () => { +describe(AwsJson1_1Protocol, () => { it("is 1.0", async () => { - const protocol = new AwsJson1_0Protocol({ + const protocol = new AwsJson1_1Protocol({ defaultNamespace: "", + serviceTarget: "JsonRpc11", }); - expect(protocol.getShapeId()).toEqual("aws.protocols#awsJson1_0"); + expect(protocol.getShapeId()).toEqual("aws.protocols#awsJson1_1"); }); it("serializes a request", async () => { - const protocol = new AwsJson1_0Protocol({ + const protocol = new AwsJson1_1Protocol({ defaultNamespace: "", + serviceTarget: "JsonRpc11", }); const httpRequest = await protocol.serializeRequest( deleteObjects, @@ -59,8 +61,9 @@ describe(AwsJson1_0Protocol, () => { headers: {}, }); - const protocol = new AwsJson1_0Protocol({ + const protocol = new AwsJson1_1Protocol({ defaultNamespace: "", + serviceTarget: "JsonRpc11", }); const output = await protocol.deserializeResponse(deleteObjects, context, httpResponse); diff --git a/packages/core/src/submodules/protocols/json/AwsJson1_1Protocol.ts b/packages/core/src/submodules/protocols/json/AwsJson1_1Protocol.ts index 8f291c6962690..4b4745ee2046f 100644 --- a/packages/core/src/submodules/protocols/json/AwsJson1_1Protocol.ts +++ b/packages/core/src/submodules/protocols/json/AwsJson1_1Protocol.ts @@ -5,9 +5,10 @@ import { AwsJsonRpcProtocol } from "./AwsJsonRpcProtocol"; * @see https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#differences-between-awsjson1-0-and-awsjson1-1 */ export class AwsJson1_1Protocol extends AwsJsonRpcProtocol { - public constructor({ defaultNamespace }: { defaultNamespace: string }) { + public constructor({ defaultNamespace, serviceTarget }: { defaultNamespace: string; serviceTarget: string }) { super({ defaultNamespace, + serviceTarget, }); } @@ -18,4 +19,11 @@ export class AwsJson1_1Protocol extends AwsJsonRpcProtocol { protected getJsonRpcVersion() { return "1.1" as const; } + + /** + * @override + */ + protected getDefaultContentType(): string { + return "application/x-amz-json-1.1"; + } } diff --git a/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.spec.ts b/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.spec.ts index 31db5f743f720..d477d5eb71e04 100644 --- a/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.spec.ts +++ b/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.spec.ts @@ -7,7 +7,7 @@ describe(AwsJsonRpcProtocol.name, () => { it("has expected codec settings", async () => { const protocol = new (class extends AwsJsonRpcProtocol { constructor() { - super({ defaultNamespace: "" }); + super({ defaultNamespace: "", serviceTarget: "" }); } getShapeId(): string { diff --git a/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts b/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts index 9da3b0f121701..ff561878cbf1a 100644 --- a/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts +++ b/packages/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts @@ -22,12 +22,14 @@ import { loadRestJsonErrorCode } from "./parseJsonBody"; export abstract class AwsJsonRpcProtocol extends RpcProtocol { protected serializer: ShapeSerializer; protected deserializer: ShapeDeserializer; + protected serviceTarget: string; private codec: JsonCodec; - protected constructor({ defaultNamespace }: { defaultNamespace: string }) { + protected constructor({ defaultNamespace, serviceTarget }: { defaultNamespace: string; serviceTarget: string }) { super({ defaultNamespace, }); + this.serviceTarget = serviceTarget; this.codec = new JsonCodec({ timestampFormat: { useTrait: true, @@ -50,9 +52,7 @@ export abstract class AwsJsonRpcProtocol extends RpcProtocol { } Object.assign(request.headers, { "content-type": `application/x-amz-json-${this.getJsonRpcVersion()}`, - "x-amz-target": - (this.getJsonRpcVersion() === "1.0" ? `JsonRpc10.` : `JsonProtocol.`) + - NormalizedSchema.of(operationSchema).getName(), + "x-amz-target": `${this.serviceTarget}.${NormalizedSchema.of(operationSchema).getName()}`, }); if (deref(operationSchema.input) === "unit" || !request.body) { request.body = "{}"; diff --git a/packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts b/packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts index 0d9ba1fe9cf63..0b89f40148f41 100644 --- a/packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts +++ b/packages/core/src/submodules/protocols/json/AwsRestJsonProtocol.ts @@ -81,7 +81,7 @@ export class AwsRestJsonProtocol extends HttpBindingProtocol { } else if (httpPayloadMember.isBlobSchema()) { request.headers["content-type"] = "application/octet-stream"; } else { - request.headers["content-type"] = "application/json"; + request.headers["content-type"] = this.getDefaultContentType(); } } else if (!inputSchema.isUnitSchema()) { const hasBody = Object.values(members).find((m) => { @@ -89,7 +89,7 @@ export class AwsRestJsonProtocol extends HttpBindingProtocol { return !httpQuery && !httpQueryParams && !httpHeader && !httpLabel && httpPrefixHeaders === void 0; }); if (hasBody) { - request.headers["content-type"] = "application/json"; + request.headers["content-type"] = this.getDefaultContentType(); } } } @@ -157,4 +157,11 @@ export class AwsRestJsonProtocol extends HttpBindingProtocol { throw exception; } + + /** + * @override + */ + protected getDefaultContentType(): string { + return "application/json"; + } } diff --git a/packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts b/packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts index ec47aa7f166f3..9c66bfb0d4a52 100644 --- a/packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts +++ b/packages/core/src/submodules/protocols/query/AwsQueryProtocol.ts @@ -212,4 +212,11 @@ export class AwsQueryProtocol extends RpcProtocol { const errorData = this.loadQueryError(data); return errorData?.message ?? errorData?.Message ?? data.message ?? data.Message ?? "Unknown"; } + + /** + * @override + */ + protected getDefaultContentType(): string { + return "application/x-www-form-urlencoded"; + } } diff --git a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts index 8f3465da98571..a0a57aa4ec771 100644 --- a/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts +++ b/packages/core/src/submodules/protocols/xml/AwsRestXmlProtocol.ts @@ -85,7 +85,7 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol { } else if (httpPayloadMember.isBlobSchema()) { request.headers["content-type"] = "application/octet-stream"; } else { - request.headers["content-type"] = "application/xml"; + request.headers["content-type"] = this.getDefaultContentType(); } } else if (!ns.isUnitSchema()) { const hasBody = Object.values(members).find((m) => { @@ -93,12 +93,12 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol { return !httpQuery && !httpQueryParams && !httpHeader && !httpLabel && httpPrefixHeaders === void 0; }); if (hasBody) { - request.headers["content-type"] = "application/xml"; + request.headers["content-type"] = this.getDefaultContentType(); } } } - if (request.headers["content-type"] === "application/xml") { + if (request.headers["content-type"] === this.getDefaultContentType()) { if (typeof request.body === "string") { request.body = '' + request.body; } @@ -172,4 +172,11 @@ export class AwsRestXmlProtocol extends HttpBindingProtocol { throw exception; } + + /** + * @override + */ + protected getDefaultContentType(): string { + return "application/xml"; + } } diff --git a/private/aws-protocoltests-json-10-schema/src/runtimeConfig.shared.ts b/private/aws-protocoltests-json-10-schema/src/runtimeConfig.shared.ts index 70fff70b46ac7..b1369d8a2e49e 100644 --- a/private/aws-protocoltests-json-10-schema/src/runtimeConfig.shared.ts +++ b/private/aws-protocoltests-json-10-schema/src/runtimeConfig.shared.ts @@ -31,7 +31,9 @@ export const getRuntimeConfig = (config: JSONRPC10ClientConfig) => { }, ], logger: config?.logger ?? new NoOpLogger(), - protocol: config?.protocol ?? new AwsJson1_0Protocol({ defaultNamespace: "aws.protocoltests.json10" }), + protocol: + config?.protocol ?? + new AwsJson1_0Protocol({ defaultNamespace: "aws.protocoltests.json10", serviceTarget: "JsonRpc10" }), serviceId: config?.serviceId ?? "JSON RPC 10", urlParser: config?.urlParser ?? parseUrl, utf8Decoder: config?.utf8Decoder ?? fromUtf8, diff --git a/private/aws-protocoltests-json-schema/src/runtimeConfig.shared.ts b/private/aws-protocoltests-json-schema/src/runtimeConfig.shared.ts index 02dfd0e73f90a..c5c69b5bdf817 100644 --- a/private/aws-protocoltests-json-schema/src/runtimeConfig.shared.ts +++ b/private/aws-protocoltests-json-schema/src/runtimeConfig.shared.ts @@ -31,7 +31,9 @@ export const getRuntimeConfig = (config: JsonProtocolClientConfig) => { }, ], logger: config?.logger ?? new NoOpLogger(), - protocol: config?.protocol ?? new AwsJson1_1Protocol({ defaultNamespace: "aws.protocoltests.json" }), + protocol: + config?.protocol ?? + new AwsJson1_1Protocol({ defaultNamespace: "aws.protocoltests.json", serviceTarget: "JsonProtocol" }), serviceId: config?.serviceId ?? "Json Protocol", urlParser: config?.urlParser ?? parseUrl, utf8Decoder: config?.utf8Decoder ?? fromUtf8, diff --git a/private/aws-protocoltests-restjson-schema/src/schemas/schemas.ts b/private/aws-protocoltests-restjson-schema/src/schemas/schemas.ts index 59b70b8f679b0..a654ff45d27e1 100644 --- a/private/aws-protocoltests-restjson-schema/src/schemas/schemas.ts +++ b/private/aws-protocoltests-restjson-schema/src/schemas/schemas.ts @@ -2046,8 +2046,8 @@ export var TimestampFormatHeadersIO = struct( ); export var TopLevel = struct(n0, _TLo, 0, [_di, _dLi, _dMi], [() => Dialog, () => DialogList, () => DialogMap]); export var UnionInputOutput = struct(n0, _UIO, 0, [_con], [() => MyUnion]); -export var GreetingStruct = struct(n2, _GS, 0, [_sa], [0]); -export var GreetingStruct_n1 = struct(n1, _GS, 0, [_hi], [0]); +export var GreetingStruct_n2 = struct(n2, _GS, 0, [_sa], [0]); +export var GreetingStruct = struct(n1, _GS, 0, [_hi], [0]); export var Unit = "unit" as const; export var RestJsonProtocolServiceException = error( @@ -2110,7 +2110,7 @@ export var DenseNumberMap = 128 | 1; export var DenseSetMap = map(n0, _DSM, 0, 0, 64 | 0); export var DenseStringMap = 128 | 0; -export var DenseStructMap = map(n0, _DSMe, 0, 0, () => GreetingStruct_n1); +export var DenseStructMap = map(n0, _DSMe, 0, 0, () => GreetingStruct); export var DialogMap = map(n0, _DM, 0, 0, () => Dialog); export var DocumentValuedMap = 128 | 15; @@ -2150,7 +2150,7 @@ export var SparseStructMap = map( [_sp]: 1, }, 0, - () => GreetingStruct_n1 + () => GreetingStruct ); export var TestStringMap = 128 | 0; @@ -2175,7 +2175,7 @@ export var MyUnion = uni( _MU, 0, [_sV, _bVo, _nVu, _bVl, _tV, _eV, _lVi, _mV, _sVt, _rSV], - [0, 2, 1, 21, 4, 0, 64 | 0, 128 | 0, () => GreetingStruct_n1, () => GreetingStruct] + [0, 2, 1, 21, 4, 0, 64 | 0, 128 | 0, () => GreetingStruct, () => GreetingStruct_n2] ); export var PlayerAction = uni(n0, _PA, 0, [_qu], [() => Unit]); export var SimpleUnion = uni(n0, _SU, 0, [_int, _st], [1, 0]); @@ -2570,7 +2570,7 @@ export var MalformedAcceptWithBody = op( [_ht]: ["POST", "/MalformedAcceptWithBody", 200], }, () => Unit, - () => GreetingStruct_n1 + () => GreetingStruct ); export var MalformedAcceptWithGenericString = op( n0, @@ -2623,7 +2623,7 @@ export var MalformedContentTypeWithBody = op( { [_ht]: ["POST", "/MalformedContentTypeWithBody", 200], }, - () => GreetingStruct_n1, + () => GreetingStruct, () => Unit ); export var MalformedContentTypeWithGenericString = op( diff --git a/scripts/model-analysis/model-analysis.js b/scripts/model-analysis/model-analysis.js new file mode 100644 index 0000000000000..67c3e5b7c0a2b --- /dev/null +++ b/scripts/model-analysis/model-analysis.js @@ -0,0 +1,131 @@ +/** + * This script analyzes the collection of AWS service models. + */ + +const path = require("node:path"); +const fs = require("node:fs"); + +const root = path.join(__dirname, "..", ".."); + +const models = path.join(root, "codegen", "sdk-codegen", "aws-models"); + +for (const file of fs.readdirSync(models)) { + if (file.endsWith(".json")) { + const model = require(path.join(models, file)); + const shapes = model.shapes; + const service = Object.entries(shapes).find(([id, s]) => s.type === "service"); + const operations = Object.entries(shapes).filter(([id, s]) => { + return s.type === "operation"; + }); + const protocol = Object.entries(service[1].traits).find(([id, trait]) => id.startsWith("aws.protocol"))[0]; + + if (protocol.includes("rest")) { + const inputOutputShapes = Object.entries(shapes).filter(([id, s]) => { + return ( + (s.traits?.["smithy.api#input"] || s.traits?.["smithy.api#output"]) && s.type === "structure" && s.members + ); + }); + for (const [id, ioShape] of inputOutputShapes) { + const hasCompleteHttpBindings = + Object.values(ioShape.members ?? {}).filter((member) => { + return Object.keys(member?.traits ?? {}).filter((trait) => trait.includes("http")); + }).length === Object.keys(ioShape.members).length; + if (hasCompleteHttpBindings) { + // console.log(`✅ complete http bindings for operation ${id}`); + } else { + /** + * This flags an operation that has incomplete HTTP bindings. This is valid, but + * would currently be considered unusual for AWS SDK models. As of writing there + * are no such cases. + */ + console.log(`❌ incomplete http bindings for operation ${id}`); + } + } + + const eventStreamShapes = Object.entries(shapes).filter( + ([id, s]) => s.type === "union" && s.traits["smithy.api#streaming"] + ); + + const eventStreamInputMembers = Object.entries(shapes).filter(([id, s]) => { + return ( + s.traits?.["smithy.api#input"] && + s.type === "structure" && + Object.entries(s.members).find(([name, member]) => { + return eventStreamShapes.find(([_id, s]) => _id === member.target); + }) + ); + }); + const eventStreamOutputMembers = Object.entries(shapes).filter(([id, s]) => { + return ( + s.traits?.["smithy.api#output"] && + s.type === "structure" && + Object.entries(s.members).find(([name, member]) => { + return eventStreamShapes.find(([_id, s]) => _id === member.target); + }) + ); + }); + + const eventStreamInputOperations = operations.filter(([id, op]) => { + return eventStreamInputMembers.find(([_id, s]) => _id === op.input.target); + }); + const eventStreamOutputOperations = operations.filter(([id, op]) => { + return eventStreamOutputMembers.find(([_id, s]) => _id === op.output.target); + }); + + if (eventStreamShapes.length) { + console.log("=".repeat(99)); + + /** + * This reports the services that have event stream operations and lists them. + */ + console.log({ + service: service[0], + operations: operations.length, + protocol, + eventStreamInputOperations: eventStreamInputOperations.map(([id, op]) => id), + eventStreamOutputOperations: eventStreamOutputOperations.map(([id, op]) => id), + }); + + /** + * This reports the shapes that are input/outputs of event stream operations and whether + * they have complete HTTP bindings modeled. + */ + for (const group of [eventStreamInputMembers, eventStreamOutputMembers]) { + for (const [id, struct] of group) { + console.log(id, ":"); + const members = struct.members ?? {}; + + const hasCompleteHttpBindings = + Object.values(members).filter((member) => { + return Object.keys(member.traits).filter((trait) => trait.includes("http")); + }).length === Object.keys(members).length; + + if (hasCompleteHttpBindings) { + console.log("✅ complete http bindings"); + } else { + console.log("❌ incomplete http bindings"); + } + + const payloadBindingMember = Object.values(members).find((member) => { + return member.traits?.["smithy.api#httpPayload"]; + }); + const payloadBindingIsEventStream = eventStreamShapes.find(([id, s]) => id === payloadBindingMember.target); + + if (payloadBindingIsEventStream) { + console.log("✅ payload binding is event stream"); + } else { + console.log("❌ payload binding is not event stream. How would that even work?"); + } + for (const [memberName, member] of Object.entries(members)) { + console.log( + " ", + memberName, + Object.keys(member.traits).filter((trait) => trait.includes("http")) + ); + } + } + } + } + } + } +}