diff --git a/.changeset/hungry-mayflies-confess.md b/.changeset/hungry-mayflies-confess.md new file mode 100644 index 00000000000..a43ace2692f --- /dev/null +++ b/.changeset/hungry-mayflies-confess.md @@ -0,0 +1,6 @@ +--- +"@smithy/core": patch +"@smithy/types": patch +--- + +fix ordering of static simple schema type diff --git a/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts b/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts index 65bd1c6a0cd..3b7efc7f6fe 100644 --- a/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts +++ b/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts @@ -8,6 +8,7 @@ import type { ResponseMetadata, RetryableTrait, SchemaRef, + StaticErrorSchema, StringSchema, TimestampDefaultSchema, } from "@smithy/types"; @@ -306,6 +307,14 @@ describe(SmithyRpcV2CborProtocol.name, () => { }), }); + const errorResponseNoDiscriminator = new HttpResponse({ + statusCode: 404, + headers: {}, + body: cbor.serialize({ + modeledProperty: "oh no", + }), + }); + const serdeContext = {}; class ServiceBaseException extends Error { @@ -326,18 +335,31 @@ describe(SmithyRpcV2CborProtocol.name, () => { beforeEach(() => { ns.clear(); + synthetic.clear(); }); - const modeledExceptionSchema = error("ns", "ModeledException", 0, ["modeledProperty"], [0], null); - const baseServiceExceptionSchema = error("smithy.ts.sdk.synthetic.ns", "BaseServiceException", 0, [], [], null); + const modeledExceptionSchema = [ + -3, + "ns", + "ModeledException", + 0, + ["modeledProperty"], + [0], + ] satisfies StaticErrorSchema; + const baseServiceExceptionSchema = [ + -3, + "smithy.ts.sdk.synthetic.ns", + "BaseServiceException", + 0, + [], + [], + ] satisfies StaticErrorSchema; it("should throw the schema error ctor if one exists", async () => { // this is for modeled exceptions. - ns.register(modeledExceptionSchema.getName(), modeledExceptionSchema); ns.registerError(modeledExceptionSchema, ModeledExceptionCtor); - synthetic.register(baseServiceExceptionSchema.getName(), baseServiceExceptionSchema); synthetic.registerError(baseServiceExceptionSchema, ServiceBaseException); try { @@ -353,11 +375,10 @@ describe(SmithyRpcV2CborProtocol.name, () => { it("should throw a base error if available in the namespace, when no error schema is modeled", async () => { // this is the expected fallback case for all generated clients. - synthetic.register(baseServiceExceptionSchema.getName(), baseServiceExceptionSchema); synthetic.registerError(baseServiceExceptionSchema, ServiceBaseException); try { - await protocol.deserializeResponse(operation, serdeContext as any, errorResponse); + await protocol.deserializeResponse(operation, serdeContext as any, errorResponseNoDiscriminator); } catch (e) { expect(e).toBeInstanceOf(ServiceBaseException); } diff --git a/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.ts b/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.ts index 6d1119cf437..61d352fbd97 100644 --- a/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.ts +++ b/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.ts @@ -10,6 +10,7 @@ import type { OperationSchema, ResponseMetadata, SerdeFunctions, + StaticErrorSchema, } from "@smithy/types"; import { getSmithyContext } from "@smithy/util-middleware"; @@ -104,9 +105,9 @@ export class SmithyRpcV2CborProtocol extends RpcProtocol { const registry = TypeRegistry.for(namespace); - let errorSchema: ErrorSchema; + let errorSchema: StaticErrorSchema; try { - errorSchema = registry.getSchema(errorName) as ErrorSchema; + errorSchema = registry.getSchema(errorName) as StaticErrorSchema; } catch (e) { if (dataObject.Message) { dataObject.message = dataObject.Message; diff --git a/packages/core/src/submodules/schema/TypeRegistry.spec.ts b/packages/core/src/submodules/schema/TypeRegistry.spec.ts index ad3562a3417..486ce427d1d 100644 --- a/packages/core/src/submodules/schema/TypeRegistry.spec.ts +++ b/packages/core/src/submodules/schema/TypeRegistry.spec.ts @@ -1,6 +1,6 @@ +import type { StaticErrorSchema } from "@smithy/types"; import { describe, expect, test as it } from "vitest"; -import { error } from "./schemas/ErrorSchema"; import { list } from "./schemas/ListSchema"; import { map } from "./schemas/MapSchema"; import { struct } from "./schemas/StructureSchema"; @@ -16,15 +16,26 @@ describe(TypeRegistry.name, () => { it("stores and retrieves schema objects", () => { const tr = TypeRegistry.for("NAMESPACE"); + tr.register(List.getName(), List); expect(tr.getSchema("List")).toBe(List); + tr.register(Map.getName(), Map); expect(tr.getSchema("Map")).toBe(Map); + tr.register(Struct().getName(), Struct()); expect(tr.getSchema("Structure")).toBe(schema); }); it("has a helper method to retrieve a synthetic base exception", () => { // the service namespace is appended to the synthetic prefix. - const err = error("smithy.ts.sdk.synthetic.NAMESPACE", "UhOhServiceException", 0, [], [], Error); - const tr = TypeRegistry.for("smithy.ts.sdk.synthetic.NAMESPACE"); + const err = [ + -3, + "smithy.ts.sdk.synthetic.NAMESPACE", + "UhOhServiceException", + 0, + [], + [], + ] satisfies StaticErrorSchema; + const tr = TypeRegistry.for(err[1]); + tr.registerError(err, Error); expect(tr.getBaseException()).toBe(err); }); }); diff --git a/packages/core/src/submodules/schema/TypeRegistry.ts b/packages/core/src/submodules/schema/TypeRegistry.ts index 03c388f011b..2534a7ec28a 100644 --- a/packages/core/src/submodules/schema/TypeRegistry.ts +++ b/packages/core/src/submodules/schema/TypeRegistry.ts @@ -35,7 +35,8 @@ export class TypeRegistry { */ public register(shapeId: string, schema: ISchema) { const qualifiedName = this.normalizeShapeId(shapeId); - this.schemas.set(qualifiedName, schema); + const registry = TypeRegistry.for(qualifiedName.split("#")[0]); + registry.schemas.set(qualifiedName, schema); } /** @@ -54,7 +55,10 @@ export class TypeRegistry { * Associates an error schema with its constructor. */ public registerError(es: ErrorSchema | StaticErrorSchema, ctor: any) { - this.exceptions.set(es, ctor); + const $error = es as StaticErrorSchema; + const registry = TypeRegistry.for($error[1]); + registry.schemas.set($error[1] + "#" + $error[2], $error); + registry.exceptions.set($error, ctor); } /** @@ -62,7 +66,9 @@ export class TypeRegistry { * @returns Error constructor that extends the service's base exception. */ public getErrorCtor(es: ErrorSchema | StaticErrorSchema): any { - return this.exceptions.get(es); + const $error = es as StaticErrorSchema; + const registry = TypeRegistry.for($error[1]); + return registry.exceptions.get(es); } /** @@ -78,10 +84,14 @@ export class TypeRegistry { * * @returns the synthetic base exception of the service namespace associated with this registry instance. */ - public getBaseException(): ErrorSchema | undefined { - for (const [id, schema] of this.schemas.entries()) { - if (id.startsWith("smithy.ts.sdk.synthetic.") && id.endsWith("ServiceException")) { - return schema as ErrorSchema; + public getBaseException(): StaticErrorSchema | undefined { + for (const exceptionKey of this.exceptions.keys()) { + if (Array.isArray(exceptionKey)) { + const [, ns, name] = exceptionKey; + const id = ns + "#" + name; + if (id.startsWith("smithy.ts.sdk.synthetic.") && id.endsWith("ServiceException")) { + return exceptionKey; + } } } return undefined; @@ -109,8 +119,4 @@ export class TypeRegistry { } return this.namespace + "#" + shapeId; } - - private getNamespace(shapeId: string) { - return this.normalizeShapeId(shapeId).split("#")[0]; - } } diff --git a/packages/core/src/submodules/schema/schemas/NormalizedSchema.ts b/packages/core/src/submodules/schema/schemas/NormalizedSchema.ts index 25941d294ff..3da56b898f1 100644 --- a/packages/core/src/submodules/schema/schemas/NormalizedSchema.ts +++ b/packages/core/src/submodules/schema/schemas/NormalizedSchema.ts @@ -43,7 +43,7 @@ import type { OperationSchema } from "./OperationSchema"; import { op } from "./OperationSchema"; import { Schema } from "./Schema"; import type { SimpleSchema } from "./SimpleSchema"; -import { sim } from "./SimpleSchema"; +import { simAdapter } from "./SimpleSchema"; import { struct, StructureSchema } from "./StructureSchema"; import { translateTraits } from "./translateTraits"; @@ -450,7 +450,7 @@ export function hydrate( const [id, ...rest] = ss; return ( { - [0 satisfies StaticSchemaIdSimple]: sim, + [0 satisfies StaticSchemaIdSimple]: simAdapter, [1 satisfies StaticSchemaIdList]: list, [2 satisfies StaticSchemaIdMap]: map, [3 satisfies StaticSchemaIdStruct]: struct, diff --git a/packages/core/src/submodules/schema/schemas/Schema.ts b/packages/core/src/submodules/schema/schemas/Schema.ts index cf71348be10..e84c2860d04 100644 --- a/packages/core/src/submodules/schema/schemas/Schema.ts +++ b/packages/core/src/submodules/schema/schemas/Schema.ts @@ -15,7 +15,7 @@ export abstract class Schema implements TraitsSchema { public static assign(instance: T, values: Omit): T { const schema = Object.assign(instance, values); - TypeRegistry.for(schema.namespace).register(schema.name, schema); + // TypeRegistry.for(schema.namespace).register(schema.name, schema); return schema; } diff --git a/packages/core/src/submodules/schema/schemas/SimpleSchema.ts b/packages/core/src/submodules/schema/schemas/SimpleSchema.ts index 113a2c08434..f5fbb912eb9 100644 --- a/packages/core/src/submodules/schema/schemas/SimpleSchema.ts +++ b/packages/core/src/submodules/schema/schemas/SimpleSchema.ts @@ -29,3 +29,15 @@ export const sim = (namespace: string, name: string, schemaRef: SchemaRef, trait traits, schemaRef, }); + +/** + * @internal + * @deprecated + */ +export const simAdapter = (namespace: string, name: string, traits: SchemaTraits, schemaRef: SchemaRef) => + Schema.assign(new SimpleSchema(), { + name, + namespace, + traits, + schemaRef, + }); diff --git a/packages/core/src/submodules/schema/schemas/schemas.spec.ts b/packages/core/src/submodules/schema/schemas/schemas.spec.ts index 81422f9f34f..7b31b0a3098 100644 --- a/packages/core/src/submodules/schema/schemas/schemas.spec.ts +++ b/packages/core/src/submodules/schema/schemas/schemas.spec.ts @@ -23,9 +23,8 @@ describe("schemas", () => { expect(schema.ctor).toBe(null); }); - it("has a factory and the factory registers the schema", () => { + it("has a factory", () => { expect(error("ack", "Error", 0, [], [])).toEqual(schema); - expect(TypeRegistry.for("ack").getSchema(schema.name)).toEqual(schema); }); it("has an instanceOf operator", () => { @@ -44,9 +43,8 @@ describe("schemas", () => { it("has a value schema", () => { expect(schema.valueSchema).toBe(0 as SchemaRef); }); - it("has a factory and the factory registers the schema", () => { + it("has a factory", () => { expect(list("ack", "List", 0, 0)).toEqual(schema); - expect(TypeRegistry.for("ack").getSchema(schema.name)).toEqual(schema); }); it("has an instanceOf operator", () => { const object = { ...schema }; @@ -65,9 +63,8 @@ describe("schemas", () => { expect(schema.keySchema).toBe(0 as SchemaRef); expect(schema.valueSchema).toBe(1 as SchemaRef); }); - it("has a factory and the factory registers the schema", () => { + it("has a factory", () => { expect(map("ack", "Map", 0, 0, 1)).toEqual(schema); - expect(TypeRegistry.for("ack").getSchema(schema.name)).toEqual(schema); }); it("has an instanceOf operator", () => { const object = { ...schema }; @@ -86,9 +83,8 @@ describe("schemas", () => { expect(schema.input).toEqual("unit"); expect(schema.output).toEqual("unit"); }); - it("has a factory and the factory registers the schema", () => { + it("has a factory", () => { expect(op("ack", "Operation", 0, "unit", "unit")).toEqual(schema); - expect(TypeRegistry.for("ack").getSchema(schema.name)).toEqual(schema); }); }); @@ -121,9 +117,8 @@ describe("schemas", () => { expect(schema).toBeInstanceOf(Schema); expect(schema).toBeInstanceOf(SimpleSchema); }); - it("has a factory and the factory registers the schema", () => { + it("has a factory", () => { expect(sim("ack", "Simple", 0, 0)).toEqual(schema); - expect(TypeRegistry.for("ack").getSchema(schema.name)).toEqual(schema); }); it("has an instanceOf operator", () => { const object = { ...schema }; @@ -143,9 +138,8 @@ describe("schemas", () => { expect(schema.memberNames).toEqual(["a", "b", "c"]); expect(schema.memberList).toEqual([0, 1, 2]); }); - it("has a factory and the factory registers the schema", () => { + it("has a factory", () => { expect(struct("ack", "Structure", 0, ["a", "b", "c"], [0, 1, 2])).toEqual(schema); - expect(TypeRegistry.for("ack").getSchema(schema.name)).toEqual(schema); }); it("has an instanceOf operator", () => { const object = { ...schema }; diff --git a/packages/types/src/schema/static-schemas.ts b/packages/types/src/schema/static-schemas.ts index fea9fb232a3..43b07d4c8a4 100644 --- a/packages/types/src/schema/static-schemas.ts +++ b/packages/types/src/schema/static-schemas.ts @@ -59,7 +59,7 @@ export type ShapeNamespace = string; /** * @alpha */ -export type StaticSimpleSchema = [StaticSchemaIdSimple, ShapeNamespace, ShapeName, SchemaRef, SchemaTraits]; +export type StaticSimpleSchema = [StaticSchemaIdSimple, ShapeNamespace, ShapeName, SchemaTraits, SchemaRef]; /** * @alpha diff --git a/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_0.ts b/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_0.ts index d77bd655138..10b24258b38 100644 --- a/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_0.ts +++ b/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_0.ts @@ -137,7 +137,7 @@ import { InvalidGreeting as __InvalidGreeting, ValidationException as __ValidationException, } from "../models/index"; -import { TypeRegistry, error } from "@smithy/core/schema"; +import { TypeRegistry } from "@smithy/core/schema"; import { StaticErrorSchema, StaticListSchema, @@ -295,7 +295,7 @@ export var RpcV2CborDenseMapsInputOutput: StaticStructureSchema = [ _RVCDMIO, 0, [_dSM, _dNM, _dBM, _dSMe, _dSMen], - [() => DenseStructMap, 128 | 1, 128 | 2, 128 | 0, [2, n1, _DSM, 0, 64 | 0, 64 | 0] as StaticMapSchema], + [() => DenseStructMap, 128 | 1, 128 | 2, 128 | 0, [2, n1, _DSM, 0, 0, 64 | 0]], ]; export var RpcV2CborListInputOutput: StaticStructureSchema = [ 3, @@ -303,18 +303,7 @@ export var RpcV2CborListInputOutput: StaticStructureSchema = [ _RVCLIO, 0, [_sL, _sS, _iL, _bL, _tL, _eL, _iEL, _nSL, _sLt, _bLl], - [ - 64 | 0, - 64 | 0, - 64 | 1, - 64 | 2, - 64 | 4, - 64 | 0, - 64 | 1, - [1, n2, _NSL, 0, 64 | 0] as StaticListSchema, - () => StructureList, - 64 | 21, - ], + [64 | 0, 64 | 0, 64 | 1, 64 | 2, 64 | 4, 64 | 0, 64 | 1, [1, n2, _NSL, 0, 64 | 0], () => StructureList, 64 | 21], ]; export var RpcV2CborSparseMapsInputOutput: StaticStructureSchema = [ 3, @@ -352,7 +341,7 @@ export var SparseNullsOperationInputOutput: StaticStructureSchema = [ ]; export var StructureListMember: StaticStructureSchema = [3, n1, _SLM, 0, [_a, _b_], [0, 0]]; export var GreetingStruct: StaticStructureSchema = [3, n2, _GS, 0, [_h], [0]]; -export var RpcV2ProtocolServiceException = error(_sC, "RpcV2ProtocolServiceException", 0, [], [], null); +export var RpcV2ProtocolServiceException: StaticErrorSchema = [-3, _sC, "RpcV2ProtocolServiceException", 0, [], []]; TypeRegistry.for(_sC).registerError(RpcV2ProtocolServiceException, __RpcV2ProtocolServiceException); export var ValidationExceptionFieldList: StaticListSchema = [1, n0, _VEFL, 0, () => ValidationExceptionField]; 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 6329bb04c0e..5753889426a 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 @@ -276,15 +276,17 @@ private void writeSimpleSchema(Shape shape) { TypeScriptWriter writer = getWriter(shape.getId()); if (elision.traits.hasSchemaTraits(shape)) { writer.addImport("StaticSimpleSchema", null, TypeScriptDependency.SMITHY_TYPES); - writer.write(""" - export var $L: StaticSimpleSchema = [0, $L, $L, $L,""", + writer.openBlock(""" + export var $L: StaticSimpleSchema = [0, $L, $L,""", + ", $L];", getShapeVariableName(shape), checkImportString(shape, shape.getId().getNamespace(), "n"), checkImportString(shape, shape.getId().getName()), - resolveSimpleSchema(shape, shape) + resolveSimpleSchema(shape, shape), + () -> { + writeTraits(shape); + } ); - writeTraits(shape); - writer.write("];"); } } @@ -341,7 +343,7 @@ private void writeBaseError() { String namespace = settings.getService(model).getId().getNamespace(); String exceptionCtorSymbolName = "__" + serviceExceptionName; - writer.addImportSubmodule("error", null, TypeScriptDependency.SMITHY_CORE, "/schema"); + writer.addImport("StaticErrorSchema", null, TypeScriptDependency.SMITHY_TYPES); writer.addRelativeImport( serviceExceptionName, exceptionCtorSymbolName, @@ -350,7 +352,7 @@ private void writeBaseError() { String syntheticNamespace = store.var("smithy.ts.sdk.synthetic." + namespace); writer.write(""" - export var $L = error($L, $S, 0, [], [], null);""", + export var $L: StaticErrorSchema = [-3, $L, $S, 0, [], []];""", serviceExceptionName, syntheticNamespace, serviceExceptionName @@ -672,25 +674,19 @@ private String resolveSimpleSchemaNestedContainer(Shape context, Shape shape, Ty String staticTypePrefix; String sentinel; String keySchema = ""; - String valueSchema; - String as; switch (shape.getType()) { case LIST -> { contained = shape.asListShape().get().getMember(); - staticTypePrefix = "([1, "; - valueSchema = ""; + staticTypePrefix = "[1, "; sentinel = "64"; writer.addImport("StaticListSchema", null, TypeScriptDependency.SMITHY_TYPES); - as = " as StaticListSchema)"; } case MAP -> { contained = shape.asMapShape().get().getValue(); - staticTypePrefix = "([2, "; + staticTypePrefix = "[2, "; keySchema = this.resolveSimpleSchema(context, shape.asMapShape().get().getKey()) + ", "; - valueSchema = this.resolveSimpleSchema(context, shape.asMapShape().get().getValue()) + ", "; sentinel = "128"; writer.addImport("StaticMapSchema", null, TypeScriptDependency.SMITHY_TYPES); - as = " as StaticMapSchema)"; } default -> { throw new IllegalArgumentException( @@ -702,19 +698,12 @@ private String resolveSimpleSchemaNestedContainer(Shape context, Shape shape, Ty contained = model.expectShape(contained.asMemberShape().get().getTarget()); } - if (contained.isListShape()) { - String schemaVarName = checkImportString(context, shape.getId().getName()); - return staticTypePrefix - + checkImportString(context, shape.getId().getNamespace(), "n") + ", " + schemaVarName + ", 0, " - + valueSchema - + this.resolveSimpleSchema(context, contained) + "]" + as; - } else if (contained.isMapShape()) { + if (contained.isListShape() || contained.isMapShape()) { String schemaVarName = checkImportString(context, shape.getId().getName()); return staticTypePrefix + checkImportString(context, shape.getId().getNamespace(), "n") + ", " + schemaVarName + ", 0, " + keySchema - + valueSchema - + this.resolveSimpleSchema(context, contained) + "]" + as; + + this.resolveSimpleSchema(context, contained) + "]"; } else { return sentinel + "|" + this.resolveSimpleSchema(context, contained); }