Skip to content

Commit f9ab64e

Browse files
committed
rework schemas
1 parent b1a901e commit f9ab64e

26 files changed

+1056
-664
lines changed

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

+36-39
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,6 @@
1-
import { deref, ListSchema, MapSchema, StructureSchema } from "@smithy/core/schema";
1+
import { NormalizedSchema } from "@smithy/core/schema";
22
import { copyDocumentWithTransform, parseEpochTimestamp } from "@smithy/core/serde";
3-
import {
4-
Codec,
5-
MemberSchema,
6-
Schema,
7-
SchemaRef,
8-
SerdeContext,
9-
ShapeDeserializer,
10-
ShapeSerializer,
11-
TraitsSchema,
12-
} from "@smithy/types";
3+
import { Codec, Schema, SchemaRef, SerdeContext, ShapeDeserializer, ShapeSerializer } from "@smithy/types";
134

145
import { cbor } from "./cbor";
156
import { dateToTag } from "./parseCborBody";
@@ -46,17 +37,19 @@ export class CborShapeSerializer implements ShapeSerializer<Uint8Array> {
4637
if (_ instanceof Date) {
4738
return dateToTag(_);
4839
}
49-
const schema = deref((schemaRef as MemberSchema)?.[0] ?? schemaRef);
5040
if (_ instanceof Uint8Array) {
5141
return _;
5242
}
53-
const sparse = (schema as TraitsSchema)?.traits?.sparse;
43+
44+
const ns = NormalizedSchema.of(schemaRef);
45+
const sparse = !!ns.getMergedTraits().sparse;
46+
5447
if (Array.isArray(_)) {
5548
if (!sparse) {
5649
return _.filter((item) => item != null);
5750
}
5851
} else if (_ && typeof _ === "object") {
59-
if (!sparse) {
52+
if (!sparse || ns.isStructSchema()) {
6053
for (const [k, v] of Object.entries(_)) {
6154
if (v == null) {
6255
delete _[k];
@@ -65,6 +58,7 @@ export class CborShapeSerializer implements ShapeSerializer<Uint8Array> {
6558
return _;
6659
}
6760
}
61+
6862
return _;
6963
});
7064
}
@@ -88,15 +82,20 @@ export class CborShapeDeserializer implements ShapeDeserializer {
8882
return this.readValue(schema, data);
8983
}
9084

91-
private readValue(schema: Schema, value: any): any {
92-
if (typeof schema === "string") {
93-
if (schema === "time" || schema === "epoch-seconds" || schema === "date-time") {
85+
private readValue(_schema: Schema, value: any): any {
86+
const ns = NormalizedSchema.of(_schema);
87+
const schema = ns.getSchema();
88+
89+
if (typeof schema === "number") {
90+
if (ns.isTimestampSchema()) {
91+
// format is ignored.
9492
return parseEpochTimestamp(value);
9593
}
96-
if (schema === "blob" || schema === "streaming-blob") {
94+
if (ns.isBlobSchema()) {
9795
return value;
9896
}
9997
}
98+
10099
switch (typeof value) {
101100
case "undefined":
102101
case "boolean":
@@ -116,38 +115,36 @@ export class CborShapeDeserializer implements ShapeDeserializer {
116115
if (value instanceof Date) {
117116
return value;
118117
}
119-
const traits =
120-
Array.isArray(schema) && schema.length >= 2
121-
? {
122-
...(deref((schema as MemberSchema)[0]) as TraitsSchema)?.traits,
123-
...(schema as MemberSchema)[1],
124-
}
125-
: (deref(schema) as TraitsSchema)?.traits;
126118

127119
if (Array.isArray(value)) {
128120
const newArray = [];
121+
const memberSchema = ns.getValueSchema();
122+
const sparse = ns.isListSchema() && !!ns.getMergedTraits().sparse;
123+
129124
for (const item of value) {
130-
newArray.push(this.readValue(schema instanceof ListSchema ? deref(schema.valueSchema) : void 0, item));
131-
if (!traits?.sparse) {
132-
if (newArray[newArray.length - 1] == null) {
133-
newArray.pop();
134-
}
125+
newArray.push(this.readValue(memberSchema, item));
126+
if (!sparse && newArray[newArray.length - 1] == null) {
127+
newArray.pop();
135128
}
136129
}
137130
return newArray;
138131
}
139132

140133
const newObject = {} as any;
141134
for (const key of Object.keys(value)) {
142-
const targetSchema =
143-
schema instanceof StructureSchema
144-
? deref(schema.members[key]?.[0])
145-
: schema instanceof MapSchema
146-
? deref(schema.valueSchema)
147-
: void 0;
148-
newObject[key] = this.readValue(targetSchema, value[key]);
149-
if (!traits?.sparse && newObject[key] == null) {
150-
delete newObject[key];
135+
if (ns.isMapSchema() || ns.isDocumentSchema()) {
136+
const targetSchema = ns.getValueSchema();
137+
newObject[key] = this.readValue(targetSchema, value[key]);
138+
139+
if (ns.isMapSchema() && newObject[key] == null && !ns.getMergedTraits().sparse) {
140+
delete newObject[key];
141+
}
142+
} else if (ns.isStructSchema()) {
143+
const targetSchema = ns.getMemberSchema(key);
144+
if (targetSchema === undefined) {
145+
continue;
146+
}
147+
newObject[key] = this.readValue(targetSchema, value[key]);
151148
}
152149
}
153150
return newObject;

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ describe(SmithyRpcV2CborProtocol.name, () => {
154154
const protocol = new SmithyRpcV2CborProtocol();
155155
const httpRequest = await protocol.serializeRequest(
156156
{
157+
name: "dummy",
157158
input: testCase.schema,
158159
output: void 0,
159160
traits: {},
160-
errors: [],
161161
},
162162
testCase.input,
163163
{
@@ -249,10 +249,10 @@ describe(SmithyRpcV2CborProtocol.name, () => {
249249
const protocol = new SmithyRpcV2CborProtocol();
250250
const output = await protocol.deserializeResponse(
251251
{
252+
name: "dummy",
252253
input: void 0,
253254
output: testCase.schema,
254255
traits: {},
255-
errors: [],
256256
},
257257
{},
258258
new HttpResponse({

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { HttpInterceptingShapeSerializer,HttpProtocol } from "@smithy/core/protocols";
1+
import { HttpInterceptingShapeSerializer, HttpProtocol } from "@smithy/core/protocols";
22
import { deref, ErrorSchema, OperationSchema, TypeRegistry } from "@smithy/core/schema";
33
import type {
44
HandlerExecutionContext,
@@ -36,6 +36,10 @@ export class SmithyRpcV2CborProtocol extends HttpProtocol {
3636
delete request.body;
3737
delete request.headers["content-type"];
3838
} else {
39+
if (!request.body) {
40+
this.serializer.write(15, {})
41+
request.body = this.serializer.flush();
42+
}
3943
try {
4044
request.headers["content-length"] = String((request.body as Uint8Array).byteLength);
4145
} catch (e) {}
@@ -82,6 +86,7 @@ export class SmithyRpcV2CborProtocol extends HttpProtocol {
8286
// TODO(schema) throw client base exception using the dataObject.
8387
throw new Error("schema not found for " + error);
8488
}
89+
8590
const message = dataObject.message ?? dataObject.Message ?? "Unknown";
8691
const exception = new errorSchema.ctor(message);
8792
Object.assign(exception, {

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

+22-9
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,10 @@ export abstract class HttpProtocol implements Protocol<IHttpRequest, IHttpRespon
7676
...input,
7777
};
7878

79-
if (operationSchema.traits.http) {
80-
request.method = operationSchema.traits.http[0];
81-
const [path, search] = operationSchema.traits.http[1].split("?");
79+
const opTraits = NormalizedSchema.translateTraits(operationSchema.traits);
80+
if (opTraits.http) {
81+
request.method = opTraits.http[0];
82+
const [path, search] = opTraits.http[1].split("?");
8283

8384
request.path = path;
8485
const traitSearchParams = new URLSearchParams(search ?? "");
@@ -87,6 +88,9 @@ export abstract class HttpProtocol implements Protocol<IHttpRequest, IHttpRespon
8788

8889
for (const memberName of Object.keys(_input)) {
8990
const memberNs = ns.getMemberSchema(memberName);
91+
if (memberNs === undefined) {
92+
continue;
93+
}
9094
const memberSchema = memberNs.getSchema();
9195
const memberTraits = memberNs.getMergedTraits();
9296
const inputMember = (_input as any)[memberName] as any;
@@ -122,13 +126,13 @@ export abstract class HttpProtocol implements Protocol<IHttpRequest, IHttpRespon
122126
} else if (memberTraits.httpPrefixHeaders) {
123127
for (const [key, val] of Object.entries(inputMember)) {
124128
const amalgam = memberTraits.httpPrefixHeaders + key;
125-
serializer.write([, { httpHeader: amalgam }], val);
129+
serializer.write([0, { httpHeader: amalgam }], val);
126130
headers[amalgam.toLowerCase()] = String(serializer.flush()).toLowerCase();
127131
}
128132
delete _input[memberName];
129133
} else if (memberTraits.httpQueryParams) {
130134
for (const [key, val] of Object.entries(inputMember)) {
131-
serializer.write([, { httpQuery: key }], val);
135+
serializer.write([0, { httpQuery: key }], val);
132136
query[key] = serializer.flush() as string;
133137
}
134138
delete _input[memberName];
@@ -178,6 +182,16 @@ export abstract class HttpProtocol implements Protocol<IHttpRequest, IHttpRespon
178182
const schema = ns.getSchema() as StructureSchema;
179183

180184
let dataObject: any = {};
185+
186+
if (response.statusCode >= 300) {
187+
const bytes: Uint8Array = await collectBody(response.body, context as SerdeContext);
188+
if (bytes.byteLength > 0) {
189+
Object.assign(dataObject, await deserializer.read(15, bytes));
190+
}
191+
await this.handleError(operationSchema, context, response, dataObject, this.deserializeMetadata(response));
192+
throw new Error("@smithy/core/protocols - HTTP Protocol error handler failed to throw.");
193+
}
194+
181195
let hasNonHttpBindingMember = false;
182196

183197
for (const header in response.headers) {
@@ -188,6 +202,9 @@ export abstract class HttpProtocol implements Protocol<IHttpRequest, IHttpRespon
188202

189203
for (const [memberName] of Object.entries(schema?.members ?? {})) {
190204
const memberSchema = ns.getMemberSchema(memberName);
205+
if (memberSchema === undefined) {
206+
continue;
207+
}
191208
const memberSchemas = memberSchema.getMemberSchemas();
192209
const memberTraits = memberSchema.getMemberTraits();
193210

@@ -261,10 +278,6 @@ export abstract class HttpProtocol implements Protocol<IHttpRequest, IHttpRespon
261278
}
262279
}
263280

264-
if (response.statusCode >= 300) {
265-
await this.handleError(operationSchema, context, response, dataObject, this.deserializeMetadata(response));
266-
}
267-
268281
const output: Output = {
269282
$metadata: this.deserializeMetadata(response),
270283
...dataObject,

packages/core/src/submodules/protocols/serde/FromStringShapeDeserializer.ts

+10-12
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NormalizedSchema, TypeRegistry } from "@smithy/core/schema";
2-
import { parseRfc3339DateTimeWithOffset, splitHeader } from "@smithy/core/serde";
2+
import { NumericValue,parseRfc3339DateTimeWithOffset, splitHeader } from "@smithy/core/serde";
33
import { Schema, SerdeContext, ShapeDeserializer } from "@smithy/types";
44
import { fromBase64 } from "@smithy/util-base64";
55

@@ -21,25 +21,23 @@ export class FromStringShapeDeserializer implements ShapeDeserializer<string> {
2121
if (ns.isListSchema()) {
2222
return splitHeader(data);
2323
}
24-
if (schema === "blob" || schema === "streaming-blob") {
24+
if (schema === 0b0001_0101 || schema === 0b0010_1010) {
2525
return fromBase64(data);
2626
}
27-
if (schema === "time") {
27+
if (schema === 0b0000_0100) {
2828
return this.defaultTimestampParser(data);
2929
}
30-
if (schema === "date-time") {
30+
if (schema === 0b0000_0101) {
3131
return parseRfc3339DateTimeWithOffset(data);
3232
}
33-
const simpleType = this.registry.getSimpleType(ns.getName() ?? "");
34-
switch (simpleType) {
35-
case "number":
33+
switch (true) {
34+
case ns.isNumericSchema():
3635
return Number(data);
37-
case "bigint":
36+
case ns.isBigIntegerSchema():
3837
return BigInt(data);
39-
case "bigdecimal":
40-
// todo(schema)
41-
throw new Error("bigdecimal not implemented");
42-
case "boolean":
38+
case ns.isBigDecimalSchema():
39+
return new NumericValue(data, "bigDecimal");
40+
case ns.isBooleanSchema():
4341
return String(data).toLowerCase() === "true";
4442
}
4543
return data;

packages/core/src/submodules/schema/TypeRegistry.ts

+23-36
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ import type { Schema as ISchema } from "@smithy/types";
22

33
export class TypeRegistry {
44
public static active: TypeRegistry | null = null;
5-
public static registries = new Map<string, TypeRegistry>();
6-
public static schemaToRegistry = new Map<object, TypeRegistry>();
7-
8-
private simpleTypes: Record<string, string> = {};
5+
public static readonly registries = new Map<string, TypeRegistry>();
6+
public static readonly schemaToRegistry = new Map<object, TypeRegistry>();
97

108
private constructor(
119
public readonly namespace: string,
@@ -29,49 +27,24 @@ export class TypeRegistry {
2927
* @param schema - to be registered.
3028
*/
3129
public register(shapeId: string, schema: ISchema) {
32-
const qualifiedName = this.namespace + "#" + shapeId;
33-
this.schemas.set(qualifiedName, schema);
30+
const qualifiedName = this.normalizeShapeId(shapeId);
31+
const registry = TypeRegistry.for(this.getNamespace(shapeId));
32+
registry.schemas.set(qualifiedName, schema);
3433
if (typeof schema === "object") {
3534
TypeRegistry.schemaToRegistry.set(schema, this);
3635
}
3736
}
3837

39-
/**
40-
* Used to disambiguate e.g. XML values, which are all strings,
41-
* into other simple JavaScript types like boolean and number.
42-
*
43-
* @param simpleTypes - map of shape id strings to simple type.
44-
*/
45-
public registerSimpleTypes(simpleTypes: Record<string, "boolean" | "number" | "bigint" | "bigdecimal">): void {
46-
for (const [name, type] of Object.entries(simpleTypes)) {
47-
const normalizedName = name.includes("#") ? name : this.namespace + "#" + name;
48-
this.simpleTypes[normalizedName] = type;
49-
}
50-
}
51-
52-
/**
53-
* Used to disambiguate e.g. XML values, which are all strings,
54-
* into other simple JavaScript types like boolean and number.
55-
*
56-
* @param shapeId - to query.
57-
* @returns simple type of the shape id in this registry.
58-
*/
59-
public getSimpleType(shapeId: string): string {
60-
if (shapeId.includes("#")) {
61-
return this.simpleTypes[shapeId];
62-
}
63-
return this.simpleTypes[this.namespace + "#" + shapeId] ?? "unknown";
64-
}
65-
6638
/**
6739
* @param shapeId - query.
6840
* @returns the schema.
6941
*/
7042
public getSchema(shapeId: string): ISchema {
71-
if (shapeId.includes("#")) {
72-
return this.schemas.get(shapeId);
43+
const id = this.normalizeShapeId(shapeId);
44+
if (!this.schemas.has(id)) {
45+
throw new Error(`@smithy/core/schema - schema not found for ${id}`);
7346
}
74-
return this.schemas.get(this.namespace + "#" + shapeId);
47+
return this.schemas.get(id)!;
7548
}
7649

7750
/**
@@ -91,6 +64,20 @@ export class TypeRegistry {
9164

9265
public destroy() {
9366
TypeRegistry.registries.delete(this.namespace);
67+
for (const schema of this.schemas) {
68+
TypeRegistry.schemaToRegistry.delete(schema);
69+
}
9470
this.schemas.clear();
9571
}
72+
73+
private normalizeShapeId(shapeId: string) {
74+
if (shapeId.includes("#")) {
75+
return shapeId;
76+
}
77+
return this.namespace + "#" + shapeId;
78+
}
79+
80+
private getNamespace(shapeId: string) {
81+
return this.normalizeShapeId(shapeId).split("#")[0];
82+
}
9683
}

packages/core/src/submodules/schema/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@ export * from "./schemas/OperationSchema";
66
export * from "./schemas/ErrorSchema";
77
export * from "./schemas/NormalizedSchema";
88
export * from "./schemas/Schema";
9+
export * from "./schemas/SimpleSchema";
910
export * from "./schemas/StructureSchema";
1011
export * from "./TypeRegistry";

0 commit comments

Comments
 (0)