Skip to content

feat(endpoint): endpoints v2 codegen #3942

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

Merged
merged 3 commits into from
Sep 14, 2022
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion codegen/sdk-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ import software.amazon.smithy.model.shapes.ServiceShape
import software.amazon.smithy.model.node.Node
import software.amazon.smithy.gradle.tasks.SmithyBuild
import software.amazon.smithy.aws.traits.ServiceTrait
import java.util.stream.Stream
import kotlin.streams.toList

buildscript {
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
Expand Down Expand Up @@ -62,7 +64,10 @@ tasks.register("generate-smithy-build") {
val model = Model.assembler()
.addImport(file.absolutePath)
.assemble().result.get();
val services = model.shapes(ServiceShape::class.javaObjectType).sorted().toList();
val servicesStream: Stream<ServiceShape> = model.shapes(ServiceShape::class.javaObjectType)
val servicesStreamSorted: Stream<ServiceShape> = servicesStream.sorted()
val services: List<ServiceShape> = servicesStreamSorted.toList()

if (services.size != 1) {
throw Exception("There must be exactly one service in each aws model file, but found " +
"${services.size} in ${file.name}: ${services.map { it.id }}");
Expand Down
1 change: 1 addition & 0 deletions codegen/smithy-aws-typescript-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ dependencies {
api("software.amazon.smithy:smithy-aws-iam-traits:${rootProject.extra["smithyVersion"]}")
api("software.amazon.smithy:smithy-protocol-test-traits:${rootProject.extra["smithyVersion"]}")
api("software.amazon.smithy:smithy-model:${rootProject.extra["smithyVersion"]}")
api("software.amazon.smithy:smithy-rules-engine:${rootProject.extra["smithyVersion"]}")
api("software.amazon.smithy.typescript:smithy-typescript-codegen:0.11.0")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package software.amazon.smithy.aws.typescript.codegen;

import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isAwsService;
import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isEndpointsV2Service;
import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isSigV4Service;
import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_CONFIG;
import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_MIDDLEWARE;
Expand Down Expand Up @@ -46,82 +47,89 @@ public List<RuntimeClientPlugin> getClientPlugins() {
// Note that order is significant because configurations might
// rely on previously resolved values.
return ListUtils.of(
RuntimeClientPlugin.builder()
.withConventions(TypeScriptDependency.CONFIG_RESOLVER.dependency, "Region", HAS_CONFIG)
.servicePredicate((m, s) -> isAwsService(s) || isSigV4Service(s))
.build(),
// Only one of Endpoints or CustomEndpoints should be used
RuntimeClientPlugin.builder()
.withConventions(TypeScriptDependency.CONFIG_RESOLVER.dependency, "Endpoints", HAS_CONFIG)
.servicePredicate((m, s) -> isAwsService(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(TypeScriptDependency.CONFIG_RESOLVER.dependency, "CustomEndpoints", HAS_CONFIG)
.servicePredicate((m, s) -> !isAwsService(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(TypeScriptDependency.MIDDLEWARE_RETRY.dependency, "Retry")
.build(),
RuntimeClientPlugin.builder()
.withConventions(TypeScriptDependency.MIDDLEWARE_CONTENT_LENGTH.dependency, "ContentLength",
HAS_MIDDLEWARE)
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.ACCEPT_HEADER.dependency, "AcceptHeader",
HAS_MIDDLEWARE)
.servicePredicate((m, s) -> testServiceId(s, "API Gateway"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.GLACIER_MIDDLEWARE.dependency,
"Glacier", HAS_MIDDLEWARE)
.servicePredicate((m, s) -> testServiceId(s, "Glacier"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.EC2_MIDDLEWARE.dependency,
"CopySnapshotPresignedUrl", HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> o.getId().getName(s).equals("CopySnapshot")
&& testServiceId(s, "EC2"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.MACHINELEARNING_MIDDLEWARE.dependency, "PredictEndpoint",
HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> o.getId().getName(s).equals("Predict")
&& testServiceId(s, "Machine Learning"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.ROUTE53_MIDDLEWARE.dependency,
"ChangeResourceRecordSets", HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> o.getId().getName(s).equals("ChangeResourceRecordSets")
&& testServiceId(s, "Route 53"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.ROUTE53_MIDDLEWARE.dependency, "IdNormalizer",
HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> testInputContainsMember(m, o, ROUTE_53_ID_MEMBERS)
&& testServiceId(s, "Route 53"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.MIDDLEWARE_HOST_HEADER.dependency, "HostHeader")
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.MIDDLEWARE_LOGGER.dependency, "Logger", HAS_MIDDLEWARE)
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.RECURSION_DETECTION_MIDDLEWARE.dependency,
"RecursionDetection", HAS_MIDDLEWARE)
.build()
RuntimeClientPlugin.builder()
.withConventions(TypeScriptDependency.CONFIG_RESOLVER.dependency, "Region", HAS_CONFIG)
.servicePredicate((m, s) -> isAwsService(s) || isSigV4Service(s))
.build(),
// Only one of Endpoints or CustomEndpoints should be used
RuntimeClientPlugin.builder()
.withConventions(
TypeScriptDependency.CONFIG_RESOLVER.dependency, "Endpoints", HAS_CONFIG)
.servicePredicate((m, s) -> isAwsService(s) && !isEndpointsV2Service(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(
TypeScriptDependency.CONFIG_RESOLVER.dependency, "CustomEndpoints", HAS_CONFIG)
.servicePredicate((m, s) -> !isAwsService(s) && !isEndpointsV2Service(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(
TypeScriptDependency.MIDDLEWARE_ENDPOINTS_V2.dependency, "Endpoint", HAS_CONFIG)
.servicePredicate((m, s) -> isAwsService(s) && isEndpointsV2Service(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(TypeScriptDependency.MIDDLEWARE_RETRY.dependency, "Retry")
.build(),
RuntimeClientPlugin.builder()
.withConventions(TypeScriptDependency.MIDDLEWARE_CONTENT_LENGTH.dependency, "ContentLength",
HAS_MIDDLEWARE)
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.ACCEPT_HEADER.dependency, "AcceptHeader",
HAS_MIDDLEWARE)
.servicePredicate((m, s) -> testServiceId(s, "API Gateway"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.GLACIER_MIDDLEWARE.dependency,
"Glacier", HAS_MIDDLEWARE)
.servicePredicate((m, s) -> testServiceId(s, "Glacier"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.EC2_MIDDLEWARE.dependency,
"CopySnapshotPresignedUrl", HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> o.getId().getName(s).equals("CopySnapshot")
&& testServiceId(s, "EC2"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.MACHINELEARNING_MIDDLEWARE.dependency, "PredictEndpoint",
HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> o.getId().getName(s).equals("Predict")
&& testServiceId(s, "Machine Learning"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.ROUTE53_MIDDLEWARE.dependency,
"ChangeResourceRecordSets", HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> o.getId().getName(s).equals("ChangeResourceRecordSets")
&& testServiceId(s, "Route 53"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.ROUTE53_MIDDLEWARE.dependency, "IdNormalizer",
HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> testInputContainsMember(m, o, ROUTE_53_ID_MEMBERS)
&& testServiceId(s, "Route 53"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.MIDDLEWARE_HOST_HEADER.dependency, "HostHeader")
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.MIDDLEWARE_LOGGER.dependency, "Logger", HAS_MIDDLEWARE)
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.RECURSION_DETECTION_MIDDLEWARE.dependency,
"RecursionDetection", HAS_MIDDLEWARE)
.build()
);
}

private static boolean testInputContainsMember(
Model model,
OperationShape operationShape,
Set<String> expectedMemberNames
Model model,
OperationShape operationShape,
Set<String> expectedMemberNames
) {
OperationIndex operationIndex = OperationIndex.of(model);
return operationIndex.getInput(operationShape)
.filter(input -> input.getMemberNames().stream().anyMatch(expectedMemberNames::contains))
.isPresent();
.filter(input -> input.getMemberNames().stream().anyMatch(expectedMemberNames::contains))
.isPresent();
}

private static boolean testServiceId(Shape serviceShape, String expectedId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

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

import static software.amazon.smithy.aws.typescript.codegen.AwsTraitsUtils.isEndpointsV2Service;
import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_CONFIG;
import static software.amazon.smithy.typescript.codegen.integration.RuntimeClientPlugin.Convention.HAS_MIDDLEWARE;

Expand Down Expand Up @@ -77,7 +78,7 @@ public final class AddS3Config implements TypeScriptIntegration {
@Override
public Model preprocessModel(Model model, TypeScriptSettings settings) {
ServiceShape serviceShape = settings.getService(model);
if (!testServiceId(serviceShape)) {
if (!isS3(serviceShape)) {
return model;
}
Model.Builder modelBuilder = model.toBuilder();
Expand Down Expand Up @@ -106,9 +107,14 @@ public Model preprocessModel(Model model, TypeScriptSettings settings) {
}

@Override
public void addConfigInterfaceFields(TypeScriptSettings settings, Model model, SymbolProvider symbolProvider,
TypeScriptWriter writer) {
if (!testServiceId(settings.getService(model))) {
public void addConfigInterfaceFields(
TypeScriptSettings settings,
Model model,
SymbolProvider symbolProvider,
TypeScriptWriter writer
) {
ServiceShape service = settings.getService(model);
if (!isS3(service)) {
return;
}
writer.writeDocs("Whether to escape request path when signing the request.")
Expand All @@ -123,7 +129,7 @@ public void addConfigInterfaceFields(TypeScriptSettings settings, Model model, S
@Override
public Map<String, Consumer<TypeScriptWriter>> getRuntimeConfigWriters(TypeScriptSettings settings, Model model,
SymbolProvider symbolProvider, LanguageTarget target) {
if (!testServiceId(settings.getService(model))) {
if (!isS3(settings.getService(model))) {
return Collections.emptyMap();
}
switch (target) {
Expand Down Expand Up @@ -158,62 +164,68 @@ public List<RuntimeClientPlugin> getClientPlugins() {
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.S3_MIDDLEWARE.dependency, "ValidateBucketName",
HAS_MIDDLEWARE)
.servicePredicate((m, s) -> testServiceId(s))
.servicePredicate((m, s) -> isS3(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.S3_MIDDLEWARE.dependency, "CheckContentLengthHeader",
HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> testServiceId(s) && o.getId().getName(s).equals("PutObject"))
.operationPredicate((m, s, o) -> isS3(s) && o.getId().getName(s).equals("PutObject"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.S3_MIDDLEWARE.dependency, "throw200Exceptions",
HAS_MIDDLEWARE)
.operationPredicate(
(m, s, o) -> EXCEPTIONS_OF_200_OPERATIONS.contains(o.getId().getName(s))
&& testServiceId(s))
.build(),
.withConventions(AwsDependency.S3_MIDDLEWARE.dependency, "throw200Exceptions",
HAS_MIDDLEWARE)
.operationPredicate(
(m, s, o) -> EXCEPTIONS_OF_200_OPERATIONS.contains(o.getId().getName(s))
&& isS3(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.S3_MIDDLEWARE.dependency,
"WriteGetObjectResponseEndpoint", HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> testServiceId(s)
&& o.getId().getName(s).equals("WriteGetObjectResponse"))
.build(),
.withConventions(AwsDependency.S3_MIDDLEWARE.dependency,
"WriteGetObjectResponseEndpoint", HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> isS3(s)
&& o.getId().getName(s).equals("WriteGetObjectResponse"))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.ADD_EXPECT_CONTINUE.dependency, "AddExpectContinue",
HAS_MIDDLEWARE)
.servicePredicate((m, s) -> testServiceId(s))
.servicePredicate((m, s) -> isS3(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.SSEC_MIDDLEWARE.dependency, "Ssec", HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> testInputContainsMember(m, o, SSEC_INPUT_KEYS)
&& testServiceId(s))
.operationPredicate((m, s, o) -> containsInputMembers(m, o, SSEC_INPUT_KEYS)
&& isS3(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.LOCATION_CONSTRAINT.dependency, "LocationConstraint",
HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> o.getId().getName(s).equals("CreateBucket")
&& testServiceId(s))
&& isS3(s))
.build(),
/**
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.S3_MIDDLEWARE.dependency, "S3",
HAS_CONFIG)
.servicePredicate((m, s) -> isS3(s) && isEndpointsV2Service(s))
.build(),
/*
* BUCKET_ENDPOINT_MIDDLEWARE needs two separate plugins. The first resolves the config in the client.
* The second applies the middleware to bucket endpoint operations.
*/
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.BUCKET_ENDPOINT_MIDDLEWARE.dependency, "BucketEndpoint",
HAS_CONFIG)
.servicePredicate((m, s) -> testServiceId(s))
.servicePredicate((m, s) -> isS3(s) && !isEndpointsV2Service(s))
.build(),
RuntimeClientPlugin.builder()
.withConventions(AwsDependency.BUCKET_ENDPOINT_MIDDLEWARE.dependency, "BucketEndpoint",
HAS_MIDDLEWARE)
.operationPredicate((m, s, o) -> !NON_BUCKET_ENDPOINT_OPERATIONS.contains(o.getId().getName(s))
&& testServiceId(s)
&& testInputContainsMember(m, o, BUCKET_ENDPOINT_INPUT_KEYS))
&& isS3(s)
&& !isEndpointsV2Service(s)
&& containsInputMembers(m, o, BUCKET_ENDPOINT_INPUT_KEYS))
.build()
);
}

private static boolean testInputContainsMember(
private static boolean containsInputMembers(
Model model,
OperationShape operationShape,
Set<String> expectedMemberNames
Expand All @@ -224,7 +236,7 @@ private static boolean testInputContainsMember(
.isPresent();
}

private static boolean testServiceId(Shape serviceShape) {
private static boolean isS3(Shape serviceShape) {
return serviceShape.getTrait(ServiceTrait.class).map(ServiceTrait::getSdkId).orElse("").equals("S3");
}
}
Loading