diff --git a/.changeset/wise-nails-design.md b/.changeset/wise-nails-design.md new file mode 100644 index 00000000000..cede0d18dc0 --- /dev/null +++ b/.changeset/wise-nails-design.md @@ -0,0 +1,5 @@ +--- +"@smithy/core": patch +--- + +fix detection of member idempotencyToken trait diff --git a/packages/core/src/submodules/cbor/CborCodec.spec.ts b/packages/core/src/submodules/cbor/CborCodec.spec.ts index 91bccbac78d..d16134af89d 100644 --- a/packages/core/src/submodules/cbor/CborCodec.spec.ts +++ b/packages/core/src/submodules/cbor/CborCodec.spec.ts @@ -30,16 +30,18 @@ describe(CborShapeSerializer.name, () => { "ns", "StructWithIdempotencyToken", 0, - ["idempotencyToken", "plainString"], - [idempotencyTokenSchema, plainSchema] + ["idempotencyToken", "plainString", "memberTraitToken"], + [idempotencyTokenSchema, plainSchema, [() => plainSchema, 0b0100]] ); serializer.write(objectSchema, { idempotencyToken: undefined, plainString: undefined, + memberTraitToken: undefined, }); expect(cbor.deserialize(serializer.flush())).toMatchObject({ idempotencyToken: UUID_V4, + memberTraitToken: UUID_V4, }); serializer.write(objectSchema, { @@ -49,6 +51,18 @@ describe(CborShapeSerializer.name, () => { expect(cbor.deserialize(serializer.flush())).toMatchObject({ idempotencyToken: UUID_V4, plainString: /^abc$/, + memberTraitToken: UUID_V4, + }); + + serializer.write(objectSchema, { + idempotencyToken: "jrt", + plainString: "abc", + memberTraitToken: "qrf", + }); + expect(cbor.deserialize(serializer.flush())).toMatchObject({ + idempotencyToken: "jrt", + plainString: /^abc$/, + memberTraitToken: "qrf", }); } } diff --git a/packages/core/src/submodules/schema/schemas/NormalizedSchema.spec.ts b/packages/core/src/submodules/schema/schemas/NormalizedSchema.spec.ts index 1c057a72b09..36a868faaac 100644 --- a/packages/core/src/submodules/schema/schemas/NormalizedSchema.spec.ts +++ b/packages/core/src/submodules/schema/schemas/NormalizedSchema.spec.ts @@ -257,6 +257,18 @@ describe(NormalizedSchema.name, () => { expect(schema.getMergedTraits().idempotencyToken).toBe(undefined); } }); + + it("can understand members with the idempotencyToken trait", () => { + for (const schema of plainSchemas) { + expect(schema.isIdempotencyToken()).toBe(false); + expect(schema.getMergedTraits().idempotencyToken).toBe(undefined); + + const structure = struct("", "StructureWithIdempotencyTokenMember", 0, ["token"], [[() => schema, 0b0100]]); + const ns = NormalizedSchema.of(structure).getMemberSchema("token"); + + expect(ns.isIdempotencyToken()).toBe(true); + } + }); }); describe("event stream detection", () => { diff --git a/packages/core/src/submodules/schema/schemas/NormalizedSchema.ts b/packages/core/src/submodules/schema/schemas/NormalizedSchema.ts index af7e8f86a0b..55887721d47 100644 --- a/packages/core/src/submodules/schema/schemas/NormalizedSchema.ts +++ b/packages/core/src/submodules/schema/schemas/NormalizedSchema.ts @@ -64,8 +64,9 @@ export class NormalizedSchema implements INormalizedSchema { } if (schema instanceof NormalizedSchema) { + const computedMemberTraits = this.memberTraits; Object.assign(this, schema); - this.memberTraits = Object.assign({}, schema.getMemberTraits(), this.getMemberTraits()); + this.memberTraits = Object.assign({}, computedMemberTraits, schema.getMemberTraits(), this.getMemberTraits()); this.normalizedTraits = void 0; this.memberName = memberName ?? schema.memberName; return; @@ -255,10 +256,19 @@ export class NormalizedSchema implements INormalizedSchema { * @returns whether the schema has the idempotencyToken trait. */ public isIdempotencyToken(): boolean { - if (typeof this.traits === "number") { - return (this.traits & 0b0100) === 0b0100; - } else if (typeof this.traits === "object") { - return !!this.traits.idempotencyToken; + if (this.normalizedTraits) { + return !!this.normalizedTraits.idempotencyToken; + } + for (const traits of [this.traits, this.memberTraits]) { + if (typeof traits === "number") { + if ((traits & 0b0100) === 0b0100) { + return true; + } + } else if (typeof traits === "object") { + if (!!traits.idempotencyToken) { + return true; + } + } } return false; } diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/schema/SchemaGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/schema/SchemaGenerator.java index c98e24dc2ec..861daa85b67 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/schema/SchemaGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/schema/SchemaGenerator.java @@ -103,12 +103,16 @@ public void run() { return; } for (OperationShape operation : TopDownIndex.of(model).getContainedOperations(service)) { - operation.getInput().ifPresent(inputShape -> { - loadShapes(model.expectShape(inputShape)); - }); - operation.getOutput().ifPresent(outputShape -> { - loadShapes(model.expectShape(outputShape)); - }); + if (operation.getInput().isPresent()) { + loadShapes(model.expectShape(operation.getInput().get())); + } else { + loadShapes(model.expectShape(ShapeId.from("smithy.api#Unit"))); + } + if (operation.getOutput().isPresent()) { + loadShapes(model.expectShape(operation.getOutput().get())); + } else { + loadShapes(model.expectShape(ShapeId.from("smithy.api#Unit"))); + } operation.getErrors().forEach(error -> { loadShapes(model.expectShape(error)); });