Skip to content

Commit 15d574f

Browse files
committed
feat(protocol-http): implement SRA HttpResponse
This change implements the core functionality of HttpResponse for SRA. New properties were added to the HttpResponse class, old properties and interfaces were deprecated. To maintain compatibility with old implementation, Proxy was added for headers property, and Object.defineProperties is used with getters/setters for other old properties. This change does not include updates to the SDK to use new functionality. Additional notes: * HttpResponse.isInstance was left unchanged to maintain compatibility. Previous implementation only checked for specific properties, so it would return true when passed the HttpResponse interface from types package. * smithy-lang/smithy-typescript#698 is a related change that ensures protocol tests are using HttpResponse constructor. Due to type changes in HttpResponse, this is required.
1 parent d9d24b0 commit 15d574f

File tree

3 files changed

+146
-10
lines changed

3 files changed

+146
-10
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { Fields } from "./Fields";
2+
import { HttpResponse } from "./httpResponse";
3+
4+
describe("HttpResponse", () => {
5+
describe("construction", () => {
6+
it.each([
7+
["constructor", new HttpResponse({ statusCode: 200 })],
8+
["`from` factory method", HttpResponse.from({ status: 200 })],
9+
])("can be constructed using %s", (_, response) => {
10+
expect(response.status).toEqual(200);
11+
expect(response.fields).toEqual(Fields.from([]));
12+
expect(response.statusCode).toEqual(200);
13+
expect(response.headers).toEqual({});
14+
});
15+
it("populates `fields` when using constructor", () => {
16+
const response = new HttpResponse({ statusCode: 200, headers: { foo: "bar" } });
17+
expect(response.fields.getField("foo")?.toString()).toEqual("bar");
18+
});
19+
20+
it("can be constructed with object spread syntax", () => {
21+
const baseResponse = new HttpResponse({
22+
statusCode: 200,
23+
headers: { foo: "bar" },
24+
body: "body",
25+
});
26+
const updatedResponse = new HttpResponse({
27+
...baseResponse,
28+
headers: {
29+
...baseResponse.headers,
30+
baz: "qux",
31+
},
32+
});
33+
expect(updatedResponse.body).toEqual(baseResponse.body);
34+
expect(updatedResponse.headers).toEqual({ ...baseResponse.headers, baz: "qux" });
35+
});
36+
});
37+
38+
describe("deprecated properties and their getters/setters", () => {
39+
const mockHeaders = {
40+
foo: "bar",
41+
baz: "qux",
42+
};
43+
const mockFields = Fields.from([
44+
{ name: "foo", values: ["bar"] },
45+
{ name: "baz", values: ["qux"] },
46+
]);
47+
48+
it("can be set via constructor", () => {
49+
const response = new HttpResponse({ headers: mockHeaders, statusCode: 200 });
50+
expect(response.headers).toEqual(mockHeaders);
51+
expect(response.statusCode).toEqual(200);
52+
});
53+
54+
it("can be set explicitly", () => {
55+
const response = new HttpResponse({ statusCode: 200 });
56+
response.headers = mockHeaders;
57+
response.statusCode = 201;
58+
expect(response.headers).toEqual(mockHeaders);
59+
expect(response.statusCode).toEqual(201);
60+
});
61+
62+
it("updates non-deprecated property when set via constructor", () => {
63+
const response = new HttpResponse({ statusCode: 200 });
64+
response.headers = mockHeaders;
65+
response.statusCode = 201;
66+
expect(response.fields).toEqual(mockFields);
67+
expect(response.status).toEqual(201);
68+
});
69+
});
70+
});

packages/protocol-http/src/httpResponse.ts

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,88 @@
1-
import { HeaderBag, HttpMessage, HttpResponse as IHttpResponse } from "@aws-sdk/types";
1+
import { HeaderBag, HttpMessage, Response } from "@aws-sdk/types";
22

3-
type HttpResponseOptions = Partial<HttpMessage> & {
3+
import { Fields } from "./Fields";
4+
import { getHeadersProxy, headersToFields } from "./headersProxy";
5+
6+
export type HttpResponseOptions = Partial<HttpMessage> & {
47
statusCode: number;
58
};
69

7-
export interface HttpResponse extends IHttpResponse {}
10+
export type HttpResponseFromOptions = {
11+
status: number;
12+
reason?: string;
13+
fields?: Fields;
14+
body?: any;
15+
};
16+
17+
export interface HttpResponse extends Response {
18+
status: number;
19+
reason?: string;
20+
fields: Fields;
21+
body: any;
22+
}
23+
24+
export class HttpResponse implements Response {
25+
public status: number;
26+
public reason?: string;
27+
public fields: Fields;
28+
public body: any;
29+
30+
/**
31+
* @deprecated Use {@link status}
32+
*/
33+
public statusCode = -1;
834

9-
export class HttpResponse {
10-
public statusCode: number;
11-
public headers: HeaderBag;
12-
public body?: any;
35+
/**
36+
* @deprecated Use {@link fields}
37+
*/
38+
public headers: HeaderBag = {};
1339

40+
/**
41+
* @deprecated Use {@link HttpResponse.from}
42+
*/
1443
constructor(options: HttpResponseOptions) {
15-
this.statusCode = options.statusCode;
16-
this.headers = options.headers || {};
44+
this.status = options.statusCode;
45+
this.fields = headersToFields(options.headers || {});
1746
this.body = options.body;
47+
// Deprecated properties are accessed using getters and setters.
48+
// Object.defineProperties is used so the properties are still considered
49+
// enumerable.
50+
// eslint-disable-next-line @typescript-eslint/no-this-alias
51+
const httpResponse = this;
52+
Object.defineProperties(httpResponse, {
53+
statusCode: {
54+
enumerable: true,
55+
get() {
56+
return httpResponse.status;
57+
},
58+
set(statusCode: number) {
59+
httpResponse.status = statusCode;
60+
},
61+
},
62+
headers: {
63+
enumerable: true,
64+
get() {
65+
return getHeadersProxy(httpResponse.fields);
66+
},
67+
set(headers: HeaderBag) {
68+
httpResponse.fields = headersToFields(headers);
69+
},
70+
},
71+
});
72+
}
73+
74+
static from(options: HttpResponseFromOptions) {
75+
const response = new HttpResponse({ statusCode: options.status, body: options.body });
76+
response.reason = options.reason;
77+
// Constructor handles setting default values for fields.
78+
if (options.fields) {
79+
response.fields = options.fields;
80+
}
81+
return response;
1882
}
1983

2084
static isInstance(response: unknown): response is HttpResponse {
21-
//determine if response is a valid HttpResponse
85+
// determine if response is a valid HttpResponse
2286
if (!response) return false;
2387
const resp = response as any;
2488
return typeof resp.statusCode === "number" && typeof resp.headers === "object";

packages/types/src/http.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ export interface HttpRequest extends HttpMessage, Endpoint {
9999
*
100100
* Represents an HTTP message as received in reply to a request. Contains a
101101
* numeric status code in addition to standard message properties.
102+
*
103+
* @deprecated Replaced by implementation HttpResponse in @aws-sdk/protocol-http.
102104
*/
103105
export interface HttpResponse extends HttpMessage {
104106
statusCode: number;

0 commit comments

Comments
 (0)