From f736ce4f0c2a89b4f7e61ee3f5575ad8dc2031d3 Mon Sep 17 00:00:00 2001 From: George Fu Date: Thu, 25 Sep 2025 13:45:54 -0400 Subject: [PATCH] chore(core/schema): separate error schema and error ctor --- .changeset/short-dodos-cover.md | 5 ++++ .../cbor/SmithyRpcV2CborProtocol.spec.ts | 28 +++++++++-------- .../cbor/SmithyRpcV2CborProtocol.ts | 8 +++-- .../src/submodules/schema/TypeRegistry.ts | 25 ++++++++++++---- .../submodules/schema/schemas/ErrorSchema.ts | 11 +++++-- .../submodules/schema/schemas/schemas.spec.ts | 9 +++--- .../src/schemas/schemas_0.ts | 13 +++----- .../src/schemas/schemas_1_Rpc.ts | 17 ++++++----- .../codegen/schema/SchemaGenerator.java | 30 ++++++++++++++----- 9 files changed, 93 insertions(+), 53 deletions(-) create mode 100644 .changeset/short-dodos-cover.md diff --git a/.changeset/short-dodos-cover.md b/.changeset/short-dodos-cover.md new file mode 100644 index 00000000000..b43c6bdb705 --- /dev/null +++ b/.changeset/short-dodos-cover.md @@ -0,0 +1,5 @@ +--- +"@smithy/core": minor +--- + +separate error schema objects from error ctor diff --git a/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts b/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts index f1d64865c09..96627f9e05d 100644 --- a/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts +++ b/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts @@ -1,3 +1,4 @@ +import type { ErrorSchema } from "@smithy/core/schema"; import { error, list, map, op, SCHEMA, struct, TypeRegistry } from "@smithy/core/schema"; import { HttpRequest, HttpResponse } from "@smithy/protocol-http"; import type { ResponseMetadata, RetryableTrait, SchemaRef } from "@smithy/types"; @@ -311,21 +312,24 @@ describe(SmithyRpcV2CborProtocol.name, () => { public modeledProperty: string = ""; } + const ns = TypeRegistry.for("ns"); + const synthetic = TypeRegistry.for("smithy.ts.sdk.synthetic.ns"); + beforeEach(() => { - TypeRegistry.for("ns").destroy(); + ns.clear(); }); + const modeledExceptionSchema = error("ns", "ModeledException", 0, ["modeledProperty"], [0], null); + const baseServiceExceptionSchema = error("smithy.ts.sdk.synthetic.ns", "BaseServiceException", 0, [], [], null); + it("should throw the schema error ctor if one exists", async () => { // this is for modeled exceptions. - TypeRegistry.for("ns").register( - "ns#ModeledException", - error("ns", "ModeledException", 0, ["modeledProperty"], [0], ModeledExceptionCtor) - ); - TypeRegistry.for("ns").register( - "smithy.ts.sdk.synthetic.ns#BaseServiceException", - error("smithy.ts.sdk.synthetic.ns", "BaseServiceException", 0, [], [], ServiceBaseException) - ); + ns.register(modeledExceptionSchema.getName(), modeledExceptionSchema); + ns.registerError(modeledExceptionSchema, ModeledExceptionCtor); + + synthetic.register(baseServiceExceptionSchema.getName(), baseServiceExceptionSchema); + synthetic.registerError(baseServiceExceptionSchema, ServiceBaseException); try { await protocol.deserializeResponse(operation, serdeContext as any, errorResponse); @@ -340,10 +344,8 @@ 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. - TypeRegistry.for("ns").register( - "smithy.ts.sdk.synthetic.ns#BaseServiceException", - error("smithy.ts.sdk.synthetic.ns", "BaseServiceException", 0, [], [], ServiceBaseException) - ); + synthetic.register(baseServiceExceptionSchema.getName(), baseServiceExceptionSchema); + synthetic.registerError(baseServiceExceptionSchema, ServiceBaseException); try { await protocol.deserializeResponse(operation, serdeContext as any, errorResponse); diff --git a/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.ts b/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.ts index 108a1671655..6d1119cf437 100644 --- a/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.ts +++ b/packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.ts @@ -111,17 +111,19 @@ export class SmithyRpcV2CborProtocol extends RpcProtocol { if (dataObject.Message) { dataObject.message = dataObject.Message; } - const baseExceptionSchema = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace).getBaseException(); + const synthetic = TypeRegistry.for("smithy.ts.sdk.synthetic." + namespace); + const baseExceptionSchema = synthetic.getBaseException(); if (baseExceptionSchema) { - const ErrorCtor = baseExceptionSchema.ctor; + const ErrorCtor = synthetic.getErrorCtor(baseExceptionSchema); throw Object.assign(new ErrorCtor({ name: errorName }), errorMetadata, dataObject); } throw Object.assign(new Error(errorName), errorMetadata, dataObject); } const ns = NormalizedSchema.of(errorSchema); + const ErrorCtor = registry.getErrorCtor(errorSchema); const message = dataObject.message ?? dataObject.Message ?? "Unknown"; - const exception = new errorSchema.ctor(message); + const exception = new ErrorCtor(message); const output = {} as any; for (const [name, member] of ns.structIterator()) { diff --git a/packages/core/src/submodules/schema/TypeRegistry.ts b/packages/core/src/submodules/schema/TypeRegistry.ts index a8b8034058b..f6ff1f7eb1c 100644 --- a/packages/core/src/submodules/schema/TypeRegistry.ts +++ b/packages/core/src/submodules/schema/TypeRegistry.ts @@ -12,7 +12,8 @@ export class TypeRegistry { private constructor( public readonly namespace: string, - private schemas: Map = new Map() + private schemas: Map = new Map(), + private exceptions: Map = new Map() ) {} /** @@ -34,8 +35,7 @@ export class TypeRegistry { */ public register(shapeId: string, schema: ISchema) { const qualifiedName = this.normalizeShapeId(shapeId); - const registry = TypeRegistry.for(this.getNamespace(shapeId)); - registry.schemas.set(qualifiedName, schema); + this.schemas.set(qualifiedName, schema); } /** @@ -50,6 +50,21 @@ export class TypeRegistry { return this.schemas.get(id)!; } + /** + * Associates an error schema with its constructor. + */ + public registerError(errorSchema: ErrorSchema, ctor: any) { + this.exceptions.set(errorSchema, ctor); + } + + /** + * @param errorSchema - query. + * @returns Error constructor that extends the service's base exception. + */ + public getErrorCtor(errorSchema: ErrorSchema): any { + return this.exceptions.get(errorSchema); + } + /** * The smithy-typescript code generator generates a synthetic (i.e. unmodeled) base exception, * because generated SDKs before the introduction of schemas have the notion of a ServiceBaseException, which @@ -83,9 +98,9 @@ export class TypeRegistry { /** * Unloads the current TypeRegistry. */ - public destroy() { - TypeRegistry.registries.delete(this.namespace); + public clear() { this.schemas.clear(); + this.exceptions.clear(); } private normalizeShapeId(shapeId: string) { diff --git a/packages/core/src/submodules/schema/schemas/ErrorSchema.ts b/packages/core/src/submodules/schema/schemas/ErrorSchema.ts index cf6d441b7c0..accb166cf64 100644 --- a/packages/core/src/submodules/schema/schemas/ErrorSchema.ts +++ b/packages/core/src/submodules/schema/schemas/ErrorSchema.ts @@ -1,6 +1,5 @@ import type { SchemaRef, SchemaTraits } from "@smithy/types"; -import { TypeRegistry } from "../TypeRegistry"; import { Schema } from "./Schema"; import { StructureSchema } from "./StructureSchema"; @@ -14,6 +13,9 @@ import { StructureSchema } from "./StructureSchema"; */ export class ErrorSchema extends StructureSchema { public static readonly symbol = Symbol.for("@smithy/err"); + /** + * @deprecated - field unused. + */ public ctor!: any; protected readonly symbol = ErrorSchema.symbol; } @@ -36,7 +38,10 @@ export const error = ( traits: SchemaTraits, memberNames: string[], memberList: SchemaRef[], - ctor: any + /** + * @deprecated - field unused. + */ + ctor?: any ): ErrorSchema => Schema.assign(new ErrorSchema(), { name, @@ -44,5 +49,5 @@ export const error = ( traits, memberNames, memberList, - ctor, + ctor: null, }); diff --git a/packages/core/src/submodules/schema/schemas/schemas.spec.ts b/packages/core/src/submodules/schema/schemas/schemas.spec.ts index f7cc1b293c5..b6e13c448e3 100644 --- a/packages/core/src/submodules/schema/schemas/schemas.spec.ts +++ b/packages/core/src/submodules/schema/schemas/schemas.spec.ts @@ -34,20 +34,19 @@ describe("schemas", () => { }); describe(ErrorSchema.name, () => { - const schema = error("ack", "Error", 0, [], [], Error); + const schema = error("ack", "Error", 0, [], []); it("is a StructureSchema", () => { expect(schema).toBeInstanceOf(StructureSchema); expect(schema).toBeInstanceOf(ErrorSchema); }); - it("additionally defines an error constructor", () => { - expect(schema.ctor).toBeInstanceOf(Function); - expect(new schema.ctor()).toBeInstanceOf(schema.ctor); + it("deprecated reference to the error constructor", () => { + expect(schema.ctor).toBe(null); }); it("has a factory and the factory registers the schema", () => { - expect(error("ack", "Error", 0, [], [], Error)).toEqual(schema); + expect(error("ack", "Error", 0, [], [])).toEqual(schema); expect(TypeRegistry.for("ack").getSchema(schema.name)).toEqual(schema); }); 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 0250a19e8fe..6b2c9ec0c83 100644 --- a/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_0.ts +++ b/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_0.ts @@ -105,6 +105,7 @@ export const _p = "path"; export const _rM = "recursiveMember"; export const _s = "sparse"; export const _sBM = "sparseBooleanMap"; +export const _sC = "smithy.ts.sdk.synthetic.smithy.protocoltests.rpcv2Cbor"; export const _sL = "stringList"; export const _sLt = "structureList"; export const _sNM = "sparseNumberMap"; @@ -131,15 +132,9 @@ export const n2 = "smithy.protocoltests.shared"; // smithy-typescript generated code import { RpcV2ProtocolServiceException as __RpcV2ProtocolServiceException } from "../models/RpcV2ProtocolServiceException"; -import { error } from "@smithy/core/schema"; +import { TypeRegistry, error } from "@smithy/core/schema"; /* eslint no-var: 0 */ -export var RpcV2ProtocolServiceException = error( - "smithy.ts.sdk.synthetic.smithy.protocoltests.rpcv2Cbor", - "RpcV2ProtocolServiceException", - 0, - [], - [], - __RpcV2ProtocolServiceException -); +export var RpcV2ProtocolServiceException = error(_sC, "RpcV2ProtocolServiceException", 0, [], [], null); +TypeRegistry.for(_sC).registerError(RpcV2ProtocolServiceException, __RpcV2ProtocolServiceException); diff --git a/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_1_Rpc.ts b/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_1_Rpc.ts index 8f3205f17cd..d392f856fb8 100644 --- a/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_1_Rpc.ts +++ b/private/smithy-rpcv2-cbor-schema/src/schemas/schemas_1_Rpc.ts @@ -128,7 +128,7 @@ import { n1, n2, } from "./schemas_0"; -import { error, list, map, op, struct } from "@smithy/core/schema"; +import { TypeRegistry, error, list, map, op, struct } from "@smithy/core/schema"; /* eslint no-var: 0 */ @@ -142,9 +142,10 @@ export var ValidationException = error( }, [_m, _fL], [0, () => ValidationExceptionFieldList], - - __ValidationException + null ); +TypeRegistry.for(n0).registerError(ValidationException, __ValidationException); + export var ValidationExceptionField = struct(n0, _VEF, 0, [_p, _m], [0, 0]); export var ClientOptionalDefaults = struct(n1, _COD, 0, [_me], [1]); export var ComplexError = error( @@ -155,9 +156,10 @@ export var ComplexError = error( }, [_TL, _N], [0, () => ComplexNestedErrorData], - - __ComplexError + null ); +TypeRegistry.for(n1).registerError(ComplexError, __ComplexError); + export var ComplexNestedErrorData = struct(n1, _CNED, 0, [_F], [0]); export var Defaults = struct( n1, @@ -199,9 +201,10 @@ export var InvalidGreeting = error( }, [_M], [0], - - __InvalidGreeting + null ); +TypeRegistry.for(n1).registerError(InvalidGreeting, __InvalidGreeting); + export var OperationWithDefaultsInput = struct( n1, _OWDI, 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 861daa85b67..81703842ca3 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 @@ -302,14 +302,20 @@ private void writeStructureSchema(StructureShape shape) { ); writer.openBlock(""" export var $L = error($L, $L,""", - "", + ", null);", getShapeVariableName(shape), checkImportString(shape, shape.getId().getNamespace(), "n"), checkImportString(shape, shape.getId().getName()), () -> doWithMembers(shape) ); - writer.writeInline(",$L", exceptionCtorSymbolName); - writer.write(");"); + writer.addImportSubmodule("TypeRegistry", null, TypeScriptDependency.SMITHY_CORE, "/schema"); + writer.write(""" + TypeRegistry.for($L).registerError($L, $L); + """, + checkImportString(shape, shape.getId().getNamespace(), "n"), + getShapeVariableName(shape), + exceptionCtorSymbolName + ); } else { writer.addImportSubmodule("struct", "struct", TypeScriptDependency.SMITHY_CORE, "/schema"); writer.openBlock(""" @@ -335,20 +341,28 @@ private void writeBaseError() { String namespace = settings.getService(model).getId().getNamespace(); String exceptionCtorSymbolName = "__" + serviceExceptionName; - writer.addImportSubmodule("error", "error", TypeScriptDependency.SMITHY_CORE, "/schema"); + writer.addImportSubmodule("error", null, TypeScriptDependency.SMITHY_CORE, "/schema"); writer.addRelativeImport( serviceExceptionName, exceptionCtorSymbolName, Paths.get("..", "models", serviceExceptionName) ); + + String syntheticNamespace = store.var("smithy.ts.sdk.synthetic." + namespace); writer.write(""" - export var $L = error($S, $S, 0, [], []""", + export var $L = error($L, $S, 0, [], [], null);""", serviceExceptionName, - "smithy.ts.sdk.synthetic." + namespace, + syntheticNamespace, serviceExceptionName ); - writer.writeInline(",$L", exceptionCtorSymbolName); - writer.write(");"); + writer.addImportSubmodule("TypeRegistry", null, TypeScriptDependency.SMITHY_CORE, "/schema"); + writer.write(""" + TypeRegistry.for($L).registerError($L, $L); + """, + syntheticNamespace, + serviceExceptionName, + exceptionCtorSymbolName + ); } private void writeUnionSchema(UnionShape shape) {