From fccb1023b4e06080186e13d370ec4d49b5afac67 Mon Sep 17 00:00:00 2001 From: Jason Del Ponte Date: Thu, 22 Oct 2020 15:04:18 -0700 Subject: [PATCH] Add HostImmutable to endpoint prefix middleware codegen --- .../EndpointHostPrefixMiddleware.java | 208 ++++++++++++++++++ .../HttpBindingProtocolGenerator.java | 3 - .../HttpProtocolGeneratorUtils.java | 65 ------ .../integration/HttpRpcProtocolGenerator.java | 4 - ...mithy.go.codegen.integration.GoIntegration | 1 + 5 files changed, 209 insertions(+), 72 deletions(-) create mode 100644 codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EndpointHostPrefixMiddleware.java diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EndpointHostPrefixMiddleware.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EndpointHostPrefixMiddleware.java new file mode 100644 index 000000000..442042765 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/EndpointHostPrefixMiddleware.java @@ -0,0 +1,208 @@ +/* + * Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + * + * + */ + +package software.amazon.smithy.go.codegen.integration; + +import java.util.ArrayList; +import java.util.List; +import software.amazon.smithy.codegen.core.SymbolProvider; +import software.amazon.smithy.go.codegen.GoDelegator; +import software.amazon.smithy.go.codegen.GoSettings; +import software.amazon.smithy.go.codegen.GoStackStepMiddlewareGenerator; +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.go.codegen.SmithyGoDependency; +import software.amazon.smithy.go.codegen.SymbolUtils; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.pattern.SmithyPattern; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.EndpointTrait; + +public class EndpointHostPrefixMiddleware implements GoIntegration { + + private static final String MIDDLEWARE_ID = "EndpointHostPrefix"; + + List runtimeClientPlugins = new ArrayList<>(); + + @Override + public List getClientPlugins() { + return runtimeClientPlugins; + } + + /** + * Builds the set of runtime plugs used by the endpoint prefix middleware. + * + * @param settings codegen settings + * @param model api model + */ + @Override + public void processFinalizedModel(GoSettings settings, Model model) { + ServiceShape service = settings.getService(model); + List operations = getOperationsWithEndpointPrefix(model, service); + + operations.forEach((operation) -> { + String middlewareHelperName = getMiddlewareHelperName(operation); + runtimeClientPlugins.add(RuntimeClientPlugin.builder() + .operationPredicate((m, s, o) -> o.equals(operation)) + .registerMiddleware(MiddlewareRegistrar.builder() + .resolvedFunction(SymbolUtils.createValueSymbolBuilder(middlewareHelperName).build()) + .build()) + .build() + ); + }); + } + + /** + * Generates the middleware and helper for the endpoint host prefix trait. + * + * @param settings Settings used to generate. + * @param model Model to generate from. + * @param symbolProvider Symbol provider used for codegen. + * @param delegator the go file factor + */ + @Override + public void writeAdditionalFiles( + GoSettings settings, + Model model, + SymbolProvider symbolProvider, + GoDelegator delegator + ) { + ServiceShape service = settings.getService(model); + List operations = getOperationsWithEndpointPrefix(model, service); + + operations.forEach((operation) -> { + delegator.useShapeWriter(operation, (writer) -> { + SmithyPattern pattern = operation.expectTrait(EndpointTrait.class).getHostPrefix(); + + writeMiddleware(writer, model, symbolProvider, operation, pattern); + + String middlewareName = getMiddlewareName(operation); + String middlewareHelperName = getMiddlewareHelperName(operation); + writer.openBlock("func $L(stack *middleware.Stack) error {", "}", + middlewareHelperName, + () -> { + writer.write( + "return stack.Serialize.Insert(&$L{}, `OperationSerializer`, middleware.Before)", + middlewareName); + }); + + }); + }); + } + + private static void writeMiddleware( + GoWriter writer, + Model model, + SymbolProvider symbolProvider, + OperationShape operation, + SmithyPattern pattern + ) { + GoStackStepMiddlewareGenerator middlewareGenerator = + GoStackStepMiddlewareGenerator.createSerializeStepMiddleware( + getMiddlewareName(operation), + MIDDLEWARE_ID + ); + + middlewareGenerator.writeMiddleware(writer, (generator, w) -> { + writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_TRANSPORT); + writer.addUseImports(SmithyGoDependency.FMT); + + w.openBlock("if smithyhttp.GetHostnameImmutable(ctx) {", "}", () -> { + w.write("return next.$L(ctx, in)", generator.getHandleMethodName()); + }).write(""); + + w.write("req, ok := in.Request.(*smithyhttp.Request)"); + w.openBlock("if !ok {", "}", () -> { + writer.write("return out, metadata, fmt.Errorf(\"unknown transport type %T\", in.Request)"); + }).write(""); + + if (pattern.getLabels().isEmpty()) { + w.write("request.HostPrefix = $S", pattern.toString()); + + } else { + // If the pattern has labels, we need to build up the host prefix using a string builder. + writer.addUseImports(SmithyGoDependency.STRINGS); + StructureShape input = ProtocolUtils.expectInput(model, operation); + writer.write("input, ok := in.Parameters.($T)", symbolProvider.toSymbol(input)); + w.openBlock("if !ok {", "}", () -> { + writer.write("return out, metadata, fmt.Errorf(\"unknown input type %T\", in.Parameters)"); + }).write(""); + + w.write("var prefix strings.Builder"); + for (SmithyPattern.Segment segment : pattern.getSegments()) { + if (!segment.isLabel()) { + w.write("prefix.WriteString($S)", segment.toString()); + } else { + MemberShape member = input.getMember(segment.getContent()).get(); + String memberName = symbolProvider.toMemberName(member); + String memberReference = "input." + memberName; + + // Theoretically this should never be nil or empty by this point unless validation has + // been disabled. + w.write("if $L == nil || len(*$L) == 0 {", memberReference, memberReference).indent(); + w.write("return out, metadata, &smithy.SerializationError{Err: " + + "fmt.Errorf(\"$L forms part of the endpoint host and so may not be nil\")}", + memberName); + w.dedent().write("} else if !smithyhttp.ValidHostLabel(*$L) {", memberReference).indent(); + w.write("return out, metadata, &smithy.SerializationError{Err: " + + "fmt.Errorf(\"$L forms part of the endpoint host and so must match " + + "\\\"[a-zA-Z0-9-]{1,63}\\\"" + + ", but was \\\"%s\\\"\", *$L)}", memberName, memberReference); + w.dedent().openBlock("} else {", "}", () -> { + w.write("prefix.WriteString(*$L)", memberReference); + }); + } + } + + w.write("request.HostPrefix = prefix.String()"); + } + w.write(""); + + w.write("return next.$L(ctx, in)", generator.getHandleMethodName()); + }); + } + + /** + * Gets a list of the operations decorated with the EndpointTrait. + * + * @param model Model used for generation. + * @param service Service for getting list of operations. + * @return list of operations decorated with the EndpointTrait. + */ + public static List getOperationsWithEndpointPrefix(Model model, ServiceShape service) { + List operations = new ArrayList<>(); + service.getAllOperations().stream().forEach((operationId) -> { + OperationShape operation = model.expectShape(operationId).asOperationShape().get(); + if (!operation.hasTrait(EndpointTrait.ID)) { + return; + } + + operations.add(operation); + }); + return operations; + } + + private static String getMiddlewareName(OperationShape operation) { + return String.format("endpointPrefix_op%sMiddleware", operation.getId().getName()); + } + + private static String getMiddlewareHelperName(OperationShape operation) { + return String.format("addEndpointPrefix_op%sMiddleware", operation.getId().getName()); + } +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java index 47cf1f4d1..04e58c6e7 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpBindingProtocolGenerator.java @@ -16,7 +16,6 @@ package software.amazon.smithy.go.codegen.integration; import static software.amazon.smithy.go.codegen.integration.HttpProtocolGeneratorUtils.isShapeWithResponseBindings; -import static software.amazon.smithy.go.codegen.integration.HttpProtocolGeneratorUtils.setHostPrefix; import static software.amazon.smithy.go.codegen.integration.ProtocolUtils.requiresDocumentSerdeFunction; import static software.amazon.smithy.go.codegen.integration.ProtocolUtils.writeSafeMemberAccessor; @@ -214,8 +213,6 @@ private void generateOperationSerializerMiddleware(GenerationContext context, Op + " in.Parameters)}"); }); - setHostPrefix(context, operation); - writer.write(""); writer.write("opPath, opQuery := httpbinding.SplitURI($S)", httpTrait.getUri()); writer.write("request.URL.Path = opPath"); diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java index 0671b2e8c..fb96a196d 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpProtocolGeneratorUtils.java @@ -20,20 +20,15 @@ import java.util.TreeSet; import java.util.function.Consumer; import software.amazon.smithy.codegen.core.Symbol; -import software.amazon.smithy.codegen.core.SymbolProvider; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; import software.amazon.smithy.go.codegen.integration.ProtocolGenerator.GenerationContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.HttpBinding; import software.amazon.smithy.model.knowledge.HttpBindingIndex; -import software.amazon.smithy.model.pattern.SmithyPattern; -import software.amazon.smithy.model.pattern.SmithyPattern.Segment; -import software.amazon.smithy.model.shapes.MemberShape; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.traits.EndpointTrait; public final class HttpProtocolGeneratorUtils { @@ -138,64 +133,4 @@ public static boolean isShapeWithResponseBindings(Model model, Shape shape, Http } return false; } - - /** - * Sets the HostPrefix on the request if the operation has the Endpoint trait. - * - *

If there are no HostLabels then this will be a simple string assignment, - * otherwise a string builder will be used. - * - *

This assumes that the smithyhttp.Request is available under the variable - * "request" and the operation's input struct is available under the variable - * "input". - * - * @param context The generation context. - * @param operation The operation to set the host prefix for. - */ - public static void setHostPrefix(GenerationContext context, OperationShape operation) { - if (!operation.hasTrait(EndpointTrait.ID)) { - return; - } - GoWriter writer = context.getWriter(); - SmithyPattern pattern = operation.expectTrait(EndpointTrait.class).getHostPrefix(); - - // If the pattern is just a string without any labels, then we simply use string - // assignment to avoid unnecessary imports / work. - if (pattern.getLabels().isEmpty()) { - writer.write("request.HostPrefix = $S", pattern.toString()); - return; - } - - SymbolProvider symbolProvider = context.getSymbolProvider(); - StructureShape input = ProtocolUtils.expectInput(context.getModel(), operation); - - // If the pattern has labels, we need to build up the host prefix using a string builder. - writer.addUseImports(SmithyGoDependency.STRINGS); - writer.addUseImports(SmithyGoDependency.SMITHY_HTTP_TRANSPORT); - writer.addUseImports(SmithyGoDependency.FMT); - writer.write("var prefix strings.Builder"); - for (Segment segment : pattern.getSegments()) { - if (!segment.isLabel()) { - writer.write("prefix.WriteString($S)", segment.toString()); - } else { - MemberShape member = input.getMember(segment.getContent()).get(); - String memberName = symbolProvider.toMemberName(member); - String memberReference = "input." + memberName; - - // Theoretically this should never be nil by this point unless validation has been disabled. - writer.write("if $L == nil {", memberReference).indent(); - writer.write("return out, metadata, &smithy.SerializationError{Err: " - + "fmt.Errorf(\"$L forms part of the endpoint host and so may not be nil\")}", memberName); - writer.dedent().write("} else if !smithyhttp.ValidHostLabel(*$L) {", memberReference).indent(); - writer.write("return out, metadata, &smithy.SerializationError{Err: " - + "fmt.Errorf(\"$L forms part of the endpoint host and so must match \\\"[a-zA-Z0-9-]{1,63}\\\"" - + ", but was \\\"%s\\\"\", *$L)}", memberName, memberReference); - writer.dedent().openBlock("} else {", "}", () -> { - writer.write("prefix.WriteString(*$L)", memberReference); - }); - } - } - - writer.write("request.HostPrefix = prefix.String()"); - } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpRpcProtocolGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpRpcProtocolGenerator.java index e146e28b7..4fbad7b53 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpRpcProtocolGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/HttpRpcProtocolGenerator.java @@ -15,8 +15,6 @@ package software.amazon.smithy.go.codegen.integration; -import static software.amazon.smithy.go.codegen.integration.HttpProtocolGeneratorUtils.setHostPrefix; - import java.util.Set; import java.util.TreeSet; import software.amazon.smithy.codegen.core.Symbol; @@ -119,8 +117,6 @@ private void generateOperationSerializer(GenerationContext context, OperationSha + " in.Parameters)}"); }).write(""); - setHostPrefix(context, operation); - writer.write("request.Request.URL.Path = $S", getOperationPath(context, operation)); writer.write("request.Request.Method = \"POST\""); writer.write("httpBindingEncoder, err := httpbinding.NewEncoder(request.URL.Path, " diff --git a/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration b/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration index fb46faa89..739fed7ad 100644 --- a/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration +++ b/codegen/smithy-go-codegen/src/main/resources/META-INF/services/software.amazon.smithy.go.codegen.integration.GoIntegration @@ -2,3 +2,4 @@ software.amazon.smithy.go.codegen.integration.ValidationGenerator software.amazon.smithy.go.codegen.integration.IdempotencyTokenMiddlewareGenerator software.amazon.smithy.go.codegen.integration.AddChecksumRequiredMiddleware software.amazon.smithy.go.codegen.integration.RequiresLengthTraitSupport +software.amazon.smithy.go.codegen.integration.EndpointHostPrefixMiddleware