Skip to content

Commit 796567d

Browse files
authored
chore(protocol-http): guidance for HttpRequest cloning (#1333)
1 parent 6aac850 commit 796567d

File tree

3 files changed

+111
-11
lines changed

3 files changed

+111
-11
lines changed

.changeset/khaki-seahorses-cough.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@smithy/protocol-http": patch
3+
---
4+
5+
add guidance for HttpRequest cloning
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { HttpRequest, IHttpRequest } from "./httpRequest";
2+
3+
describe("HttpRequest", () => {
4+
const httpRequest: IHttpRequest = {
5+
headers: {
6+
hKey: "header-value",
7+
},
8+
query: {
9+
qKey: "query-value",
10+
},
11+
method: "GET",
12+
protocol: "https",
13+
hostname: "localhost",
14+
path: "/",
15+
body: [],
16+
};
17+
18+
it("should statically clone with deep-cloned headers/query but shallow cloned body", () => {
19+
const httpRequest: IHttpRequest = {
20+
headers: {
21+
hKey: "header-value",
22+
},
23+
query: {
24+
qKey: "query-value",
25+
},
26+
method: "GET",
27+
protocol: "https",
28+
hostname: "localhost",
29+
path: "/",
30+
body: [],
31+
};
32+
33+
const clone1 = HttpRequest.clone(httpRequest);
34+
const clone2 = HttpRequest.clone(httpRequest);
35+
36+
expect(new HttpRequest(httpRequest)).toEqual(clone1);
37+
expect(clone1).toEqual(clone2);
38+
39+
expect(clone1.query).not.toBe(clone2.query);
40+
expect(clone1.headers).not.toBe(clone2.headers);
41+
expect(clone1.body).toBe(clone2.body);
42+
});
43+
44+
it("should maintain a deprecated instance clone method", () => {
45+
const httpRequestInstance = new HttpRequest(httpRequest);
46+
47+
const clone1 = httpRequestInstance.clone();
48+
const clone2 = httpRequestInstance.clone();
49+
50+
expect(httpRequestInstance).toEqual(clone1);
51+
expect(clone1).toEqual(clone2);
52+
53+
expect(clone1.query).not.toBe(clone2.query);
54+
expect(clone1.headers).not.toBe(clone2.headers);
55+
expect(clone1.body).toBe(clone2.body);
56+
});
57+
});

packages/protocol-http/src/httpRequest.ts

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,24 @@ import { HeaderBag, HttpMessage, HttpRequest as IHttpRequest, QueryParameterBag,
33

44
type HttpRequestOptions = Partial<HttpMessage> & Partial<URI> & { method?: string };
55

6+
/**
7+
* Use the distinct IHttpRequest interface from @smithy/types instead.
8+
* This should not be used due to
9+
* overlapping with the concrete class' name.
10+
*
11+
* This is not marked deprecated since that would mark the concrete class
12+
* deprecated as well.
13+
*/
614
export interface HttpRequest extends IHttpRequest {}
715

16+
/**
17+
* @public
18+
*/
19+
export { IHttpRequest };
20+
21+
/**
22+
* @public
23+
*/
824
export class HttpRequest implements HttpMessage, URI {
925
public method: string;
1026
public protocol: string;
@@ -18,7 +34,7 @@ export class HttpRequest implements HttpMessage, URI {
1834
public fragment?: string;
1935
public body?: any;
2036

21-
constructor(options: HttpRequestOptions) {
37+
public constructor(options: HttpRequestOptions) {
2238
this.method = options.method || "GET";
2339
this.hostname = options.hostname || "localhost";
2440
this.port = options.port;
@@ -36,9 +52,31 @@ export class HttpRequest implements HttpMessage, URI {
3652
this.fragment = options.fragment;
3753
}
3854

39-
static isInstance(request: unknown): request is HttpRequest {
40-
//determine if request is a valid httpRequest
41-
if (!request) return false;
55+
/**
56+
* Note: this does not deep-clone the body.
57+
*/
58+
public static clone(request: IHttpRequest) {
59+
const cloned = new HttpRequest({
60+
...request,
61+
headers: { ...request.headers },
62+
});
63+
if (cloned.query) {
64+
cloned.query = cloneQuery(cloned.query);
65+
}
66+
return cloned;
67+
}
68+
69+
/**
70+
* This method only actually asserts that request is the interface {@link IHttpRequest},
71+
* and not necessarily this concrete class. Left in place for API stability.
72+
*
73+
* Do not call instance methods on the input of this function, and
74+
* do not assume it has the HttpRequest prototype.
75+
*/
76+
public static isInstance(request: unknown): request is HttpRequest {
77+
if (!request) {
78+
return false;
79+
}
4280
const req: any = request;
4381
return (
4482
"method" in req &&
@@ -50,13 +88,13 @@ export class HttpRequest implements HttpMessage, URI {
5088
);
5189
}
5290

53-
clone(): HttpRequest {
54-
const cloned = new HttpRequest({
55-
...this,
56-
headers: { ...this.headers },
57-
});
58-
if (cloned.query) cloned.query = cloneQuery(cloned.query);
59-
return cloned;
91+
/**
92+
* @deprecated use static HttpRequest.clone(request) instead. It's not safe to call
93+
* this method because {@link HttpRequest.isInstance} incorrectly
94+
* asserts that IHttpRequest (interface) objects are of type HttpRequest (class).
95+
*/
96+
public clone(): HttpRequest {
97+
return HttpRequest.clone(this);
6098
}
6199
}
62100

0 commit comments

Comments
 (0)