Skip to content

Commit 103535a

Browse files
authored
fix(middleware-serde): add response metadata to deserialization errors (#1588)
* fix(middleware-serde): add response metadata to deserialization errors * statusCode is number is implied by HttpResponse.isInstance
1 parent fb0ef76 commit 103535a

File tree

5 files changed

+105
-0
lines changed

5 files changed

+105
-0
lines changed

.changeset/tiny-otters-talk.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/middleware-serde": patch
3+
---
4+
5+
add response metadata to deserializer errors

packages/middleware-serde/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
},
2626
"license": "Apache-2.0",
2727
"dependencies": {
28+
"@smithy/protocol-http": "workspace:^",
2829
"@smithy/types": "workspace:^",
2930
"tslib": "^2.6.2"
3031
},

packages/middleware-serde/src/deserializerMiddleware.spec.ts

+69
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { HttpResponse } from "@smithy/protocol-http";
12
import { EndpointBearer, SerdeFunctions } from "@smithy/types";
23
import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest";
34

@@ -39,6 +40,12 @@ describe("deserializerMiddleware", () => {
3940
statusCode: 200,
4041
headers: {},
4142
},
43+
$metadata: {
44+
httpStatusCode: 200,
45+
requestId: undefined,
46+
extendedRequestId: undefined,
47+
cfId: undefined,
48+
},
4249
};
4350

4451
const mockResponse = {
@@ -139,4 +146,66 @@ describe("deserializerMiddleware", () => {
139146
expect(e.$response.body).toEqual("oh no");
140147
}
141148
});
149+
150+
describe("metadata", () => {
151+
it("assigns metadata from the response in the event of a deserializer failure", async () => {
152+
const midware = deserializerMiddleware(mockOptions, async () => {
153+
JSON.parse(`this isn't JSON`);
154+
});
155+
const handler = midware(
156+
async () => ({
157+
response: new HttpResponse({
158+
headers: {
159+
"x-namespace-requestid": "requestid",
160+
"x-namespace-id-2": "id2",
161+
"x-namespace-cf-id": "cf",
162+
},
163+
statusCode: 503,
164+
}),
165+
}),
166+
{}
167+
);
168+
try {
169+
await handler(mockArgs);
170+
fail("DeserializerMiddleware should throw");
171+
} catch (e) {
172+
expect(e.$metadata).toEqual({
173+
httpStatusCode: 503,
174+
requestId: "requestid",
175+
extendedRequestId: "id2",
176+
cfId: "cf",
177+
});
178+
}
179+
expect.assertions(1);
180+
});
181+
182+
it("assigns any available metadata from the response in the event of a deserializer failure", async () => {
183+
const midware = deserializerMiddleware(mockOptions, async () => {
184+
JSON.parse(`this isn't JSON`);
185+
});
186+
const handler = midware(
187+
async () => ({
188+
response: new HttpResponse({
189+
statusCode: 301,
190+
headers: {
191+
"x-namespace-requestid": "requestid",
192+
},
193+
}),
194+
}),
195+
{}
196+
);
197+
try {
198+
await handler(mockArgs);
199+
fail("DeserializerMiddleware should throw");
200+
} catch (e) {
201+
expect(e.$metadata).toEqual({
202+
httpStatusCode: 301,
203+
requestId: "requestid",
204+
extendedRequestId: undefined,
205+
cfId: undefined,
206+
});
207+
}
208+
expect.assertions(1);
209+
});
210+
});
142211
});

packages/middleware-serde/src/deserializerMiddleware.ts

+29
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { HttpResponse } from "@smithy/protocol-http";
12
import {
23
DeserializeHandler,
34
DeserializeHandlerArguments,
45
DeserializeHandlerOutput,
56
DeserializeMiddleware,
67
HandlerExecutionContext,
8+
MetadataBearer,
79
ResponseDeserializer,
810
SerdeContext,
911
SerdeFunctions,
@@ -60,8 +62,35 @@ export const deserializerMiddleware =
6062
error.$response.body = error.$responseBodyText;
6163
}
6264
}
65+
66+
try {
67+
// if the deserializer failed, then $metadata may still be set
68+
// by taking information from the response.
69+
if (HttpResponse.isInstance(response)) {
70+
const { headers = {} } = response;
71+
const headerEntries = Object.entries(headers);
72+
(error as MetadataBearer).$metadata = {
73+
httpStatusCode: response.statusCode,
74+
requestId: findHeader(/^x-[\w-]+-request-?id$/, headerEntries),
75+
extendedRequestId: findHeader(/^x-[\w-]+-id-2$/, headerEntries),
76+
cfId: findHeader(/^x-[\w-]+-cf-id$/, headerEntries),
77+
};
78+
}
79+
} catch (e) {
80+
// ignored, error object was not writable.
81+
}
6382
}
6483

6584
throw error;
6685
}
6786
};
87+
88+
/**
89+
* @internal
90+
* @returns header value where key matches regex.
91+
*/
92+
const findHeader = (pattern: RegExp, headers: [string, string][]): string | undefined => {
93+
return (headers.find(([k]) => {
94+
return k.match(pattern);
95+
}) || [void 0, void 1])[1];
96+
};

yarn.lock

+1
Original file line numberDiff line numberDiff line change
@@ -2791,6 +2791,7 @@ __metadata:
27912791
version: 0.0.0-use.local
27922792
resolution: "@smithy/middleware-serde@workspace:packages/middleware-serde"
27932793
dependencies:
2794+
"@smithy/protocol-http": "workspace:^"
27942795
"@smithy/types": "workspace:^"
27952796
"@smithy/util-test": "workspace:^"
27962797
concurrently: "npm:7.0.0"

0 commit comments

Comments
 (0)