diff --git a/.changeset/good-dots-run.md b/.changeset/good-dots-run.md new file mode 100644 index 00000000000..4bf66be2049 --- /dev/null +++ b/.changeset/good-dots-run.md @@ -0,0 +1,6 @@ +--- +"@smithy/service-error-classification": patch +"@smithy/types": patch +--- + +add support for error cause in transient error checks diff --git a/packages/service-error-classification/src/index.spec.ts b/packages/service-error-classification/src/index.spec.ts index 5daf462a175..b6173f3d9e0 100644 --- a/packages/service-error-classification/src/index.spec.ts +++ b/packages/service-error-classification/src/index.spec.ts @@ -15,14 +15,16 @@ const checkForErrorType = ( name?: string; httpStatusCode?: number; $retryable?: RetryableTrait; + cause?: Partial; }, errorTypeResult: boolean ) => { - const { name, httpStatusCode, $retryable } = options; + const { name, httpStatusCode, $retryable, cause } = options; const error = Object.assign(new Error(), { name, $metadata: { httpStatusCode }, $retryable, + cause, }); expect(isErrorTypeFunc(error as SdkError)).toBe(errorTypeResult); }; @@ -127,6 +129,18 @@ describe("isTransientError", () => { break; } } + + TRANSIENT_ERROR_CODES.forEach((name) => { + it(`should declare error with cause with the name "${name}" to be a Transient error`, () => { + checkForErrorType(isTransientError, { cause: { name } }, true); + }); + }); + + it("should limit recursion to 10 depth", () => { + const error = { cause: null } as SdkError; + error.cause = error; + checkForErrorType(isTransientError, { cause: error }, false); + }); }); describe("isServerError", () => { diff --git a/packages/service-error-classification/src/index.ts b/packages/service-error-classification/src/index.ts index 77941ba8a5d..2b1d098059f 100644 --- a/packages/service-error-classification/src/index.ts +++ b/packages/service-error-classification/src/index.ts @@ -31,11 +31,12 @@ export const isThrottlingError = (error: SdkError) => * cause where the NodeHttpHandler does not decorate the Error with * the name "TimeoutError" to be checked by the TRANSIENT_ERROR_CODES condition. */ -export const isTransientError = (error: SdkError) => +export const isTransientError = (error: SdkError, depth = 0): boolean => isClockSkewCorrectedError(error) || TRANSIENT_ERROR_CODES.includes(error.name) || NODEJS_TIMEOUT_ERROR_CODES.includes((error as { code?: string })?.code || "") || - TRANSIENT_ERROR_STATUS_CODES.includes(error.$metadata?.httpStatusCode || 0); + TRANSIENT_ERROR_STATUS_CODES.includes(error.$metadata?.httpStatusCode || 0) || + (error.cause !== undefined && depth <= 10 && isTransientError(error.cause, depth + 1)); export const isServerError = (error: SdkError) => { if (error.$metadata?.httpStatusCode !== undefined) { diff --git a/packages/types/src/shapes.ts b/packages/types/src/shapes.ts index 129444877c1..924ca2f327c 100644 --- a/packages/types/src/shapes.ts +++ b/packages/types/src/shapes.ts @@ -90,4 +90,5 @@ export type SdkError = Error & */ readonly clockSkewCorrected?: true; }; + cause?: Error; };