Skip to content

Commit a4c53ac

Browse files
committed
chore(core/protocols): expose v2 JSON codec
1 parent 8c117e3 commit a4c53ac

35 files changed

Lines changed: 776 additions & 231 deletions

packages-internal/core/integ/lazy-json-string.integ.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { requireRequestsFrom } from "@aws-sdk/aws-util-test/src";
22
import { Schemas } from "@aws-sdk/client-schemas";
3-
import { LazyJsonString } from "@smithy/core/serde";
3+
import { LazyJsonString, toUtf8 } from "@smithy/core/serde";
44
import { describe, expect, test as it } from "vitest";
55

66
describe(LazyJsonString.name, () => {
@@ -13,6 +13,7 @@ describe(LazyJsonString.name, () => {
1313

1414
requireRequestsFrom(client).toMatch({
1515
body(b) {
16+
b = toUtf8(b);
1617
if (request === 0) {
1718
expect(b).toEqual(`{"Policy":"this is a plain string"}`);
1819
request += 1;

packages-internal/core/integ/pagination.integ.spec.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { requireRequestsFrom } from "@aws-sdk/aws-util-test/src";
22
import type { ScanCommandInput } from "@aws-sdk/client-dynamodb";
33
import { DynamoDB, paginateScan } from "@aws-sdk/client-dynamodb";
44
import { HttpResponse } from "@smithy/core/protocols";
5+
import { toUtf8 } from "@smithy/core/serde";
56
import { describe, expect, test as it } from "vitest";
67

78
describe("pagination", () => {
@@ -19,6 +20,7 @@ describe("pagination", () => {
1920
{
2021
hostname: /dynamodb/,
2122
body(b) {
23+
b = toUtf8(b);
2224
expect(b).toContain("TableName");
2325
expect(b).not.toContain("ExclusiveStartKey");
2426
},

packages-internal/core/scripts/benchmark-json-serializer.ts

Lines changed: 44 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,14 @@ import type {
2828
StaticListSchema,
2929
} from "@smithy/types";
3030

31-
import { ByteJsonShapeSerializer } from "../src/submodules/protocols/json/codec-v2/ByteJsonShapeSerializer";
32-
import { BufferJsonShapeDeserializer } from "../src/submodules/protocols/json/codec-v2/BufferJsonShapeDeserializer";
33-
import { JsonShapeDeserializer } from "../src/submodules/protocols/json/codec-v1/JsonShapeDeserializer";
34-
import { JsonShapeSerializer } from "../src/submodules/protocols/json/codec-v1/JsonShapeSerializer";
35-
import { AttributeValue$ } from "../../../clients/client-dynamodb/src/schemas/schemas_0";
36-
import { DynamoDBJsonCodec } from "../../../packages-internal/dynamodb-codec/src/index";
31+
import {
32+
JsonShapeSerializer,
33+
JsonShapeDeserializer,
34+
JsonShapeSerializer2,
35+
JsonShapeDeserializer2,
36+
} from "@aws-sdk/core/protocols";
37+
import { AttributeValue$ } from "@aws-sdk/client-dynamodb";
38+
import { DynamoDBJsonCodec, DynamoDBJsonCodec2 } from "@aws-sdk/dynamodb-codec";
3739

3840
// ─── Schemas ─────────────────────────────────────────────────────────────────
3941

@@ -302,8 +304,7 @@ function runVariant(variant: "multipass" | "byte"): BenchResult[] {
302304
timestampFormat: { default: 7 satisfies TimestampEpochSecondsSchema, useTrait: true },
303305
};
304306

305-
const serializer =
306-
variant === "multipass" ? new JsonShapeSerializer(settings) : new ByteJsonShapeSerializer(settings);
307+
const serializer = variant === "multipass" ? new JsonShapeSerializer(settings) : new JsonShapeSerializer2(settings);
307308

308309
const scenarios = getScenarios();
309310
const results: BenchResult[] = [];
@@ -348,7 +349,7 @@ async function runDeserVariant(variant: DeserVariant): Promise<BenchResult[]> {
348349
} as any);
349350

350351
const deserializer =
351-
variant === "deser-original" ? new JsonShapeDeserializer(settings) : new BufferJsonShapeDeserializer(settings);
352+
variant === "deser-original" ? new JsonShapeDeserializer(settings) : new JsonShapeDeserializer2(settings);
352353

353354
const useBytes = variant === "deser-buffer-bytes";
354355
const encoder = new TextEncoder();
@@ -404,11 +405,13 @@ type DdbVariant =
404405
| "ddb-multipass"
405406
| "ddb-byte"
406407
| "ddb-codec-ser"
408+
| "ddb-codec2-ser"
407409
| "ddb-codec-deser"
410+
| "ddb-codec2-deser"
408411
| "ddb-deser-original"
409412
| "ddb-deser-buffer";
410413

411-
function runDdbSerVariant(variant: "ddb-multipass" | "ddb-byte" | "ddb-codec-ser"): BenchResult[] {
414+
function runDdbSerVariant(variant: "ddb-multipass" | "ddb-byte" | "ddb-codec-ser" | "ddb-codec2-ser"): BenchResult[] {
412415
const settings = {
413416
jsonName: true,
414417
timestampFormat: { default: 7 satisfies TimestampEpochSecondsSchema, useTrait: true },
@@ -419,8 +422,12 @@ function runDdbSerVariant(variant: "ddb-multipass" | "ddb-byte" | "ddb-codec-ser
419422
const codec = new DynamoDBJsonCodec();
420423
codec.setSerdeContext({ base64Encoder: (input: Uint8Array) => Buffer.from(input).toString("base64") } as any);
421424
serializer = codec.createSerializer();
425+
} else if (variant === "ddb-codec2-ser") {
426+
const codec = new DynamoDBJsonCodec2();
427+
codec.setSerdeContext({ base64Encoder: (input: Uint8Array) => Buffer.from(input).toString("base64") } as any);
428+
serializer = codec.createSerializer();
422429
} else if (variant === "ddb-byte") {
423-
serializer = new ByteJsonShapeSerializer(settings);
430+
serializer = new JsonShapeSerializer2(settings);
424431
} else {
425432
serializer = new JsonShapeSerializer(settings);
426433
}
@@ -450,7 +457,7 @@ function runDdbSerVariant(variant: "ddb-multipass" | "ddb-byte" | "ddb-codec-ser
450457
}
451458

452459
async function runDdbDeserVariant(
453-
variant: "ddb-deser-original" | "ddb-deser-buffer" | "ddb-codec-deser"
460+
variant: "ddb-deser-original" | "ddb-deser-buffer" | "ddb-codec-deser" | "ddb-codec2-deser"
454461
): Promise<BenchResult[]> {
455462
const settings = {
456463
jsonName: true,
@@ -465,8 +472,12 @@ async function runDdbDeserVariant(
465472
const codec = new DynamoDBJsonCodec();
466473
codec.setSerdeContext({ base64Decoder: (input: string) => Buffer.from(input, "base64") } as any);
467474
deserializer = codec.createDeserializer();
475+
} else if (variant === "ddb-codec2-deser") {
476+
const codec = new DynamoDBJsonCodec2();
477+
codec.setSerdeContext({ base64Decoder: (input: string) => Buffer.from(input, "base64") } as any);
478+
deserializer = codec.createDeserializer();
468479
} else if (variant === "ddb-deser-buffer") {
469-
deserializer = new BufferJsonShapeDeserializer(settings);
480+
deserializer = new JsonShapeDeserializer2(settings);
470481
} else {
471482
deserializer = new JsonShapeDeserializer(settings);
472483
}
@@ -558,7 +569,7 @@ const variantArg = process.argv.find((a) => a.startsWith("--variant="));
558569
if (variantArg) {
559570
// Child process mode: run one variant, output JSON
560571
const variant = variantArg.split("=")[1] as string;
561-
if (variant.startsWith("ddb-deser") || variant === "ddb-codec-deser") {
572+
if (variant.startsWith("ddb-deser") || variant === "ddb-codec-deser" || variant === "ddb-codec2-deser") {
562573
runDdbDeserVariant(variant as any).then((results) => {
563574
process.stdout.write(JSON.stringify(results));
564575
});
@@ -701,35 +712,29 @@ if (variantArg) {
701712
process.stdout.write("Running DDB codec serializer... ");
702713
const ddbCodecSerResults = runChild("ddb-codec-ser");
703714
console.log("done.");
715+
716+
process.stdout.write("Running DDB codec2 serializer... ");
717+
const ddbCodec2SerResults = runChild("ddb-codec2-ser");
718+
console.log("done.");
704719
console.log();
705720

706-
const ddbSerHeader = `${col("Scenario", 20)}${colR("Size", 8)}${colR("Multipass", 12)}${colR("Byte", 12)}${colR("DDB Codec", 12)}${colR("Byte vs MP", 10)}${colR("Codec vs MP", 11)}`;
721+
const ddbSerHeader = `${col("Scenario", 20)}${colR("Size", 8)}${colR("DDB Codec", 12)}${colR("DDB Codec2", 12)}${colR("Codec2 vs Codec", 15)}`;
707722
console.log(ddbSerHeader);
708723
console.log(
709-
"─".repeat(20) +
710-
"─┼─" +
711-
"─".repeat(8) +
712-
"─┼─" +
713-
"─".repeat(12) +
714-
"─┼─" +
715-
"─".repeat(12) +
716-
"─┼─" +
717-
"─".repeat(12) +
718-
"─┼─" +
719-
"─".repeat(10) +
720-
"─┼─" +
721-
"─".repeat(11)
724+
"─".repeat(20) + "─┼─" + "─".repeat(8) + "─┼─" + "─".repeat(12) + "─┼─" + "─".repeat(12) + "─┼─" + "─".repeat(15)
722725
);
723726

724727
for (let i = 0; i < ddbMultipassResults.length; i++) {
725728
const mp = ddbMultipassResults[i];
726729
const bp = ddbByteResults[i];
727730
const codec = ddbCodecSerResults[i];
731+
const codec2 = ddbCodec2SerResults[i];
728732
const size = mp.size < 1024 ? `${mp.size} B` : `${(mp.size / 1024).toFixed(1)} KB`;
729733
const byteVsMp = ((mp.ms - bp.ms) / mp.ms) * 100;
730734
const codecVsMp = ((mp.ms - codec.ms) / mp.ms) * 100;
735+
const codec2VsCodec = ((codec.ms - codec2.ms) / codec.ms) * 100;
731736
console.log(
732-
`${col(mp.name, 20)}${colR(size, 8)}${colR(`${mp.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${bp.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${codec.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${byteVsMp > 0 ? "+" : ""}${byteVsMp.toFixed(1)}%`, 10)}${colR(`${codecVsMp > 0 ? "+" : ""}${codecVsMp.toFixed(1)}%`, 11)}`
737+
`${col(mp.name, 20)}${colR(size, 8)}${colR(`${codec.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${codec2.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${codec2VsCodec > 0 ? "+" : ""}${codec2VsCodec.toFixed(1)}%`, 15)}`
733738
);
734739
}
735740

@@ -750,39 +755,32 @@ if (variantArg) {
750755
process.stdout.write("Running DDB codec deserializer... ");
751756
const ddbCodecDeserResults = runChild("ddb-codec-deser");
752757
console.log("done.");
758+
759+
process.stdout.write("Running DDB codec2 deserializer... ");
760+
const ddbCodec2DeserResults = runChild("ddb-codec2-deser");
761+
console.log("done.");
753762
console.log();
754763

755-
const ddbDeserHeader = `${col("Scenario", 20)}${colR("Size", 8)}${colR("Original", 12)}${colR("Buffer", 12)}${colR("DDB Codec", 12)}${colR("Buf vs Orig", 11)}${colR("Codec vs Orig", 13)}`;
764+
const ddbDeserHeader = `${col("Scenario", 20)}${colR("Size", 8)}${colR("DDB Codec", 12)}${colR("DDB Codec2", 12)}${colR("Codec2 vs Codec", 15)}`;
756765
console.log(ddbDeserHeader);
757766
console.log(
758-
"─".repeat(20) +
759-
"─┼─" +
760-
"─".repeat(8) +
761-
"─┼─" +
762-
"─".repeat(12) +
763-
"─┼─" +
764-
"─".repeat(12) +
765-
"─┼─" +
766-
"─".repeat(12) +
767-
"─┼─" +
768-
"─".repeat(11) +
769-
"─┼─" +
770-
"─".repeat(13)
767+
"─".repeat(20) + "─┼─" + "─".repeat(8) + "─┼─" + "─".repeat(12) + "─┼─" + "─".repeat(12) + "─┼─" + "─".repeat(15)
771768
);
772769

773770
for (let i = 0; i < ddbDeserOrigResults.length; i++) {
774771
const orig = ddbDeserOrigResults[i];
775772
const buf = ddbDeserBufResults[i];
776773
const codec = ddbCodecDeserResults[i];
774+
const codec2 = ddbCodec2DeserResults[i];
777775
const size = orig.size < 1024 ? `${orig.size} B` : `${(orig.size / 1024).toFixed(1)} KB`;
778776
const bufVsOrig = ((orig.ms - buf.ms) / orig.ms) * 100;
779777
const codecVsOrig = ((orig.ms - codec.ms) / orig.ms) * 100;
778+
const codec2VsCodec = ((codec.ms - codec2.ms) / codec.ms) * 100;
780779
console.log(
781-
`${col(orig.name, 20)}${colR(size, 8)}${colR(`${orig.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${buf.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${codec.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${bufVsOrig > 0 ? "+" : ""}${bufVsOrig.toFixed(1)}%`, 11)}${colR(`${codecVsOrig > 0 ? "+" : ""}${codecVsOrig.toFixed(1)}%`, 13)}`
780+
`${col(orig.name, 20)}${colR(size, 8)}${colR(`${codec.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${codec2.kbPerMs.toFixed(1)} kb/ms`, 12)}${colR(`${codec2VsCodec > 0 ? "+" : ""}${codec2VsCodec.toFixed(1)}%`, 15)}`
782781
);
783782
}
784783

785784
console.log();
786-
console.log("Buf vs Orig = BufferJsonShapeDeserializer speed improvement over original (positive = faster)");
787-
console.log("Codec vs Orig = DynamoDBJsonCodec deserializer speed improvement over original (positive = faster)");
785+
console.log("Codec2 vs Codec = DynamoDBJsonCodec2 speed improvement over DynamoDBJsonCodec (positive = faster)");
788786
}

packages-internal/core/src/submodules/protocols/idempotencyToken.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CborShapeSerializer } from "@smithy/core/cbor";
22
import type { StaticSimpleSchema, StaticStructureSchema } from "@smithy/types";
33
import { describe, expect, test as it } from "vitest";
44

5-
import { JsonShapeSerializer } from "./json/codec-v1/JsonShapeSerializer";
5+
import { JsonShapeSerializer2 } from "./json/codec-v2/JsonShapeSerializer2";
66
import { QueryShapeSerializer } from "./query/QueryShapeSerializer";
77
import { XmlShapeSerializer } from "./xml/XmlShapeSerializer";
88

@@ -21,7 +21,7 @@ describe("idempotencyToken", () => {
2121

2222
it("all ShapeSerializer implementations should generate an idempotency token if no input was provided by the caller", () => {
2323
const serializers = [
24-
new JsonShapeSerializer({
24+
new JsonShapeSerializer2({
2525
timestampFormat: { default: 7, useTrait: true },
2626
jsonName: true,
2727
}),
Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,38 @@
1+
// CBOR
12
export { AwsSmithyRpcV2CborProtocol } from "./cbor/AwsSmithyRpcV2CborProtocol";
2-
export { _toStr, _toBool, _toNum } from "./coercing-serializers";
3+
4+
// JSON
35
export { AwsJson1_0Protocol } from "./json/AwsJson1_0Protocol";
46
export { AwsJson1_1Protocol } from "./json/AwsJson1_1Protocol";
57
export { AwsJsonRpcProtocol } from "./json/AwsJsonRpcProtocol";
68
export { AwsRestJsonProtocol } from "./json/AwsRestJsonProtocol";
7-
export { JsonCodec } from "./json/JsonCodec";
8-
export type { JsonSettings } from "./json/JsonCodec";
9+
10+
export { JsonCodec } from "./json/codec-v1/JsonCodec";
11+
export type { JsonSettings } from "./json/JsonSettings";
912
export { JsonShapeDeserializer } from "./json/codec-v1/JsonShapeDeserializer";
1013
export { JsonShapeSerializer } from "./json/codec-v1/JsonShapeSerializer";
11-
export { awsExpectUnion } from "./json/awsExpectUnion";
14+
15+
export { JsonCodec2 } from "./json/codec-v2/JsonCodec2";
16+
export { JsonShapeDeserializer2 } from "./json/codec-v2/JsonShapeDeserializer2";
17+
export { JsonShapeSerializer2 } from "./json/codec-v2/JsonShapeSerializer2";
18+
1219
export { parseJsonBody, parseJsonErrorBody, loadRestJsonErrorCode, loadJsonRpcErrorCode } from "./json/parseJsonBody";
20+
21+
// Query
1322
export { AwsEc2QueryProtocol } from "./query/AwsEc2QueryProtocol";
1423
export { AwsQueryProtocol } from "./query/AwsQueryProtocol";
1524
export type { QuerySerializerSettings } from "./query/QuerySerializerSettings";
1625
export { QueryShapeSerializer } from "./query/QueryShapeSerializer";
26+
27+
// XML
1728
export { AwsRestXmlProtocol } from "./xml/AwsRestXmlProtocol";
1829
export { XmlCodec } from "./xml/XmlCodec";
1930
export type { XmlSettings } from "./xml/XmlCodec";
2031
export { XmlShapeDeserializer } from "./xml/XmlShapeDeserializer";
2132
export { XmlShapeSerializer } from "./xml/XmlShapeSerializer";
33+
2234
export { parseXmlBody, parseXmlErrorBody, loadRestXmlErrorCode } from "./xml/parseXmlBody";
35+
36+
// utilities
37+
export { awsExpectUnion } from "./json/awsExpectUnion";
38+
export { _toStr, _toBool, _toNum } from "./coercing-serializers";

packages-internal/core/src/submodules/protocols/json/AwsJson1_0Protocol.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ describe(AwsJson1_0Protocol.name, () => {
184184
"x-amz-target": "JsonRpc10.MyOperation",
185185
});
186186
expect(request.query).toEqual({});
187-
expect(request.body).toEqual(`{"header":"hello","query":"world","headerMap":{"a":1,"b":2}}`);
187+
expect(toUtf8(request.body)).toEqual(`{"header":"hello","query":"world","headerMap":{"a":1,"b":2}}`);
188188
});
189189
});
190190
});

packages-internal/core/src/submodules/protocols/json/AwsJson1_0Protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { TypeRegistry } from "@smithy/core/schema";
22

33
import { AwsJsonRpcProtocol } from "./AwsJsonRpcProtocol";
4-
import type { JsonCodec } from "./JsonCodec";
4+
import type { JsonCodec } from "./codec-v1/JsonCodec";
55

66
/**
77
* @public

packages-internal/core/src/submodules/protocols/json/AwsJson1_1Protocol.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { describe, expect, test as it } from "vitest";
55

66
import { context, createNestingWidget, deleteObjects, nestingWidget } from "../test-schema.spec";
77
import { AwsJson1_1Protocol } from "./AwsJson1_1Protocol";
8+
import { toUtf8 } from "@smithy/core/serde";
89

910
/**
1011
* These tests are cursory since most coverage is provided by protocol tests.
@@ -80,7 +81,7 @@ describe(AwsJson1_1Protocol.name, () => {
8081
);
8182

8283
expect(httpRequest.method).toEqual("POST");
83-
expect(httpRequest.body).toEqual(
84+
expect(toUtf8(httpRequest.body)).toEqual(
8485
JSON.stringify({
8586
Delete: {
8687
Objects: [

packages-internal/core/src/submodules/protocols/json/AwsJson1_1Protocol.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { TypeRegistry } from "@smithy/core/schema";
22

33
import { AwsJsonRpcProtocol } from "./AwsJsonRpcProtocol";
4-
import type { JsonCodec } from "./JsonCodec";
4+
import type { JsonCodec } from "./codec-v1/JsonCodec";
55

66
/**
77
* @public

packages-internal/core/src/submodules/protocols/json/AwsJsonRpcProtocol.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@ import type {
1515
} from "@smithy/types";
1616

1717
import { ProtocolLib } from "../ProtocolLib";
18-
import { JsonCodec } from "./JsonCodec";
18+
import { JsonCodec } from "./codec-v1/JsonCodec";
1919
import { loadJsonRpcErrorCode } from "./parseJsonBody";
20+
import type { JsonShapeDeserializer } from "./codec-v1/JsonShapeDeserializer";
2021

2122
/**
2223
* @public
@@ -85,7 +86,7 @@ export abstract class AwsJsonRpcProtocol extends RpcProtocol {
8586
return request;
8687
}
8788

88-
public getPayloadCodec(): JsonCodec {
89+
public getPayloadCodec() {
8990
return this.codec;
9091
}
9192

@@ -123,7 +124,7 @@ export abstract class AwsJsonRpcProtocol extends RpcProtocol {
123124
const exception = new ErrorCtor({});
124125

125126
const output = {} as any;
126-
const errorDeserializer = this.codec.createDeserializer();
127+
const errorDeserializer = this.codec.createDeserializer() as JsonShapeDeserializer;
127128
for (const [name, member] of ns.structIterator()) {
128129
if (dataObject[name] != null) {
129130
output[name] = errorDeserializer.readObject(member, dataObject[name]);

0 commit comments

Comments
 (0)