Skip to content

Commit e5b876f

Browse files
authored
fix(middleware-retry): defaultStrategy handles any error (#2349)
* fix(middleware-retry): defaultStrategy handles any error The lower level stacks than throw anything to retry strategy in practice, so casting them to SdkError type is unsafe. With this change the retryStrategy will do best effort to convert the thrown any object to SdkError and then supply to retryDecider and so on. Another change is making SdkError type more general. It previously requires $fault, $metadata, which is obviously incorrect. Error thrown by SDK itself never contains these keys. So I mark them optional. Granted this is a breaking change to function interface like RetryDecider, but anyone depending it being required should already fail anyway. * fix(protocol-test): fake handler should throw error
1 parent f01d5ae commit e5b876f

File tree

8 files changed

+40
-12
lines changed

8 files changed

+40
-12
lines changed

packages/middleware-retry/src/defaultStrategy.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,16 @@ describe("defaultStrategy", () => {
100100
});
101101
});
102102

103+
it("handles non-standard errors", () => {
104+
const nonStandardErrors = [undefined, "foo", { foo: "bar" }, 123, false, null];
105+
const maxAttempts = 1;
106+
const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts));
107+
for (const error of nonStandardErrors) {
108+
next = jest.fn().mockRejectedValue(error);
109+
expect(retryStrategy.retry(next, { request: { headers: {} } } as any)).rejects.toBeInstanceOf(Error);
110+
}
111+
});
112+
103113
describe("retryDecider init", () => {
104114
it("sets defaultRetryDecider if options is undefined", () => {
105115
const retryStrategy = new StandardRetryStrategy(() => Promise.resolve(maxAttempts));

packages/middleware-retry/src/defaultStrategy.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ export class StandardRetryStrategy implements RetryStrategy {
129129
output.$metadata.totalRetryDelay = totalDelay;
130130

131131
return { response, output };
132-
} catch (err) {
132+
} catch (e) {
133+
const err = asSdkError(e);
133134
attempts++;
134135
if (this.shouldRetry(err as SdkError, attempts, maxAttempts)) {
135136
retryTokenAmount = this.retryQuota.retrieveRetryTokens(err);
@@ -154,3 +155,10 @@ export class StandardRetryStrategy implements RetryStrategy {
154155
}
155156
}
156157
}
158+
159+
const asSdkError = (error: unknown): SdkError => {
160+
if (error instanceof Error) return error;
161+
if (error instanceof Object) return Object.assign(new Error(), error);
162+
if (typeof error === "string") return new Error(error);
163+
return new Error(`AWS SDK error wrapper for ${error}`);
164+
};

packages/smithy-client/src/sdk-error.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ import { MetadataBearer } from "@aws-sdk/types";
22

33
import { SmithyException } from "./exception";
44

5-
export type SdkError = Error & SmithyException & MetadataBearer;
5+
export type SdkError = Error & Partial<SmithyException> & Partial<MetadataBearer>;

protocol_tests/aws-ec2/tests/functional/ec2query.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@ import { Readable } from "stream";
2727
/**
2828
* Throws an expected exception that contains the serialized request.
2929
*/
30-
class EXPECTED_REQUEST_SERIALIZATION_ERROR {
31-
constructor(readonly request: HttpRequest) {}
30+
class EXPECTED_REQUEST_SERIALIZATION_ERROR extends Error {
31+
constructor(readonly request: HttpRequest) {
32+
super();
33+
}
3234
}
3335

3436
/**

protocol_tests/aws-json/tests/functional/awsjson1_1.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ import { Readable } from "stream";
1818
/**
1919
* Throws an expected exception that contains the serialized request.
2020
*/
21-
class EXPECTED_REQUEST_SERIALIZATION_ERROR {
22-
constructor(readonly request: HttpRequest) {}
21+
class EXPECTED_REQUEST_SERIALIZATION_ERROR extends Error {
22+
constructor(readonly request: HttpRequest) {
23+
super();
24+
}
2325
}
2426

2527
/**

protocol_tests/aws-query/tests/functional/awsquery.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ import { Readable } from "stream";
3535
/**
3636
* Throws an expected exception that contains the serialized request.
3737
*/
38-
class EXPECTED_REQUEST_SERIALIZATION_ERROR {
39-
constructor(readonly request: HttpRequest) {}
38+
class EXPECTED_REQUEST_SERIALIZATION_ERROR extends Error {
39+
constructor(readonly request: HttpRequest) {
40+
super();
41+
}
4042
}
4143

4244
/**

protocol_tests/aws-restjson/tests/functional/restjson1.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,10 @@ import { Readable } from "stream";
5151
/**
5252
* Throws an expected exception that contains the serialized request.
5353
*/
54-
class EXPECTED_REQUEST_SERIALIZATION_ERROR {
55-
constructor(readonly request: HttpRequest) {}
54+
class EXPECTED_REQUEST_SERIALIZATION_ERROR extends Error {
55+
constructor(readonly request: HttpRequest) {
56+
super();
57+
}
5658
}
5759

5860
/**

protocol_tests/aws-restxml/tests/functional/restxml.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ import { Readable } from "stream";
6060
/**
6161
* Throws an expected exception that contains the serialized request.
6262
*/
63-
class EXPECTED_REQUEST_SERIALIZATION_ERROR {
64-
constructor(readonly request: HttpRequest) {}
63+
class EXPECTED_REQUEST_SERIALIZATION_ERROR extends Error {
64+
constructor(readonly request: HttpRequest) {
65+
super();
66+
}
6567
}
6668

6769
/**

0 commit comments

Comments
 (0)