diff --git a/.changeset/brave-trees-fry.md b/.changeset/brave-trees-fry.md new file mode 100644 index 00000000000..0fb05adc5ee --- /dev/null +++ b/.changeset/brave-trees-fry.md @@ -0,0 +1,5 @@ +--- +"@smithy/middleware-serde": patch +--- + +handle unwritable error.message field diff --git a/packages/middleware-serde/src/deserializerMiddleware.spec.ts b/packages/middleware-serde/src/deserializerMiddleware.spec.ts index de19644bfa3..9284469c0a7 100644 --- a/packages/middleware-serde/src/deserializerMiddleware.spec.ts +++ b/packages/middleware-serde/src/deserializerMiddleware.spec.ts @@ -99,4 +99,44 @@ describe("deserializerMiddleware", () => { expect(e.$response.body).toEqual("oh no"); } }); + + it("handles unwritable error.message", async () => { + const exception = Object.assign({}, mockNextResponse.response, { + $response: { + body: "", + }, + $responseBodyText: "oh no", + }); + + Object.defineProperty(exception, "message", { + set() { + throw new Error("may not call setter"); + }, + get() { + return "MockException"; + }, + }); + + const sink = vi.fn(); + + mockDeserializer.mockReset(); + mockDeserializer.mockRejectedValueOnce(exception); + try { + await deserializerMiddleware(mockOptions, mockDeserializer)(mockNext, { + logger: { + debug: sink, + info: sink, + warn: sink, + error: sink, + }, + })(mockArgs); + fail("DeserializerMiddleware should throw"); + } catch (e) { + expect(sink).toHaveBeenCalledWith( + `Deserialization error: to see the raw response, inspect the hidden field {error}.$response on this object.` + ); + expect(e.message).toEqual("MockException"); + expect(e.$response.body).toEqual("oh no"); + } + }); }); diff --git a/packages/middleware-serde/src/deserializerMiddleware.ts b/packages/middleware-serde/src/deserializerMiddleware.ts index 31fca578b02..ee1f47e7d98 100644 --- a/packages/middleware-serde/src/deserializerMiddleware.ts +++ b/packages/middleware-serde/src/deserializerMiddleware.ts @@ -3,6 +3,7 @@ import { DeserializeHandlerArguments, DeserializeHandlerOutput, DeserializeMiddleware, + HandlerExecutionContext, ResponseDeserializer, SerdeContext, SerdeFunctions, @@ -16,7 +17,7 @@ export const deserializerMiddleware = options: SerdeFunctions, deserializer: ResponseDeserializer ): DeserializeMiddleware => - (next: DeserializeHandler): DeserializeHandler => + (next: DeserializeHandler, context: HandlerExecutionContext): DeserializeHandler => async (args: DeserializeHandlerArguments): Promise> => { const { response } = await next(args); try { @@ -41,7 +42,16 @@ export const deserializerMiddleware = if (!("$metadata" in error)) { // only apply this to non-ServiceException. const hint = `Deserialization error: to see the raw response, inspect the hidden field {error}.$response on this object.`; - error.message += "\n " + hint; + try { + error.message += "\n " + hint; + } catch (e) { + // Error with an unwritable message (strict mode getter with no setter). + if (!context.logger || context.logger?.constructor?.name === "NoOpLogger") { + console.warn(hint); + } else { + context.logger?.warn?.(hint); + } + } if (typeof error.$responseBodyText !== "undefined") { // if $responseBodyText was collected by the error parser, assign it to