Skip to content

Commit c92969a

Browse files
committed
chore(core/protocols): internalize cbor serializer code
1 parent 53fd9b4 commit c92969a

File tree

8 files changed

+101
-109
lines changed

8 files changed

+101
-109
lines changed

.changeset/ten-taxis-deliver.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@smithy/types": patch
3+
"@smithy/core": patch
4+
---
5+
6+
schema serde: http binding and cbor serializer refactoring

packages/core/src/submodules/cbor/CborCodec.ts

Lines changed: 70 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { NormalizedSchema } from "@smithy/core/schema";
2-
import { copyDocumentWithTransform, parseEpochTimestamp } from "@smithy/core/serde";
3-
import { Codec, Schema, SchemaRef, SerdeFunctions, ShapeDeserializer, ShapeSerializer } from "@smithy/types";
2+
import { parseEpochTimestamp } from "@smithy/core/serde";
3+
import { Codec, Schema, SerdeFunctions, ShapeDeserializer, ShapeSerializer } from "@smithy/types";
44

55
import { cbor } from "./cbor";
66
import { dateToTag } from "./parseCborBody";
@@ -40,38 +40,80 @@ export class CborShapeSerializer implements ShapeSerializer {
4040
}
4141

4242
public write(schema: Schema, value: unknown): void {
43-
this.value = copyDocumentWithTransform(value, schema, (_: any, schemaRef: SchemaRef) => {
44-
if (_ instanceof Date) {
45-
return dateToTag(_);
46-
}
47-
if (_ instanceof Uint8Array) {
48-
return _;
49-
}
43+
this.value = this.serialize(schema, value);
44+
}
5045

51-
const ns = NormalizedSchema.of(schemaRef);
52-
const sparse = !!ns.getMergedTraits().sparse;
46+
/**
47+
* Recursive serializer transform that copies and prepares the user input object
48+
* for CBOR serialization.
49+
*/
50+
public serialize(schema: Schema, source: unknown): any {
51+
const ns = NormalizedSchema.of(schema);
5352

54-
if (ns.isListSchema() && Array.isArray(_)) {
55-
if (!sparse) {
56-
return _.filter((item) => item != null);
53+
switch (typeof source) {
54+
case "undefined":
55+
return null;
56+
case "boolean":
57+
case "number":
58+
case "string":
59+
case "bigint":
60+
case "symbol":
61+
return source;
62+
case "function":
63+
case "object":
64+
if (source === null) {
65+
return null;
5766
}
58-
} else if (_ && typeof _ === "object") {
59-
const members = ns.getMemberSchemas();
60-
const isStruct = ns.isStructSchema();
61-
if (!sparse || isStruct) {
62-
for (const [k, v] of Object.entries(_)) {
63-
const filteredOutByNonSparse = !sparse && v == null;
64-
const filteredOutByUnrecognizedMember = isStruct && !(k in members);
65-
if (filteredOutByNonSparse || filteredOutByUnrecognizedMember) {
66-
delete _[k];
67+
68+
const sourceObject = source as Record<string, unknown>;
69+
const sparse = !!ns.getMergedTraits().sparse;
70+
71+
if (ns.isListSchema() && Array.isArray(sourceObject)) {
72+
const newArray = [];
73+
let i = 0;
74+
for (const item of sourceObject) {
75+
const value = this.serialize(ns.getValueSchema(), item);
76+
if (value != null || sparse) {
77+
newArray[i++] = value;
6778
}
6879
}
69-
return _;
80+
return newArray;
7081
}
71-
}
72-
73-
return _;
74-
});
82+
if (sourceObject instanceof Uint8Array) {
83+
const newBytes = new Uint8Array(sourceObject.byteLength);
84+
newBytes.set(sourceObject, 0);
85+
return newBytes;
86+
}
87+
if (sourceObject instanceof Date) {
88+
return dateToTag(sourceObject);
89+
}
90+
const newObject = {} as any;
91+
if (ns.isMapSchema()) {
92+
for (const key of Object.keys(sourceObject)) {
93+
const value = this.serialize(ns.getValueSchema(), sourceObject[key]);
94+
if (value != null || sparse) {
95+
newObject[key] = value;
96+
}
97+
}
98+
} else if (ns.isStructSchema()) {
99+
for (const [key, memberSchema] of ns.structIterator()) {
100+
const value = this.serialize(memberSchema, sourceObject[key]);
101+
if (value != null) {
102+
newObject[key] = value;
103+
}
104+
}
105+
} else if (ns.isDocumentSchema()) {
106+
for (const key of Object.keys(sourceObject)) {
107+
const value = this.serialize(ns.getValueSchema(), sourceObject[key]);
108+
if (value != null) {
109+
newObject[key] = value;
110+
}
111+
}
112+
}
113+
return newObject;
114+
default:
115+
return source;
116+
}
75117
}
76118

77119
public flush(): Uint8Array {

packages/core/src/submodules/cbor/SmithyRpcV2CborProtocol.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ describe(SmithyRpcV2CborProtocol.name, () => {
103103
0,
104104
["mySparseList", "myRegularList", "mySparseMap", "myRegularMap"],
105105
[
106-
[() => list("", "MyList", { sparse: 1 }, SCHEMA.NUMERIC), {}],
106+
[() => list("", "MySparseList", { sparse: 1 }, SCHEMA.NUMERIC), {}],
107107
[() => list("", "MyList", {}, SCHEMA.NUMERIC), {}],
108-
[() => map("", "MyMap", { sparse: 1 }, SCHEMA.STRING, SCHEMA.NUMERIC), {}],
108+
[() => map("", "MySparseMap", { sparse: 1 }, SCHEMA.STRING, SCHEMA.NUMERIC), {}],
109109
[() => map("", "MyMap", {}, SCHEMA.STRING, SCHEMA.NUMERIC), {}],
110110
]
111111
),

packages/core/src/submodules/protocols/HttpBindingProtocol.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,9 @@ export abstract class HttpBindingProtocol extends HttpProtocol {
293293
[unionMember]: await deserializer.read(eventStreamSchema, event[unionMember].body),
294294
};
295295
} else {
296-
// this union convention is ignored by the event stream marshaller.
296+
// todo(schema): This union convention is ignored by the event stream marshaller.
297+
// todo(schema): This should be returned to the user instead.
298+
// see "if (deserialized.$unknown) return;" in getUnmarshalledStream.ts
297299
return {
298300
$unknown: event,
299301
};

packages/core/src/submodules/protocols/HttpProtocol.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { NormalizedSchema, SCHEMA } from "@smithy/core/schema";
2-
import { splitEvery, splitHeader } from "@smithy/core/serde";
1+
import { NormalizedSchema } from "@smithy/core/schema";
32
import { HttpRequest, HttpResponse } from "@smithy/protocol-http";
43
import {
54
ClientProtocol,
65
Codec,
76
Endpoint,
87
EndpointBearer,
98
EndpointV2,
10-
EventStreamSerdeContext,
119
HandlerExecutionContext,
1210
HttpRequest as IHttpRequest,
1311
HttpResponse as IHttpResponse,
@@ -19,9 +17,6 @@ import {
1917
ShapeDeserializer,
2018
ShapeSerializer,
2119
} from "@smithy/types";
22-
import { sdkStreamMixin } from "@smithy/util-stream";
23-
24-
import { collectBody } from "./collect-stream-body";
2520

2621
/**
2722
* Abstract base for HTTP-based client protocols.
Lines changed: 2 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,11 @@
1-
import { NormalizedSchema } from "@smithy/core/schema";
21
import { SchemaRef } from "@smithy/types";
32

43
/**
54
* @internal
5+
* @deprecated the former functionality has been internalized to the CborCodec.
66
*/
77
export const copyDocumentWithTransform = (
88
source: any,
99
schemaRef: SchemaRef,
1010
transform: (_: any, schemaRef: SchemaRef) => any = (_) => _
11-
): any => {
12-
const ns = NormalizedSchema.of(schemaRef);
13-
switch (typeof source) {
14-
case "undefined":
15-
case "boolean":
16-
case "number":
17-
case "string":
18-
case "bigint":
19-
case "symbol":
20-
return transform(source, ns);
21-
case "function":
22-
case "object":
23-
if (source === null) {
24-
return transform(null, ns);
25-
}
26-
if (Array.isArray(source)) {
27-
const newArray = new Array(source.length);
28-
let i = 0;
29-
for (const item of source) {
30-
newArray[i++] = copyDocumentWithTransform(item, ns.getValueSchema(), transform);
31-
}
32-
return transform(newArray, ns);
33-
}
34-
if ("byteLength" in (source as Uint8Array)) {
35-
const newBytes = new Uint8Array(source.byteLength);
36-
newBytes.set(source, 0);
37-
return transform(newBytes, ns);
38-
}
39-
if (source instanceof Date) {
40-
return transform(source, ns);
41-
}
42-
const newObject = {} as any;
43-
if (ns.isMapSchema()) {
44-
for (const key of Object.keys(source)) {
45-
newObject[key] = copyDocumentWithTransform(source[key], ns.getValueSchema(), transform);
46-
}
47-
} else if (ns.isStructSchema()) {
48-
for (const [key, memberSchema] of ns.structIterator()) {
49-
newObject[key] = copyDocumentWithTransform(source[key], memberSchema, transform);
50-
}
51-
} else if (ns.isDocumentSchema()) {
52-
for (const key of Object.keys(source)) {
53-
newObject[key] = copyDocumentWithTransform(source[key], ns.getValueSchema(), transform);
54-
}
55-
}
56-
57-
return transform(newObject, ns);
58-
default:
59-
return transform(source, ns);
60-
}
61-
};
11+
): any => source;

packages/types/src/schema/schema.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ export type SchemaTraitsObject = {
142142
mediaType?: string;
143143
error?: "client" | "server";
144144

145+
streaming?: 1;
145146
[traitName: string]: unknown;
146147
};
147148

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/schema/SchemaTraitFilterIndex.java

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -60,45 +60,41 @@ final class SchemaTraitFilterIndex implements KnowledgeIndex {
6060
private final Set<ShapeId> includedTraits = new HashSet<>(
6161
// (wrapped for mutability)
6262
SetUtils.of(
63-
SparseTrait.ID,
63+
SparseTrait.ID, // Shape serde
6464
// todo(schema) needs schema logger implementation
6565
SensitiveTrait.ID,
6666
// todo(schema) needs automatic generation by protocol serializer
6767
IdempotencyTokenTrait.ID,
68-
JsonNameTrait.ID,
69-
MediaTypeTrait.ID,
70-
XmlAttributeTrait.ID,
71-
XmlFlattenedTrait.ID,
72-
XmlNameTrait.ID,
73-
XmlNamespaceTrait.ID,
68+
JsonNameTrait.ID, // Shape serde
69+
MediaTypeTrait.ID, // JSON shape serde
70+
XmlAttributeTrait.ID, // XML shape serde
71+
XmlFlattenedTrait.ID, // XML shape serde
72+
XmlNameTrait.ID, // XML shape serde
73+
XmlNamespaceTrait.ID, // XML shape serde
74+
StreamingTrait.ID, // HttpBindingProtocol handles streaming + payload members.
75+
EndpointTrait.ID, // HttpProtocol
76+
ErrorTrait.ID, // set by the ServiceException runtime classes.
77+
RequiresLengthTrait.ID, // unhandled
78+
7479
// todo(schema)
7580
EventHeaderTrait.ID,
7681
// todo(schema)
7782
EventPayloadTrait.ID,
78-
StreamingTrait.ID,
79-
RequiresLengthTrait.ID, // unhandled
80-
EndpointTrait.ID,
83+
8184
// afaict, HttpErrorTrait is ignored by the client. The discriminator selects the error structure
8285
// but the actual HTTP response status code is used with no particular comparison
8386
// with the trait's error code.
8487
HttpErrorTrait.ID,
85-
// handled by HTTP binding protocol base class.
88+
// the following HTTP traits are handled by HTTP binding protocol base class.
89+
HttpTrait.ID,
8690
HttpHeaderTrait.ID,
87-
// handled by HTTP binding protocol base class.
8891
HttpQueryTrait.ID,
89-
// handled by HTTP binding protocol base class.
9092
HttpLabelTrait.ID,
91-
// handled by HTTP binding protocol base class.
9293
HttpPayloadTrait.ID,
93-
// handled by HTTP binding protocol base class.
9494
HttpPrefixHeadersTrait.ID,
95-
// handled by HTTP binding protocol base class.
9695
HttpQueryParamsTrait.ID,
97-
// handled by HTTP binding protocol base class.
9896
HttpResponseCodeTrait.ID,
99-
HostLabelTrait.ID,
100-
ErrorTrait.ID,
101-
HttpTrait.ID
97+
HostLabelTrait.ID
10298
)
10399
);
104100
private final Map<Shape, Boolean> cache = new HashMap<>();

0 commit comments

Comments
 (0)