Skip to content

Commit

Permalink
Add HostImmutable to endpoint prefix middleware codegen
Browse files Browse the repository at this point in the history
  • Loading branch information
jasdel committed Oct 23, 2020
1 parent 599e875 commit fccb102
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -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<RuntimeClientPlugin> runtimeClientPlugins = new ArrayList<>();

@Override
public List<RuntimeClientPlugin> 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<OperationShape> 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<OperationShape> 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<OperationShape> getOperationsWithEndpointPrefix(Model model, ServiceShape service) {
List<OperationShape> 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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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.
*
* <p>If there are no HostLabels then this will be a simple string assignment,
* otherwise a string builder will be used.
*
* <p>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()");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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, "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

0 comments on commit fccb102

Please sign in to comment.