Skip to content

Commit

Permalink
add codegen support for string array and operation context params in …
Browse files Browse the repository at this point in the history
…endpoint rules
  • Loading branch information
lucix-aws committed Jun 25, 2024
1 parent 5ca24b4 commit 5d78ff9
Show file tree
Hide file tree
Showing 12 changed files with 386 additions and 53 deletions.
2 changes: 1 addition & 1 deletion codegen/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
smithyVersion=1.49.0
smithyVersion=1.50.0
smithyGradleVersion=0.7.0
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolDependency;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.codegen.core.WriterDelegator;
import software.amazon.smithy.go.codegen.GoSettings.ArtifactType;
import software.amazon.smithy.go.codegen.integration.GoIntegration;
import software.amazon.smithy.go.codegen.integration.ProtocolGenerator;
Expand Down Expand Up @@ -75,6 +76,7 @@ final class CodegenVisitor extends ShapeVisitor.Default<Void> {
private final List<RuntimeClientPlugin> runtimePlugins = new ArrayList<>();
private final ProtocolDocumentGenerator protocolDocumentGenerator;
private final EventStreamGenerator eventStreamGenerator;
private final GoCodegenContext ctx;

CodegenVisitor(PluginContext context) {
// Load all integrations.
Expand Down Expand Up @@ -154,6 +156,15 @@ final class CodegenVisitor extends ShapeVisitor.Default<Void> {
protocolDocumentGenerator = new ProtocolDocumentGenerator(settings, model, writers);

this.eventStreamGenerator = new EventStreamGenerator(settings, model, writers, symbolProvider, service);

this.ctx = new GoCodegenContext(
model,
settings,
symbolProvider,
fileManifest,
// FUTURE: GoDelegator should satisfy this interface
new WriterDelegator<>(fileManifest, symbolProvider, (filename, namespace) -> new GoWriter(namespace)),
integrations);
}

private static ProtocolGenerator resolveProtocolGenerator(
Expand Down Expand Up @@ -359,12 +370,9 @@ public Void serviceShape(ServiceShape shape) {
TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class);
Set<OperationShape> containedOperations = new TreeSet<>(topDownIndex.getContainedOperations(service));
for (OperationShape operation : containedOperations) {
Symbol operationSymbol = symbolProvider.toSymbol(operation);

writers.useShapeWriter(
operation, operationWriter -> new OperationGenerator(settings, model, symbolProvider,
operationWriter, service, operation, operationSymbol, applicationProtocol,
protocolGenerator, runtimePlugins).run());
writers.useShapeWriter(operation, operationWriter ->
new OperationGenerator(ctx, operationWriter, operation, protocolGenerator, runtimePlugins)
.run());
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* Copyright 2024 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;

import static software.amazon.smithy.go.codegen.util.ShapeUtil.STRING_SHAPE;
import static software.amazon.smithy.go.codegen.util.ShapeUtil.expectMember;
import static software.amazon.smithy.go.codegen.util.ShapeUtil.listOf;
import static software.amazon.smithy.utils.StringUtils.capitalize;

import java.util.List;
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.jmespath.JmespathExpression;
import software.amazon.smithy.jmespath.ast.FieldExpression;
import software.amazon.smithy.jmespath.ast.FunctionExpression;
import software.amazon.smithy.jmespath.ast.ProjectionExpression;
import software.amazon.smithy.jmespath.ast.Subexpression;
import software.amazon.smithy.model.shapes.ListShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.utils.SmithyInternalApi;

/**
* Traverses a JMESPath expression, producing a series of statements that evaluate the entire expression. The generator
* is shape-aware and the return indicates the underlying shape being referenced in the final result.
* <br/>
* Note that the use of writer.write() here is deliberate, it's easier to structure the code in that way instead of
* trying to recursively compose/organize Writable templates.
*/
@SmithyInternalApi
public class GoJmespathExpressionGenerator {
private final GoCodegenContext ctx;
private final GoWriter writer;
private final Shape input;
private final JmespathExpression root;

private int idIndex = 1;

public GoJmespathExpressionGenerator(GoCodegenContext ctx, GoWriter writer, Shape input, JmespathExpression expr) {
this.ctx = ctx;
this.writer = writer;
this.input = input;
this.root = expr;
}

public Result generate(String ident) {
writer.write("v1 := $L", ident);
return visit(root, input);
}

private Result visit(JmespathExpression expr, Shape current) {
if (expr instanceof FunctionExpression) {
return visitFunction((FunctionExpression) expr, current);
} else if (expr instanceof FieldExpression) {
return visitField((FieldExpression) expr, current);
} else if (expr instanceof Subexpression) {
return visitSub((Subexpression) expr, current);
} else if (expr instanceof ProjectionExpression) {
return visitProjection((ProjectionExpression) expr, current);
} else {
throw new CodegenException("unhandled jmespath expression " + expr.getClass().getSimpleName());
}
}

private Result visitProjection(ProjectionExpression expr, Shape current) {
var left = visit(expr.getLeft(), current);

// left of projection HAS to be an array by spec, otherwise something is wrong
if (!left.shape.isListShape()) {
throw new CodegenException("left side of projection did not create a list");
}

var leftMember = expectMember(ctx.model(), (ListShape) left.shape);

// We have to know the element type for the list that we're generating, use a dummy writer to "peek" ahead and
// get the traversal result
var lookahead = new GoJmespathExpressionGenerator(ctx, new GoWriter(""), leftMember, expr.getRight())
.generate("v");

++idIndex;
writer.write("""
var v$L []$P
for _, v := range $L {""", idIndex, ctx.symbolProvider().toSymbol(lookahead.shape), left.ident);

// new scope inside loop, but now we actually want to write the contents
// projected.shape is the _member_ of the resulting list
var projected = new GoJmespathExpressionGenerator(ctx, writer, leftMember, expr.getRight())
.generate("v");

writer.write("v$1L = append(v$1L, $2L)", idIndex, projected.ident);
writer.write("}");

return new Result(listOf(projected.shape), "v" + idIndex);
}

private Result visitSub(Subexpression expr, Shape current) {
var left = visit(expr.getLeft(), current);
return visit(expr.getRight(), left.shape);
}

private Result visitField(FieldExpression expr, Shape current) {
++idIndex;
writer.write("v$L := v$L.$L", idIndex, idIndex - 1, capitalize(expr.getName()));
return new Result(expectMember(ctx.model(), current, expr.getName()), "v" + idIndex);
}

private Result visitFunction(FunctionExpression expr, Shape current) {
return switch (expr.name) {
case "keys" -> visitKeysFunction(expr.arguments, current);
default -> throw new CodegenException("unsupported function " + expr.name);
};
}

private Result visitKeysFunction(List<JmespathExpression> args, Shape current) {
if (args.size() != 1) {
throw new CodegenException("unexpected keys() arg length " + args.size());
}

var arg = visit(args.get(0), current);
++idIndex;
writer.write("""
var v$1L []string
for k := range $2L {
v$1L = append(v$1L, k)
}""", idIndex, arg.ident);

return new Result(listOf(STRING_SHAPE), "v" + idIndex);
}

public record Result(Shape shape, String ident) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,43 +33,37 @@
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.traits.DeprecatedTrait;
import software.amazon.smithy.model.traits.StreamingTrait;
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;

/**
* Generates a client operation and associated custom shapes.
*/
public final class OperationGenerator implements Runnable {

private final GoSettings settings;
private final GoCodegenContext ctx;
private final Model model;
private final SymbolProvider symbolProvider;
private final GoWriter writer;
private final ServiceShape service;
private final OperationShape operation;
private final Symbol operationSymbol;
private final ApplicationProtocol applicationProtocol;
private final ProtocolGenerator protocolGenerator;
private final List<RuntimeClientPlugin> runtimeClientPlugins;

OperationGenerator(
GoSettings settings,
Model model,
SymbolProvider symbolProvider,
GoCodegenContext ctx,
GoWriter writer,
ServiceShape service,
OperationShape operation,
Symbol operationSymbol,
ApplicationProtocol applicationProtocol,
ProtocolGenerator protocolGenerator,
List<RuntimeClientPlugin> runtimeClientPlugins
) {
this.settings = settings;
this.model = model;
this.symbolProvider = symbolProvider;
this.ctx = ctx;
this.model = ctx.model();
this.symbolProvider = ctx.symbolProvider();
this.writer = writer;
this.service = service;
this.service = ctx.settings().getService(ctx.model());
this.operation = operation;
this.operationSymbol = operationSymbol;
this.applicationProtocol = applicationProtocol;
this.operationSymbol = ctx.symbolProvider().toSymbol(operation);
this.protocolGenerator = protocolGenerator;
this.runtimeClientPlugins = runtimeClientPlugins;
}
Expand Down Expand Up @@ -131,11 +125,11 @@ public void run() {
.renderStructure(() -> {
}, true);

writer.write("""
$W
""",
new EndpointParameterOperationBindingsGenerator(operation, inputShape, inputSymbol).generate()
);
var rulesTrait = service.getTrait(EndpointRuleSetTrait.class);
if (rulesTrait.isPresent()) {
writer.write(new EndpointParameterOperationBindingsGenerator(ctx, operation, inputShape)
.generate());
}

// The output structure gets a metadata member added.
Symbol metadataSymbol = SymbolUtils.createValueSymbolBuilder("Metadata", SmithyGoDependency.SMITHY_MIDDLEWARE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ private static GoDependency smithy(String relativePath, String alias) {
}

private static GoDependency goJmespath(String relativePath) {
return relativePackage(GO_JMESPATH_SOURCE_PATH, relativePath, Versions.GO_JMESPATH, null);
return relativePackage(GO_JMESPATH_SOURCE_PATH, relativePath, Versions.GO_JMESPATH, "jmespath");
}

private static GoDependency relativePackage(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -227,4 +227,11 @@ public static boolean isNilable(Symbol symbol) {
public static Symbol pointerTo(Symbol symbol) {
return symbol.toBuilder().putProperty(POINTABLE, true).build();
}

public static Symbol sliceOf(Symbol symbol) {
return symbol.toBuilder()
.putProperty(GO_SLICE, true)
.putProperty(GO_ELEMENT_TYPE, symbol)
.build();
}
}
Loading

0 comments on commit 5d78ff9

Please sign in to comment.