Skip to content
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

add codegen support for string array and operation context params in endpoint bindings #519

Merged
merged 2 commits into from
Jun 26, 2024
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
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should really have tests for this class

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 tExpr) {
return visitFunction(tExpr, current);
} else if (expr instanceof FieldExpression tExpr) {
return visitField(tExpr, current);
} else if (expr instanceof Subexpression tExpr) {
return visitSub(tExpr, current);
} else if (expr instanceof ProjectionExpression tExpr) {
return visitProjection(tExpr, 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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not quite sure I follow why we increase the index when visiting the projection. Each field increases the index because we're visiting a concrete field, but why do we increase it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a new identifier for the slice variable that holds the result of the overall projection (it's declared in the following write()).

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) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<3

}
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
Loading