Skip to content

test(clients): schema testing #6986

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9,375 changes: 9,375 additions & 0 deletions clients/client-s3/src/schemas/schemas.ts

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/

package software.amazon.smithy.aws.typescript.codegen;

import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;

import software.amazon.smithy.aws.traits.protocols.AwsJson1_0Trait;
import software.amazon.smithy.aws.traits.protocols.AwsJson1_1Trait;
import software.amazon.smithy.aws.traits.protocols.AwsQueryTrait;
import software.amazon.smithy.aws.traits.protocols.Ec2QueryTrait;
import software.amazon.smithy.aws.traits.protocols.RestJson1Trait;
import software.amazon.smithy.aws.traits.protocols.RestXmlTrait;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.traits.XmlNamespaceTrait;
import software.amazon.smithy.typescript.codegen.LanguageTarget;
import software.amazon.smithy.typescript.codegen.TypeScriptSettings;
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
import software.amazon.smithy.typescript.codegen.integration.TypeScriptIntegration;
import software.amazon.smithy.typescript.codegen.schema.SchemaGenerationAllowlist;
import software.amazon.smithy.utils.MapUtils;
import software.amazon.smithy.utils.SmithyInternalApi;


/**
* Adds a protocol implementation to the runtime config.
*/
@SmithyInternalApi
public final class AddProtocolConfig implements TypeScriptIntegration {

public AddProtocolConfig() {
SchemaGenerationAllowlist.allow("com.amazonaws.s3#AmazonS3");
SchemaGenerationAllowlist.allow("com.amazonaws.dynamodb#DynamoDB_20120810");
SchemaGenerationAllowlist.allow("com.amazonaws.lambda#AWSGirApiService");

// protocol tests
SchemaGenerationAllowlist.allow("aws.protocoltests.json10#JsonRpc10");
SchemaGenerationAllowlist.allow("aws.protocoltests.json#JsonProtocol");
SchemaGenerationAllowlist.allow("aws.protocoltests.restjson#RestJson");
SchemaGenerationAllowlist.allow("aws.protocoltests.restxml#RestXml");
SchemaGenerationAllowlist.allow("aws.protocoltests.query#AwsQuery");
SchemaGenerationAllowlist.allow("aws.protocoltests.ec2#AwsEc2");
}

@Override
public void addConfigInterfaceFields(
TypeScriptSettings settings,
Model model,
SymbolProvider symbolProvider,
TypeScriptWriter writer
) {
// the {{ protocol?: Protocol }} type field is provided
// by the smithy client config interface.
}

@Override
public Map<String, Consumer<TypeScriptWriter>> getRuntimeConfigWriters(
TypeScriptSettings settings,
Model model,
SymbolProvider symbolProvider,
LanguageTarget target
) {
if (!SchemaGenerationAllowlist.contains(settings.getService())) {
return Collections.emptyMap();
}
String namespace = settings.getService().getNamespace();
String xmlns = settings.getService(model)
.getTrait(XmlNamespaceTrait.class)
.map(XmlNamespaceTrait::getUri)
.orElse("");

switch (target) {
case SHARED:
if (Objects.equals(settings.getProtocol(), RestXmlTrait.ID)) {
return MapUtils.of(
"protocol", writer -> {
writer.addImportSubmodule(
"AwsRestXmlProtocol", null,
AwsDependency.AWS_SDK_CORE, "/protocols");
writer.write("""
new AwsRestXmlProtocol({
defaultNamespace: $S, xmlNamespace: $S,
})""",
namespace,
xmlns
);
}
);
} else if (Objects.equals(settings.getProtocol(), AwsQueryTrait.ID)) {
return MapUtils.of(
"protocol", writer -> {
writer.addImportSubmodule(
"AwsQueryProtocol", null,
AwsDependency.AWS_SDK_CORE, "/protocols");
writer.write(
"""
new AwsQueryProtocol({
defaultNamespace: $S,
xmlNamespace: $S,
version: $S
})""",
namespace,
xmlns,
settings.getService(model).getVersion()
);
}
);
} else if (Objects.equals(settings.getProtocol(), Ec2QueryTrait.ID)) {
return MapUtils.of(
"protocol", writer -> {
writer.addImportSubmodule(
"AwsEc2QueryProtocol", null,
AwsDependency.AWS_SDK_CORE, "/protocols");
writer.write(
"""
new AwsEc2QueryProtocol({
defaultNamespace: $S,
xmlNamespace: $S,
version: $S
})""",
namespace,
xmlns,
settings.getService(model).getVersion()
);
}
);
} else if (Objects.equals(settings.getProtocol(), RestJson1Trait.ID)) {
return MapUtils.of(
"protocol", writer -> {
writer.addImportSubmodule(
"AwsRestJsonProtocol", null,
AwsDependency.AWS_SDK_CORE, "/protocols");
writer.write("new AwsRestJsonProtocol({ defaultNamespace: $S })", namespace);
}
);
} else if (Objects.equals(settings.getProtocol(), AwsJson1_0Trait.ID)) {
return MapUtils.of(
"protocol", writer -> {
writer.addImportSubmodule(
"AwsJson1_0Protocol", null,
AwsDependency.AWS_SDK_CORE, "/protocols");
writer.write("new AwsJson1_0Protocol({ defaultNamespace: $S })", namespace);
}
);
} else if (Objects.equals(settings.getProtocol(), AwsJson1_1Trait.ID)) {
return MapUtils.of(
"protocol", writer -> {
writer.addImportSubmodule(
"AwsJson1_1Protocol", null,
AwsDependency.AWS_SDK_CORE, "/protocols");
writer.write("new AwsJson1_1Protocol({ defaultNamespace: $S })", namespace);
}
);
}
case BROWSER:
case NODE:
default:
return Collections.emptyMap();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.TimestampFormatTrait.Format;
import software.amazon.smithy.model.traits.Trait;
import software.amazon.smithy.typescript.codegen.schema.SchemaTraitExtension;
import software.amazon.smithy.typescript.codegen.TypeScriptWriter;
import software.amazon.smithy.typescript.codegen.integration.HttpRpcProtocolGenerator;
import software.amazon.smithy.typescript.codegen.util.StringStore;
Expand All @@ -50,6 +52,21 @@
*/
@SmithyInternalApi
final class AwsQuery extends HttpRpcProtocolGenerator {
static {
SchemaTraitExtension.INSTANCE.add(
AwsQueryErrorTrait.ID,
(Trait trait) -> {
if (trait instanceof AwsQueryErrorTrait awsQueryError) {
return """
[`%s`, %s]""".formatted(
awsQueryError.getCode(),
awsQueryError.getHttpResponseCode()
);
}
return "";
}
);
}

AwsQuery() {
// AWS Query protocols will attempt to parse error codes from response bodies.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ software.amazon.smithy.aws.typescript.codegen.AwsServiceIdIntegration
software.amazon.smithy.aws.typescript.codegen.AwsPackageFixturesGeneratorIntegration
software.amazon.smithy.aws.typescript.codegen.AddSqsDependency
software.amazon.smithy.aws.typescript.codegen.AddBodyChecksumGeneratorDependency
software.amazon.smithy.aws.typescript.codegen.AddProtocolConfig
software.amazon.smithy.aws.typescript.codegen.AddS3Config
software.amazon.smithy.aws.typescript.codegen.AddS3ControlDependency
software.amazon.smithy.aws.typescript.codegen.AddEventStreamHandlingDependency
Expand Down
4 changes: 4 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,18 @@
"license": "Apache-2.0",
"dependencies": {
"@aws-sdk/types": "*",
"@aws-sdk/xml-builder": "*",
"@smithy/core": "^3.3.0",
"@smithy/node-config-provider": "^4.0.2",
"@smithy/property-provider": "^4.0.2",
"@smithy/protocol-http": "^5.1.0",
"@smithy/signature-v4": "^5.1.0",
"@smithy/smithy-client": "^4.2.1",
"@smithy/types": "^4.2.0",
"@smithy/util-base64": "^4.0.0",
"@smithy/util-body-length-browser": "^4.0.0",
"@smithy/util-middleware": "^4.0.2",
"@smithy/util-utf8": "^4.0.0",
"fast-xml-parser": "4.4.1",
"tslib": "^2.6.2"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ConfigurableSerdeContext, SerdeFunctions } from "@smithy/types";

/**
* @internal
*/
export class SerdeContextConfig implements ConfigurableSerdeContext {
protected serdeContext?: SerdeFunctions;

public setSerdeContext(serdeContext: SerdeFunctions): void {
this.serdeContext = serdeContext;
}
}
4 changes: 2 additions & 2 deletions packages/core/src/submodules/protocols/common.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { collectBody } from "@smithy/smithy-client";
import type { HttpResponse, SerdeContext } from "@smithy/types";
import type { SerdeFunctions } from "@smithy/types";

export const collectBodyString = (streamBody: any, context: SerdeContext): Promise<string> =>
export const collectBodyString = (streamBody: any, context: SerdeFunctions): Promise<string> =>
collectBody(streamBody, context).then((body) => context.utf8Encoder(body));
13 changes: 13 additions & 0 deletions packages/core/src/submodules/protocols/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,17 @@
export * from "./coercing-serializers";
export * from "./json/AwsJson1_0Protocol";
export * from "./json/AwsJson1_1Protocol";
export * from "./json/AwsJsonRpcProtocol";
export * from "./json/AwsRestJsonProtocol";
export * from "./json/JsonCodec";
export * from "./json/JsonShapeDeserializer";
export * from "./json/JsonShapeSerializer";
export * from "./json/awsExpectUnion";
export * from "./json/parseJsonBody";
export * from "./query/AwsEc2QueryProtocol";
export * from "./query/AwsQueryProtocol";
export * from "./xml/AwsRestXmlProtocol";
export * from "./xml/XmlCodec";
export * from "./xml/XmlShapeDeserializer";
export * from "./xml/XmlShapeSerializer";
export * from "./xml/parseXmlBody";
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { map, op, SCHEMA, sim, struct } from "@smithy/core/schema";
import { toBase64 } from "@smithy/util-base64";
import { toUtf8 } from "@smithy/util-utf8";
import { describe, expect, test as it } from "vitest";

import { AwsJson1_0Protocol } from "./AwsJson1_0Protocol";

describe(AwsJson1_0Protocol.name, () => {
const json = {
string: "string",
number: 1234,
boolean: false,
blob: "AAAAAAAAAAA=",
timestamp: 0,
};
const schema = struct(
"MyStruct",
0,
[...Object.keys(json)],
[SCHEMA.STRING, SCHEMA.NUMERIC, SCHEMA.BOOLEAN, SCHEMA.BLOB, SCHEMA.TIMESTAMP_DEFAULT]
);
const serdeContext = {
base64Encoder: toBase64,
utf8Encoder: toUtf8,
} as any;

describe("codec", () => {
it("serializes blobs and timestamps", () => {
const protocol = new AwsJson1_0Protocol();
protocol.setSerdeContext(serdeContext);
const codec = protocol.getPayloadCodec();
const serializer = codec.createSerializer();
const data = {
string: "string",
number: 1234,
boolean: false,
blob: new Uint8Array(8),
timestamp: new Date(0),
};
serializer.write(schema, data);
const serialized = serializer.flush();
expect(JSON.parse(serialized)).toEqual({
string: "string",
number: 1234,
boolean: false,
blob: "AAAAAAAAAAA=",
timestamp: 0,
});
});

it("deserializes blobs and timestamps", async () => {
const protocol = new AwsJson1_0Protocol();
protocol.setSerdeContext(serdeContext);
const codec = protocol.getPayloadCodec();
const deserializer = codec.createDeserializer();

const parsed = await deserializer.read(schema, JSON.stringify(json));
expect(parsed).toEqual({
string: "string",
number: 1234,
boolean: false,
blob: new Uint8Array(8),
timestamp: new Date(0),
});
});

it("ignores JSON name and HTTP bindings", async () => {
const protocol = new AwsJson1_0Protocol();
protocol.setSerdeContext(serdeContext);

const schema = struct(
"MyHttpBindingStructure",
{},
["header", "query", "headerMap", "payload"],
[
sim("MyHeader", SCHEMA.STRING, { httpHeader: "header", jsonName: "MyHeader" }),
sim("MyQuery", SCHEMA.STRING, { httpQuery: "query" }),
map(
"HeaderMap",
{
httpPrefixHeaders: "",
},
SCHEMA.NUMERIC
),
sim("MyPayload", SCHEMA.DOCUMENT, { httpPayload: 1 }),
]
);
const operationSchema = op("MyOperation", {}, schema, "unit");

const request = await protocol.serializeRequest(
operationSchema,
{
header: "hello",
query: "world",
headerMap: {
a: 1,
b: 2,
},
},
{
endpointV2: {
url: new URL("https://amazonaws.com/"),
},
}
);

expect(request.headers).toEqual({});
expect(request.query).toEqual({});
expect(request.body).toEqual(`{"header":"hello","query":"world","headerMap":{"a":1,"b":2}}`);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AwsJsonRpcProtocol } from "./AwsJsonRpcProtocol";

/**
* @public
* @see https://smithy.io/2.0/aws/protocols/aws-json-1_1-protocol.html#differences-between-awsjson1-0-and-awsjson1-1
*/
export class AwsJson1_0Protocol extends AwsJsonRpcProtocol {
public constructor({ defaultNamespace }: { defaultNamespace: string }) {
super({
defaultNamespace,
});
}

public getShapeId(): string {
return "aws.protocols#awsJson1_0";
}

protected getJsonRpcVersion() {
return "1.0" as const;
}
}
Loading
Loading