Skip to content

Commit 9babfac

Browse files
authored
feat(core/protocols): runtime protocol classes (#7110)
1 parent 962d8d5 commit 9babfac

38 files changed

+3038
-5
lines changed

packages/core/package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,18 @@
8282
"license": "Apache-2.0",
8383
"dependencies": {
8484
"@aws-sdk/types": "*",
85+
"@aws-sdk/xml-builder": "*",
8586
"@smithy/core": "^3.5.1",
8687
"@smithy/node-config-provider": "^4.1.3",
8788
"@smithy/property-provider": "^4.0.4",
8889
"@smithy/protocol-http": "^5.1.2",
8990
"@smithy/signature-v4": "^5.1.2",
9091
"@smithy/smithy-client": "^4.4.1",
9192
"@smithy/types": "^4.3.1",
93+
"@smithy/util-base64": "^4.0.0",
94+
"@smithy/util-body-length-browser": "^4.0.0",
9295
"@smithy/util-middleware": "^4.0.4",
96+
"@smithy/util-utf8": "^4.0.0",
9397
"fast-xml-parser": "4.4.1",
9498
"tslib": "^2.6.2"
9599
},
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { ConfigurableSerdeContext, SerdeFunctions } from "@smithy/types";
2+
3+
/**
4+
* @internal
5+
*/
6+
export class SerdeContextConfig implements ConfigurableSerdeContext {
7+
protected serdeContext?: SerdeFunctions;
8+
9+
public setSerdeContext(serdeContext: SerdeFunctions): void {
10+
this.serdeContext = serdeContext;
11+
}
12+
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { collectBody } from "@smithy/smithy-client";
2-
import type { HttpResponse, SerdeContext } from "@smithy/types";
2+
import type { SerdeFunctions } from "@smithy/types";
33

4-
export const collectBodyString = (streamBody: any, context: SerdeContext): Promise<string> =>
4+
export const collectBodyString = (streamBody: any, context: SerdeFunctions): Promise<string> =>
55
collectBody(streamBody, context).then((body) => context.utf8Encoder(body));
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
11
export * from "./coercing-serializers";
2+
export * from "./json/AwsJson1_0Protocol";
3+
export * from "./json/AwsJson1_1Protocol";
4+
export * from "./json/AwsJsonRpcProtocol";
5+
export * from "./json/AwsRestJsonProtocol";
6+
export * from "./json/JsonCodec";
7+
export * from "./json/JsonShapeDeserializer";
8+
export * from "./json/JsonShapeSerializer";
29
export * from "./json/awsExpectUnion";
310
export * from "./json/parseJsonBody";
11+
export * from "./query/AwsEc2QueryProtocol";
12+
export * from "./query/AwsQueryProtocol";
13+
export * from "./xml/AwsRestXmlProtocol";
14+
export * from "./xml/XmlCodec";
15+
export * from "./xml/XmlShapeDeserializer";
16+
export * from "./xml/XmlShapeSerializer";
417
export * from "./xml/parseXmlBody";
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { map, op, SCHEMA, sim, struct } from "@smithy/core/schema";
2+
import { toBase64 } from "@smithy/util-base64";
3+
import { toUtf8 } from "@smithy/util-utf8";
4+
import { describe, expect, test as it } from "vitest";
5+
6+
import { context } from "../test-schema.spec";
7+
import { AwsJson1_0Protocol } from "./AwsJson1_0Protocol";
8+
9+
describe(AwsJson1_0Protocol.name, () => {
10+
const json = {
11+
string: "string",
12+
number: 1234,
13+
boolean: false,
14+
blob: "AAAAAAAAAAA=",
15+
timestamp: 0,
16+
};
17+
const schema = struct(
18+
"ns",
19+
"MyStruct",
20+
0,
21+
[...Object.keys(json)],
22+
[SCHEMA.STRING, SCHEMA.NUMERIC, SCHEMA.BOOLEAN, SCHEMA.BLOB, SCHEMA.TIMESTAMP_DEFAULT]
23+
);
24+
const serdeContext = {
25+
base64Encoder: toBase64,
26+
utf8Encoder: toUtf8,
27+
} as any;
28+
29+
describe("codec", () => {
30+
it("serializes blobs and timestamps", () => {
31+
const protocol = new AwsJson1_0Protocol({
32+
defaultNamespace: "namespace",
33+
});
34+
protocol.setSerdeContext(serdeContext);
35+
const codec = protocol.getPayloadCodec();
36+
const serializer = codec.createSerializer();
37+
const data = {
38+
string: "string",
39+
number: 1234,
40+
boolean: false,
41+
blob: new Uint8Array(8),
42+
timestamp: new Date(0),
43+
};
44+
serializer.write(schema, data);
45+
const serialized = serializer.flush();
46+
expect(JSON.parse(serialized)).toEqual({
47+
string: "string",
48+
number: 1234,
49+
boolean: false,
50+
blob: "AAAAAAAAAAA=",
51+
timestamp: 0,
52+
});
53+
});
54+
55+
it("deserializes blobs and timestamps", async () => {
56+
const protocol = new AwsJson1_0Protocol({
57+
defaultNamespace: "namespace",
58+
});
59+
protocol.setSerdeContext(serdeContext);
60+
const codec = protocol.getPayloadCodec();
61+
const deserializer = codec.createDeserializer();
62+
63+
const parsed = await deserializer.read(schema, JSON.stringify(json));
64+
expect(parsed).toEqual({
65+
string: "string",
66+
number: 1234,
67+
boolean: false,
68+
blob: new Uint8Array(8),
69+
timestamp: new Date(0),
70+
});
71+
});
72+
73+
it("ignores JSON name and HTTP bindings", async () => {
74+
const protocol = new AwsJson1_0Protocol({
75+
defaultNamespace: "namespace",
76+
});
77+
protocol.setSerdeContext(serdeContext);
78+
79+
const schema = struct(
80+
"ns",
81+
"MyHttpBindingStructure",
82+
{},
83+
["header", "query", "headerMap", "payload"],
84+
[
85+
sim("ns", "MyHeader", SCHEMA.STRING, { httpHeader: "header", jsonName: "MyHeader" }),
86+
sim("ns", "MyQuery", SCHEMA.STRING, { httpQuery: "query" }),
87+
map(
88+
"ns",
89+
"HeaderMap",
90+
{
91+
httpPrefixHeaders: "",
92+
},
93+
SCHEMA.STRING,
94+
SCHEMA.NUMERIC
95+
),
96+
sim("ns", "MyPayload", SCHEMA.DOCUMENT, { httpPayload: 1 }),
97+
]
98+
);
99+
const operationSchema = op("ns", "MyOperation", {}, schema, "unit");
100+
101+
const request = await protocol.serializeRequest(
102+
operationSchema,
103+
{
104+
header: "hello",
105+
query: "world",
106+
headerMap: {
107+
a: 1,
108+
b: 2,
109+
},
110+
},
111+
context
112+
);
113+
114+
expect(request.headers).toEqual({
115+
"content-length": "60",
116+
"content-type": "application/x-amz-json-1.0",
117+
"x-amz-target": "JsonRpc10.MyOperation",
118+
});
119+
expect(request.query).toEqual({});
120+
expect(request.body).toEqual(`{"header":"hello","query":"world","headerMap":{"a":1,"b":2}}`);
121+
});
122+
});
123+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { AwsJsonRpcProtocol } from "./AwsJsonRpcProtocol";
2+
3+
/**
4+
* @alpha
5+
* @see https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#differences-between-awsjson1-0-and-awsjson1-1
6+
*/
7+
export class AwsJson1_0Protocol extends AwsJsonRpcProtocol {
8+
public constructor({ defaultNamespace }: { defaultNamespace: string }) {
9+
super({
10+
defaultNamespace,
11+
});
12+
}
13+
14+
public getShapeId(): string {
15+
return "aws.protocols#awsJson1_0";
16+
}
17+
18+
protected getJsonRpcVersion() {
19+
return "1.0" as const;
20+
}
21+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { HttpResponse } from "@smithy/protocol-http";
2+
import { describe, expect, test as it } from "vitest";
3+
4+
import { context, deleteObjects } from "../test-schema.spec";
5+
import { AwsJson1_0Protocol } from "./AwsJson1_0Protocol";
6+
7+
/**
8+
* These tests are cursory since most coverage is provided by protocol tests.
9+
*/
10+
describe(AwsJson1_0Protocol, () => {
11+
it("is 1.0", async () => {
12+
const protocol = new AwsJson1_0Protocol({
13+
defaultNamespace: "",
14+
});
15+
expect(protocol.getShapeId()).toEqual("aws.protocols#awsJson1_0");
16+
});
17+
18+
it("serializes a request", async () => {
19+
const protocol = new AwsJson1_0Protocol({
20+
defaultNamespace: "",
21+
});
22+
const httpRequest = await protocol.serializeRequest(
23+
deleteObjects,
24+
{
25+
Delete: {
26+
Objects: [
27+
{
28+
Key: "key1",
29+
},
30+
{
31+
Key: "key2",
32+
},
33+
],
34+
},
35+
},
36+
context
37+
);
38+
39+
expect(httpRequest.method).toEqual("POST");
40+
expect(httpRequest.body).toEqual(
41+
JSON.stringify({
42+
Delete: {
43+
Objects: [
44+
{
45+
Key: "key1",
46+
},
47+
{
48+
Key: "key2",
49+
},
50+
],
51+
},
52+
})
53+
);
54+
});
55+
56+
it("deserializes a response", async () => {
57+
const httpResponse = new HttpResponse({
58+
statusCode: 200,
59+
headers: {},
60+
});
61+
62+
const protocol = new AwsJson1_0Protocol({
63+
defaultNamespace: "",
64+
});
65+
66+
const output = await protocol.deserializeResponse(deleteObjects, context, httpResponse);
67+
68+
expect(output).toEqual({
69+
$metadata: {
70+
httpStatusCode: 200,
71+
requestId: undefined,
72+
extendedRequestId: undefined,
73+
cfId: undefined,
74+
},
75+
});
76+
});
77+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { AwsJsonRpcProtocol } from "./AwsJsonRpcProtocol";
2+
3+
/**
4+
* @alpha
5+
* @see https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#differences-between-awsjson1-0-and-awsjson1-1
6+
*/
7+
export class AwsJson1_1Protocol extends AwsJsonRpcProtocol {
8+
public constructor({ defaultNamespace }: { defaultNamespace: string }) {
9+
super({
10+
defaultNamespace,
11+
});
12+
}
13+
14+
public getShapeId(): string {
15+
return "aws.protocols#awsJson1_1";
16+
}
17+
18+
protected getJsonRpcVersion() {
19+
return "1.1" as const;
20+
}
21+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { SCHEMA } from "@smithy/core/schema";
2+
import { describe, expect, test as it } from "vitest";
3+
4+
import { AwsJsonRpcProtocol } from "./AwsJsonRpcProtocol";
5+
6+
describe(AwsJsonRpcProtocol.name, () => {
7+
it("has expected codec settings", async () => {
8+
const protocol = new (class extends AwsJsonRpcProtocol {
9+
constructor() {
10+
super({ defaultNamespace: "" });
11+
}
12+
13+
getShapeId(): string {
14+
throw new Error("Method not implemented.");
15+
}
16+
17+
protected getJsonRpcVersion(): "1.1" | "1.0" {
18+
throw new Error("Method not implemented.");
19+
}
20+
})();
21+
22+
const codec = protocol.getPayloadCodec();
23+
expect(codec.settings).toEqual({
24+
jsonName: false,
25+
timestampFormat: {
26+
default: SCHEMA.TIMESTAMP_EPOCH_SECONDS,
27+
useTrait: true,
28+
},
29+
});
30+
});
31+
});

0 commit comments

Comments
 (0)