Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/kind-crabs-jam.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/service-error-classification": patch
---

Treat Node.js network errors as transient
5 changes: 5 additions & 0 deletions packages/service-error-classification/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,8 @@ export const TRANSIENT_ERROR_STATUS_CODES = [500, 502, 503, 504];
* Node.js system error codes that indicate timeout.
*/
export const NODEJS_TIMEOUT_ERROR_CODES = ["ECONNRESET", "ECONNREFUSED", "EPIPE", "ETIMEDOUT"];

/**
* Node.js system error codes that indicate network error.
*/
export const NODEJS_NETWORK_ERROR_CODES = ["ENOTFOUND"];
18 changes: 17 additions & 1 deletion packages/service-error-classification/src/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { describe, expect, test as it } from "vitest";

import {
CLOCK_SKEW_ERROR_CODES,
NODEJS_NETWORK_ERROR_CODES,
NODEJS_TIMEOUT_ERROR_CODES,
THROTTLING_ERROR_CODES,
TRANSIENT_ERROR_CODES,
TRANSIENT_ERROR_STATUS_CODES,
Expand All @@ -16,15 +18,17 @@ const checkForErrorType = (
httpStatusCode?: number;
$retryable?: RetryableTrait;
cause?: Partial<Error>;
code?: string;
},
errorTypeResult: boolean
) => {
const { name, httpStatusCode, $retryable, cause } = options;
const { name, httpStatusCode, $retryable, cause, code } = options;
const error = Object.assign(new Error(), {
name,
$metadata: { httpStatusCode },
$retryable,
cause,
code,
});
expect(isErrorTypeFunc(error as SdkError)).toBe(errorTypeResult);
};
Expand Down Expand Up @@ -141,6 +145,18 @@ describe("isTransientError", () => {
error.cause = error;
checkForErrorType(isTransientError, { cause: error }, false);
});

NODEJS_TIMEOUT_ERROR_CODES.forEach((code) => {
it(`should declare error with cause with the code "${code}" to be a Transient error`, () => {
checkForErrorType(isTransientError, { code }, true);
});
});

NODEJS_NETWORK_ERROR_CODES.forEach((code) => {
it(`should declare error with cause with the code "${code}" to be a Transient error`, () => {
checkForErrorType(isTransientError, { code }, true);
});
});
});

describe("isServerError", () => {
Expand Down
2 changes: 2 additions & 0 deletions packages/service-error-classification/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { SdkError } from "@smithy/types";

import {
CLOCK_SKEW_ERROR_CODES,
NODEJS_NETWORK_ERROR_CODES,
NODEJS_TIMEOUT_ERROR_CODES,
THROTTLING_ERROR_CODES,
TRANSIENT_ERROR_CODES,
Expand Down Expand Up @@ -57,6 +58,7 @@ 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 || "") ||
NODEJS_NETWORK_ERROR_CODES.includes((error as { code?: string })?.code || "") ||
TRANSIENT_ERROR_STATUS_CODES.includes(error.$metadata?.httpStatusCode || 0) ||
isBrowserNetworkError(error) ||
(error.cause !== undefined && depth <= 10 && isTransientError(error.cause, depth + 1));
Expand Down