diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGenerator.java
index 3bff1f18..9206ecbd 100644
--- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGenerator.java
+++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGenerator.java
@@ -15,25 +15,44 @@
package software.amazon.smithy.go.codegen;
+import static software.amazon.smithy.go.codegen.GoWriter.goTemplate;
+import static software.amazon.smithy.go.codegen.SymbolUtils.isPointable;
+import static software.amazon.smithy.go.codegen.SymbolUtils.sliceOf;
+import static software.amazon.smithy.go.codegen.util.ShapeUtil.BOOL_SHAPE;
+import static software.amazon.smithy.go.codegen.util.ShapeUtil.INT_SHAPE;
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 java.util.Map;
import software.amazon.smithy.codegen.core.CodegenException;
+import software.amazon.smithy.codegen.core.Symbol;
+import software.amazon.smithy.go.codegen.util.ShapeUtil;
import software.amazon.smithy.jmespath.JmespathExpression;
+import software.amazon.smithy.jmespath.ast.AndExpression;
+import software.amazon.smithy.jmespath.ast.ComparatorExpression;
+import software.amazon.smithy.jmespath.ast.ComparatorType;
+import software.amazon.smithy.jmespath.ast.CurrentExpression;
import software.amazon.smithy.jmespath.ast.FieldExpression;
+import software.amazon.smithy.jmespath.ast.FilterProjectionExpression;
+import software.amazon.smithy.jmespath.ast.FlattenExpression;
import software.amazon.smithy.jmespath.ast.FunctionExpression;
+import software.amazon.smithy.jmespath.ast.LiteralExpression;
+import software.amazon.smithy.jmespath.ast.MultiSelectListExpression;
+import software.amazon.smithy.jmespath.ast.NotExpression;
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.CollectionShape;
+import software.amazon.smithy.model.shapes.MapShape;
+import software.amazon.smithy.model.shapes.NumberShape;
import software.amazon.smithy.model.shapes.Shape;
+import software.amazon.smithy.model.shapes.StringShape;
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.
+ * is shape-aware and the return indicates the underlying shape/symbol being referenced in the final result.
*
* 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.
@@ -42,24 +61,19 @@
public class GoJmespathExpressionGenerator {
private final GoCodegenContext ctx;
private final GoWriter writer;
- private final Shape input;
- private final JmespathExpression root;
- private int idIndex = 1;
+ private int idIndex = 0;
- public GoJmespathExpressionGenerator(GoCodegenContext ctx, GoWriter writer, Shape input, JmespathExpression expr) {
+ public GoJmespathExpressionGenerator(GoCodegenContext ctx, GoWriter writer) {
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);
+ public Variable generate(JmespathExpression expr, Variable input) {
+ return visit(expr, input);
}
- private Result visit(JmespathExpression expr, Shape current) {
+ private Variable visit(JmespathExpression expr, Variable current) {
if (expr instanceof FunctionExpression tExpr) {
return visitFunction(tExpr, current);
} else if (expr instanceof FieldExpression tExpr) {
@@ -68,61 +82,257 @@ private Result visit(JmespathExpression expr, Shape current) {
return visitSub(tExpr, current);
} else if (expr instanceof ProjectionExpression tExpr) {
return visitProjection(tExpr, current);
+ } else if (expr instanceof FlattenExpression tExpr) {
+ return visitFlatten(tExpr, current);
+ } else if (expr instanceof ComparatorExpression tExpr) {
+ return visitComparator(tExpr, current);
+ } else if (expr instanceof LiteralExpression tExpr) {
+ return visitLiteral(tExpr);
+ } else if (expr instanceof AndExpression tExpr) {
+ return visitAnd(tExpr, current);
+ } else if (expr instanceof NotExpression tExpr) {
+ return visitNot(tExpr, current);
+ } else if (expr instanceof FilterProjectionExpression tExpr) {
+ return visitFilterProjection(tExpr, current);
+ } else if (expr instanceof MultiSelectListExpression tExpr) {
+ return visitMultiSelectList(tExpr, current);
+ } else if (expr instanceof CurrentExpression) {
+ return current;
} else {
throw new CodegenException("unhandled jmespath expression " + expr.getClass().getSimpleName());
}
}
- private Result visitProjection(ProjectionExpression expr, Shape current) {
+ private Variable visitNot(NotExpression expr, Variable current) {
+ var inner = visit(expr.getExpression(), current);
+ var ident = nextIdent();
+ writer.write("$L := !$L", ident, inner.ident);
+ return new Variable(BOOL_SHAPE, ident, GoUniverseTypes.Bool);
+ }
+
+ private Variable visitMultiSelectList(MultiSelectListExpression expr, Variable current) {
+ if (expr.getExpressions().isEmpty()) {
+ throw new CodegenException("multi-select list w/ no expressions");
+ }
+
+ var items = expr.getExpressions().stream()
+ .map(it -> visit(it, current))
+ .toList();
+ var first = items.get(0);
+
+ var ident = nextIdent();
+ writer.write("$L := []$P{$L}", ident, first.type,
+ String.join(",", items.stream().map(it -> it.ident).toList()));
+
+ return new Variable(listOf(first.shape), ident, sliceOf(first.type));
+ }
+
+ private Variable visitFilterProjection(FilterProjectionExpression expr, Variable current) {
+ var unfiltered = visitProjection(new ProjectionExpression(expr.getLeft(), expr.getRight()), current);
+ if (!(unfiltered.shape instanceof CollectionShape unfilteredCol)) {
+ throw new CodegenException("projection did not create a list");
+ }
+
+ var member = expectMember(unfilteredCol);
+ var type = ctx.symbolProvider().toSymbol(unfiltered.shape);
+
+ var ident = nextIdent();
+ writer.write("var $L $T", ident, type);
+ writer.openBlock("for _, v := range $L {", "}", unfiltered.ident, () -> {
+ var filterResult = visit(expr.getComparison(), new Variable(member, "v", type));
+ writer.write("""
+ if $1L {
+ $2L = append($2L, v)
+ }""", filterResult.ident, ident);
+ });
+
+ return new Variable(unfiltered.shape, ident, type);
+ }
+
+ private Variable visitAnd(AndExpression expr, Variable current) {
+ var left = visit(expr.getLeft(), current);
+ var right = visit(expr.getRight(), current);
+ var ident = nextIdent();
+ writer.write("$L := $L && $L", ident, left.ident, right.ident);
+ return new Variable(BOOL_SHAPE, ident, GoUniverseTypes.Bool);
+ }
+
+ private Variable visitLiteral(LiteralExpression expr) {
+ var ident = nextIdent();
+ if (expr.isNumberValue()) {
+ // FUTURE: recognize floating-point, for now we just use int
+ writer.write("$L := $L", ident, expr.expectNumberValue().intValue());
+ return new Variable(INT_SHAPE, ident, GoUniverseTypes.Int);
+ } else if (expr.isStringValue()) {
+ writer.write("$L := $S", ident, expr.expectStringValue());
+ return new Variable(STRING_SHAPE, ident, GoUniverseTypes.String);
+ } else if (expr.isBooleanValue()) {
+ writer.write("$L := $L", ident, expr.expectBooleanValue());
+ return new Variable(STRING_SHAPE, ident, GoUniverseTypes.Bool);
+ } else {
+ throw new CodegenException("unhandled literal expression " + expr.getValue());
+ }
+ }
+
+ private Variable visitComparator(ComparatorExpression expr, Variable current) {
var left = visit(expr.getLeft(), current);
+ var right = visit(expr.getRight(), current);
+
+ String cast;
+ if (left.shape instanceof StringShape) {
+ cast = "string";
+ } else if (left.shape instanceof NumberShape) {
+ cast = "int64";
+ } else {
+ throw new CodegenException("don't know how to compare shape type" + left.shape.getType());
+ }
+
+ var ident = nextIdent();
+ writer.write(compareVariables(ident, left, right, expr.getComparator(), cast));
+ return new Variable(BOOL_SHAPE, ident, GoUniverseTypes.Bool);
+ }
+
+ private Variable visitFlatten(FlattenExpression tExpr, Variable current) {
+ var inner = visit(tExpr.getExpression(), current);
+
+ // inner HAS to be a list by spec, otherwise something is wrong
+ if (!(inner.shape instanceof CollectionShape innerList)) {
+ throw new CodegenException("left side of projection did not create a list");
+ }
+
+ // inner expression may not be a list-of-list - if so, we're done, the result is passed up as-is
+ var innerMember = expectMember(innerList);
+ if (!(innerMember instanceof CollectionShape)) {
+ return inner;
+ }
+
+ var innerSymbol = ctx.symbolProvider().toSymbol(innerMember);
+ var ident = nextIdent();
+ writer.write("""
+ var $1L $3P
+ for _, v := range $2L {
+ $1L = append($1L, v...)
+ }""", ident, inner.ident, innerSymbol);
+ return new Variable(innerMember, ident, innerSymbol);
+ }
+
+ private Variable visitProjection(ProjectionExpression expr, Variable current) {
+ var left = visit(expr.getLeft(), current);
+ if (expr.getRight() instanceof CurrentExpression) { // e.g. "Field[]" - the projection is just itself
+ return left;
+ }
- // left of projection HAS to be an array by spec, otherwise something is wrong
- if (!left.shape.isListShape()) {
+ Shape leftMember;
+ if (left.shape instanceof CollectionShape col) {
+ leftMember = expectMember(col);
+ } else if (left.shape instanceof MapShape map) {
+ leftMember = expectMember(map);
+ } else {
+ // left of projection HAS to be an array/map by spec, otherwise something is wrong
throw new CodegenException("left side of projection did not create a list");
}
- var leftMember = expectMember(ctx.model(), (ListShape) left.shape);
+ var leftSymbol = ctx.symbolProvider().toSymbol(leftMember);
// 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");
+ var lookahead = new GoJmespathExpressionGenerator(ctx, new GoWriter(""))
+ .generate(expr.getRight(), new Variable(leftMember, "v", leftSymbol));
- ++idIndex;
+ var ident = nextIdent();
+ // projections implicitly filter out nil evaluations of RHS
+ var needsDeref = isPointable(lookahead.type);
writer.write("""
- var v$L []$P
- for _, v := range $L {""", idIndex, ctx.symbolProvider().toSymbol(lookahead.shape), left.ident);
+ var $L []$T
+ for _, v := range $L {""", ident, ctx.symbolProvider().toSymbol(lookahead.shape), left.ident);
- // new scope inside loop, but now we actually want to write the contents
+ writer.indent();
// 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);
+ var projected = visit(expr.getRight(), new Variable(leftMember, "v", leftSymbol));
+ if (needsDeref) {
+ writer.write("""
+ if $2L != nil {
+ $1L = append($1L, *$2L)
+ }""", ident, projected.ident);
+ } else {
+ writer.write("$1L = append($1L, $2L)", ident, projected.ident);
+ }
+ writer.dedent();
writer.write("}");
- return new Result(listOf(projected.shape), "v" + idIndex);
+ return new Variable(listOf(projected.shape), ident, sliceOf(ctx.symbolProvider().toSymbol(projected.shape)));
}
- private Result visitSub(Subexpression expr, Shape current) {
+ private Variable visitSub(Subexpression expr, Variable current) {
var left = visit(expr.getLeft(), current);
- return visit(expr.getRight(), left.shape);
+ return visit(expr.getRight(), left);
}
- 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 Variable visitField(FieldExpression expr, Variable current) {
+ var optMember = current.shape.getMember(expr.getName());
+ if (optMember.isEmpty()) {
+ throw new CodegenException("expected member " + expr.getName() + " in shape " + current.shape);
+ }
+
+ var member = optMember.get();
+ var target = ctx.model().expectShape(member.getTarget());
+ var ident = nextIdent();
+ writer.write("$L := $L.$L", ident, current.ident, capitalize(expr.getName()));
+ return new Variable(target, ident, ctx.symbolProvider().toSymbol(member));
}
- private Result visitFunction(FunctionExpression expr, Shape current) {
+ private Variable visitFunction(FunctionExpression expr, Variable current) {
return switch (expr.name) {
case "keys" -> visitKeysFunction(expr.arguments, current);
+ case "length" -> visitLengthFunction(expr.arguments, current);
+ case "contains" -> visitContainsFunction(expr.arguments, current);
default -> throw new CodegenException("unsupported function " + expr.name);
};
}
- private Result visitKeysFunction(List args, Shape current) {
+ private Variable visitContainsFunction(List args, Variable current) {
+ if (args.size() != 2) {
+ throw new CodegenException("unexpected contains() arg length " + args.size());
+ }
+
+ var list = visit(args.get(0), current);
+ var item = visit(args.get(1), current);
+ var ident = nextIdent();
+ writer.write("""
+ var $1L bool
+ for _, v := range $2L {
+ if v == $3L {
+ $1L = true
+ break
+ }
+ }""", ident, list.ident, item.ident);
+ return new Variable(BOOL_SHAPE, ident, GoUniverseTypes.Bool);
+ }
+
+ private Variable visitLengthFunction(List args, Variable current) {
+ if (args.size() != 1) {
+ throw new CodegenException("unexpected length() arg length " + args.size());
+ }
+
+ var arg = visit(args.get(0), current);
+ var ident = nextIdent();
+
+ // length() can be used on a string (so also *string) - dereference if required
+ if (arg.shape instanceof StringShape && isPointable(arg.type)) {
+ writer.write("""
+ var _$1L string
+ if $1L != nil {
+ _$1L = *$1L
+ }
+ $2L := len(_$1L)""", arg.ident, ident);
+ } else {
+ writer.write("$L := len($L)", ident, arg.ident);
+ }
+
+ return new Variable(INT_SHAPE, ident, GoUniverseTypes.Int);
+ }
+
+ private Variable visitKeysFunction(List args, Variable current) {
if (args.size() != 1) {
throw new CodegenException("unexpected keys() arg length " + args.size());
}
@@ -135,8 +345,71 @@ private Result visitKeysFunction(List args, Shape current) {
v$1L = append(v$1L, k)
}""", idIndex, arg.ident);
- return new Result(listOf(STRING_SHAPE), "v" + idIndex);
+ return new Variable(listOf(STRING_SHAPE), "v" + idIndex, sliceOf(GoUniverseTypes.String));
+ }
+
+ private String nextIdent() {
+ ++idIndex;
+ return "v" + idIndex;
+ }
+
+ private Shape expectMember(CollectionShape shape) {
+ return switch (shape.getMember().getTarget().toString()) {
+ case "smithy.go.synthetic#StringList" -> listOf(STRING_SHAPE);
+ case "smithy.go.synthetic#IntegerList" -> listOf(INT_SHAPE);
+ case "smithy.go.synthetic#BooleanList" -> listOf(BOOL_SHAPE);
+ default -> ShapeUtil.expectMember(ctx.model(), shape);
+ };
}
- public record Result(Shape shape, String ident) {}
+ private Shape expectMember(MapShape shape) {
+ return switch (shape.getValue().getTarget().toString()) {
+ case "smithy.go.synthetic#StringList" -> listOf(STRING_SHAPE);
+ case "smithy.go.synthetic#IntegerList" -> listOf(INT_SHAPE);
+ case "smithy.go.synthetic#BooleanList" -> listOf(BOOL_SHAPE);
+ default -> ShapeUtil.expectMember(ctx.model(), shape);
+ };
+ }
+
+ // helper to generate comparisons from two results, automatically handling any dereferencing in the process
+ private GoWriter.Writable compareVariables(String ident, Variable left, Variable right, ComparatorType cmp,
+ String cast) {
+ var isLPtr = isPointable(left.type);
+ var isRPtr = isPointable(right.type);
+ if (!isLPtr && !isRPtr) {
+ return goTemplate("$1L := $5L($2L) $4L $5L($3L)", ident, left.ident, right.ident, cmp, cast);
+ }
+
+ return goTemplate("""
+ var $ident:L bool
+ if $lif:L $amp:L $rif:L {
+ $ident:L = $cast:L($lhs:L) $cmp:L $cast:L($rhs:L)
+ }""",
+ Map.of(
+ "ident", ident,
+ "lif", isLPtr ? left.ident + " != nil" : "",
+ "rif", isRPtr ? right.ident + " != nil" : "",
+ "amp", isLPtr && isRPtr ? "&&" : "",
+ "cmp", cmp,
+ "lhs", isLPtr ? "*" + left.ident : left.ident,
+ "rhs", isRPtr ? "*" + right.ident : right.ident,
+ "cast", cast
+ ));
+ }
+
+ /**
+ * Represents a variable (input, intermediate, or final output) of a JMESPath traversal.
+ * @param shape The underlying shape referenced by this variable. For certain jmespath expressions (e.g.
+ * LiteralExpression) the value here is a synthetic shape and does not necessarily have meaning.
+ * @param ident The identifier of the variable in the generated traversal.
+ * @param type The symbol that records the type of the variable. This does NOT necessarily correspond to the result
+ * of toSymbol(shape) because certain jmespath expressions (such as projections) may affect the type of
+ * the resulting variable in a way that severs that relationship. The caller MUST use this field to
+ * determine whether the variable is pointable/nillable.
+ */
+ public record Variable(Shape shape, String ident, Symbol type) {
+ public Variable(Shape shape, String ident) {
+ this(shape, ident, null);
+ }
+ }
}
diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterOperationBindingsGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterOperationBindingsGenerator.java
index faf9980c..5a63aad4 100644
--- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterOperationBindingsGenerator.java
+++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterOperationBindingsGenerator.java
@@ -107,10 +107,10 @@ private GoWriter.Writable generateOpContextParamBinding(String paramName, Operat
var expr = JmespathExpression.parse(def.getPath());
return writer -> {
- var generator = new GoJmespathExpressionGenerator(ctx, writer, input, expr);
+ var generator = new GoJmespathExpressionGenerator(ctx, writer);
writer.write("func() {"); // contain the scope for each binding
- var result = generator.generate("in");
+ var result = generator.generate(expr, new GoJmespathExpressionGenerator.Variable(input, "in"));
if (param.getType().equals(ParameterType.STRING_ARRAY)) {
// projections can result in either []string OR []*string -- if the latter, we have to unwrap
diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Waiters.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Waiters.java
index b23be030..02ce3a29 100644
--- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Waiters.java
+++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/integration/Waiters.java
@@ -18,6 +18,7 @@
import static java.util.Collections.emptySet;
import static software.amazon.smithy.go.codegen.GoWriter.autoDocTemplate;
import static software.amazon.smithy.go.codegen.GoWriter.goTemplate;
+import static software.amazon.smithy.go.codegen.SymbolUtils.isPointable;
import java.util.Map;
import java.util.Optional;
@@ -27,18 +28,16 @@
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.go.codegen.ClientOptions;
import software.amazon.smithy.go.codegen.GoCodegenContext;
+import software.amazon.smithy.go.codegen.GoJmespathExpressionGenerator;
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.jmespath.JmespathExpression;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.TopDownIndex;
-import software.amazon.smithy.model.shapes.ListShape;
-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.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
-import software.amazon.smithy.model.shapes.SimpleShape;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.utils.StringUtils;
import software.amazon.smithy.waiters.Acceptor;
@@ -80,14 +79,13 @@ public void writeAdditionalFiles(GoCodegenContext ctx) {
private void generateOperationWaiter(GoCodegenContext ctx, OperationShape operation, Map waiters) {
var model = ctx.model();
var symbolProvider = ctx.symbolProvider();
- var service = ctx.settings().getService(model);
ctx.writerDelegator().useShapeWriter(operation, writer -> {
waiters.forEach((name, waiter) -> {
generateWaiterOptions(model, symbolProvider, writer, operation, name, waiter);
generateWaiterClient(model, symbolProvider, writer, operation, name, waiter);
generateWaiterInvoker(model, symbolProvider, writer, operation, name, waiter);
generateWaiterInvokerWithOutput(model, symbolProvider, writer, operation, name, waiter);
- generateRetryable(model, symbolProvider, writer, service, operation, name, waiter);
+ generateRetryable(ctx, writer, operation, name, waiter);
});
});
}
@@ -495,23 +493,22 @@ private void generateWaiterInvokerWithOutput(
* Generates a waiter state mutator function which is used by the waiter retrier Middleware to mutate
* waiter state as per the defined logic and returned operation response.
*
- * @param model the smithy model
- * @param symbolProvider symbol provider
+ * @param ctx the GoCodegenContext
* @param writer the Gowriter
- * @param serviceShape service shape for which operation waiter is modeled
* @param operationShape operation shape on which the waiter is modeled
* @param waiterName the waiter name
* @param waiter the waiter structure that contains info on modeled waiter
*/
private void generateRetryable(
- Model model,
- SymbolProvider symbolProvider,
+ GoCodegenContext ctx,
GoWriter writer,
- ServiceShape serviceShape,
OperationShape operationShape,
String waiterName,
Waiter waiter
) {
+ var model = ctx.model();
+ var symbolProvider = ctx.symbolProvider();
+ var serviceShape = ctx.settings().getService(model);
StructureShape inputShape = model.expectShape(
operationShape.getInput().get(), StructureShape.class
);
@@ -531,7 +528,6 @@ private void generateRetryable(
Matcher matcher = acceptor.getMatcher();
switch (matcher.getMemberName()) {
case "output":
- writer.addUseImports(SmithyGoDependency.GO_JMESPATH);
writer.addUseImports(SmithyGoDependency.FMT);
Matcher.OutputMember outputMember = (Matcher.OutputMember) matcher;
@@ -539,39 +535,12 @@ private void generateRetryable(
String expectedValue = outputMember.getValue().getExpected();
PathComparator comparator = outputMember.getValue().getComparator();
writer.openBlock("if err == nil {", "}", () -> {
- writer.write("pathValue, err := jmespath.Search($S, output)", path);
- writer.openBlock("if err != nil {", "}", () -> {
- writer.write(
- "return false, "
- + "fmt.Errorf(\"error evaluating waiter state: %w\", err)");
- }).write("");
- writer.write("expectedValue := $S", expectedValue);
+ var pathInput = new GoJmespathExpressionGenerator.Variable(outputShape, "output");
+ var searchResult = new GoJmespathExpressionGenerator(ctx, writer)
+ .generate(JmespathExpression.parse(path), pathInput);
- if (comparator == PathComparator.BOOLEAN_EQUALS) {
- writeWaiterComparator(writer, acceptor, comparator, null, "pathValue",
- "expectedValue");
- } else {
- String[] pathMembers = path.split("\\.");
- Shape targetShape = outputShape;
- for (int i = 0; i < pathMembers.length; i++) {
- MemberShape member = getComparedMember(model, targetShape, pathMembers[i]);
- if (member == null) {
- targetShape = null;
- break;
- }
- targetShape = model.expectShape(member.getTarget());
- }
-
- if (targetShape == null) {
- writeWaiterComparator(writer, acceptor, comparator, null, "pathValue",
- "expectedValue");
- } else {
- Symbol targetSymbol = symbolProvider.toSymbol(targetShape);
- writeWaiterComparator(writer, acceptor, comparator, targetSymbol,
- "pathValue",
- "expectedValue");
- }
- }
+ writer.write("expectedValue := $S", expectedValue);
+ writeWaiterComparator(writer, acceptor, comparator, searchResult);
});
break;
@@ -583,22 +552,32 @@ private void generateRetryable(
path = ioMember.getValue().getPath();
expectedValue = ioMember.getValue().getExpected();
comparator = ioMember.getValue().getComparator();
+
+ // inputOutput matchers operate on a synthetic structure with operation input and output
+ // as top-level fields - we set that up here both in codegen for jmespathing and for
+ // the actual generated code to work
+ var inputOutputShape = StructureShape.builder()
+ .addMember("input", inputShape.toShapeId())
+ .addMember("output", outputShape.toShapeId())
+ .build();
+ writer.write("""
+ inputOutput := struct{
+ Input $P
+ Output $P
+ }{
+ Input: input,
+ Output: output,
+ }
+ """);
+
writer.openBlock("if err == nil {", "}", () -> {
- writer.openBlock("pathValue, err := jmespath.Search($S, &struct{",
- "})", path, () -> {
- writer.write("Input $P \n Output $P \n }{", inputSymbol,
- outputSymbol);
- writer.write("Input: input, \n Output: output, \n");
- });
- writer.openBlock("if err != nil {", "}", () -> {
- writer.write(
- "return false, "
- + "fmt.Errorf(\"error evaluating waiter state: %w\", err)");
- });
- writer.write("");
+ var pathInput = new GoJmespathExpressionGenerator.Variable(
+ inputOutputShape, "inputOutput");
+ var searchResult = new GoJmespathExpressionGenerator(ctx, writer)
+ .generate(JmespathExpression.parse(path), pathInput);
+
writer.write("expectedValue := $S", expectedValue);
- writeWaiterComparator(writer, acceptor, comparator, outputSymbol, "pathValue",
- "expectedValue");
+ writeWaiterComparator(writer, acceptor, comparator, searchResult);
});
break;
@@ -666,87 +645,44 @@ private void generateRetryable(
});
}
- /**
- * writes comparators for a given waiter. The comparators are defined within the waiter acceptor.
- *
- * @param writer the Gowriter
- * @param acceptor the waiter acceptor that defines the comparator and acceptor states
- * @param comparator the comparator
- * @param targetSymbol the shape symbol of the compared type.
- * @param actual the variable carrying the actual value obtained.
- * This may be computed via a jmespath expression or operation response status (success/failure)
- * @param expected the variable carrying the expected value. This value is as per the modeled waiter.
- */
- private void writeWaiterComparator(
- GoWriter writer,
- Acceptor acceptor,
- PathComparator comparator,
- Symbol targetSymbol,
- String actual,
- String expected
- ) {
- if (targetSymbol == null) {
- targetSymbol = SymbolUtils.createValueSymbolBuilder("string").build();
- }
-
- String valueAccessor = "string(value)";
- Optional isPointable = targetSymbol.getProperty(SymbolUtils.POINTABLE, Boolean.class);
- if (isPointable.isPresent() && isPointable.get().booleanValue()) {
- valueAccessor = "string(*value)";
- }
-
+ private void writeWaiterComparator(GoWriter writer, Acceptor acceptor, PathComparator comparator,
+ GoJmespathExpressionGenerator.Variable searchResult) {
switch (comparator) {
case STRING_EQUALS:
- writer.write("value, ok := $L.($P)", actual, targetSymbol);
- writer.write("if !ok {");
- writer.write("return false, fmt.Errorf(\"waiter comparator expected $P value, got %T\", $L)}",
- targetSymbol, actual);
- writer.write("");
-
- writer.openBlock("if $L == $L {", "}", valueAccessor, expected, () -> {
+ writer.write("var pathValue string");
+ if (!isPointable(searchResult.type())) {
+ writer.write("pathValue = string($L)", searchResult.ident());
+ } else {
+ writer.write("""
+ if $1L != nil {
+ pathValue = string(*$1L)
+ }""", searchResult.ident());
+ }
+ writer.openBlock("if pathValue == expectedValue {", "}", () -> {
writeMatchedAcceptorReturn(writer, acceptor);
});
break;
case BOOLEAN_EQUALS:
writer.addUseImports(SmithyGoDependency.STRCONV);
- writer.write("bv, err := strconv.ParseBool($L)", expected);
+ writer.write("bv, err := strconv.ParseBool($L)", "expectedValue");
writer.write(
"if err != nil { return false, "
+ "fmt.Errorf(\"error parsing boolean from string %w\", err)}");
- writer.write("value, ok := $L.(bool)", actual);
- writer.openBlock(" if !ok {", "}", () -> {
- writer.write("return false, "
- + "fmt.Errorf(\"waiter comparator expected bool value got %T\", $L)", actual);
- });
- writer.write("");
-
- writer.openBlock("if value == bv {", "}", () -> {
+ writer.openBlock("if $L == bv {", "}", searchResult.ident(), () -> {
writeMatchedAcceptorReturn(writer, acceptor);
});
break;
case ALL_STRING_EQUALS:
- writer.write("var match = true");
- writer.write("listOfValues, ok := $L.([]interface{})", actual);
- writer.openBlock(" if !ok {", "}", () -> {
- writer.write("return false, "
- + "fmt.Errorf(\"waiter comparator expected list got %T\", $L)", actual);
- });
- writer.write("");
-
- writer.write("if len(listOfValues) == 0 { match = false }");
-
- String allStringValueAccessor = valueAccessor;
- Symbol allStringTargetSymbol = targetSymbol;
- writer.openBlock("for _, v := range listOfValues {", "}", () -> {
- writer.write("value, ok := v.($P)", allStringTargetSymbol);
- writer.write("if !ok {");
- writer.write("return false, fmt.Errorf(\"waiter comparator expected $P value, got %T\", $L)}",
- allStringTargetSymbol, actual);
- writer.write("");
- writer.write("if $L != $L { match = false }", allStringValueAccessor, expected);
+ writer.write("match := len($L) > 0", searchResult.ident());
+ writer.openBlock("for _, v := range $L {", "}", searchResult.ident(), () -> {
+ writer.write("""
+ if string(v) != expectedValue {
+ match = false
+ break
+ }""");
});
writer.write("");
@@ -756,24 +692,18 @@ private void writeWaiterComparator(
break;
case ANY_STRING_EQUALS:
- writer.write("listOfValues, ok := $L.([]interface{})", actual);
- writer.openBlock(" if !ok {", "}", () -> {
- writer.write("return false, "
- + "fmt.Errorf(\"waiter comparator expected list got %T\", $L)", actual);
+ writer.write("var match bool");
+ writer.openBlock("for _, v := range $L {", "}", searchResult.ident(), () -> {
+ writer.write("""
+ if string(v) == expectedValue {
+ match = true
+ break
+ }""");
});
writer.write("");
- String anyStringValueAccessor = valueAccessor;
- Symbol anyStringTargetSymbol = targetSymbol;
- writer.openBlock("for _, v := range listOfValues {", "}", () -> {
- writer.write("value, ok := v.($P)", anyStringTargetSymbol);
- writer.write("if !ok {");
- writer.write("return false, fmt.Errorf(\"waiter comparator expected $P value, got %T\", $L)}",
- anyStringTargetSymbol, actual);
- writer.write("");
- writer.openBlock("if $L == $L {", "}", anyStringValueAccessor, expected, () -> {
- writeMatchedAcceptorReturn(writer, acceptor);
- });
+ writer.openBlock("if match {", "}", () -> {
+ writeMatchedAcceptorReturn(writer, acceptor);
});
break;
@@ -830,50 +760,4 @@ private String generateRetryableName(
waiterName = StringUtils.uncapitalize(waiterName);
return String.format("%sStateRetryable", waiterName);
}
-
-
- /**
- * Returns the MemberShape wrt to the provided Shape and name.
- * For eg, If shape `A` has MemberShape `B`, and the name provided is `B` as string.
- * We return the MemberShape `B`.
- *
- * @param model the generation model.
- * @param shape the shape that is walked to retreive the shape matching provided name.
- * @param name name is a single scope path string, and should only match to one or less shapes.
- * @return MemberShape matching the name.
- */
- private MemberShape getComparedMember(Model model, Shape shape, String name) {
-
- name = name.replaceAll("\\[\\]", "");
-
- // if shape is a simple shape, just return shape as member shape
- if (shape instanceof SimpleShape) {
- return shape.asMemberShape().get();
- }
-
- switch (shape.getType()) {
- case STRUCTURE:
- StructureShape st = shape.asStructureShape().get();
- for (MemberShape memberShape : st.getAllMembers().values()) {
- if (name.equalsIgnoreCase(memberShape.getMemberName())) {
- return memberShape;
- }
- }
- break;
-
- case LIST:
- ListShape listShape = shape.asListShape().get();
- MemberShape listMember = listShape.getMember();
- Shape listTarget = model.expectShape(listMember.getTarget());
- return getComparedMember(model, listTarget, name);
-
- default:
- // TODO: add support for * usage with jmespath expression.
- return null;
- }
-
- // TODO: add support for * usage with jmespath expression.
- // return null if no shape type matched (this would happen in case of * usage with jmespath expression).
- return null;
- }
}
diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java
index 68944603..fc03945a 100644
--- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java
+++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java
@@ -17,8 +17,11 @@
import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.BooleanShape;
import software.amazon.smithy.model.shapes.CollectionShape;
+import software.amazon.smithy.model.shapes.IntegerShape;
import software.amazon.smithy.model.shapes.ListShape;
+import software.amazon.smithy.model.shapes.MapShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.StringShape;
@@ -27,6 +30,14 @@ public final class ShapeUtil {
.id("smithy.go.synthetic#String")
.build();
+ public static final IntegerShape INT_SHAPE = IntegerShape.builder()
+ .id("smithy.api#Integer")
+ .build();
+
+ public static final BooleanShape BOOL_SHAPE = BooleanShape.builder()
+ .id("smithy.api#Boolean")
+ .build();
+
private ShapeUtil() {}
public static ListShape listOf(Shape member) {
@@ -49,4 +60,8 @@ public static Shape expectMember(Model model, Shape shape, String memberName) {
public static Shape expectMember(Model model, CollectionShape shape) {
return model.expectShape(shape.getMember().getTarget());
}
+
+ public static Shape expectMember(Model model, MapShape shape) {
+ return model.expectShape(shape.getValue().getTarget());
+ }
}
diff --git a/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGeneratorTest.java b/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGeneratorTest.java
index aecd1060..0e4e1e72 100644
--- a/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGeneratorTest.java
+++ b/codegen/smithy-go-codegen/src/test/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGeneratorTest.java
@@ -16,6 +16,7 @@
package software.amazon.smithy.go.codegen;
import static org.hamcrest.MatcherAssert.assertThat;
+import static software.amazon.smithy.go.codegen.util.ShapeUtil.listOf;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
@@ -33,8 +34,12 @@ public class GoJmespathExpressionGeneratorTest {
namespace smithy.go.test
+ service Test {
+ }
+
structure Struct {
simpleShape: String
+ simpleShape2: String
objectList: ObjectList
objectMap: ObjectMap
nested: NestedStruct
@@ -42,6 +47,11 @@ public class GoJmespathExpressionGeneratorTest {
structure Object {
key: String
+ innerObjectList: InnerObjectList
+ }
+
+ structure InnerObject {
+ innerKey: String
}
structure NestedStruct {
@@ -52,6 +62,10 @@ public class GoJmespathExpressionGeneratorTest {
member: Object
}
+ list InnerObjectList {
+ member: InnerObject
+ }
+
map ObjectMap {
key: String,
value: Object
@@ -63,10 +77,14 @@ public class GoJmespathExpressionGeneratorTest {
.assemble().unwrap();
private static final GoSettings TEST_SETTINGS = GoSettings.from(ObjectNode.fromStringMap(Map.of(
- "service", "smithy.go.test#foo",
+ "service", "smithy.go.test#Test",
"module", "github.com/aws/aws-sdk-go-v2/test"
)));
+ private static GoWriter testWriter() {
+ return new GoWriter("test").setIndentText(" "); // for ease of string comparison
+ }
+
private static GoCodegenContext testContext() {
return new GoCodegenContext(
TEST_MODEL, TEST_SETTINGS,
@@ -79,17 +97,16 @@ private static GoCodegenContext testContext() {
public void testFieldExpression() {
var expr = "simpleShape";
- var writer = new GoWriter("test");
- var generator = new GoJmespathExpressionGenerator(testContext(), writer,
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
- JmespathExpression.parse(expr)
- );
- var actual = generator.generate("input");
+ "input"
+ ));
assertThat(actual.shape(), Matchers.equalTo(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String"))));
- assertThat(actual.ident(), Matchers.equalTo("v2"));
+ assertThat(actual.ident(), Matchers.equalTo("v1"));
assertThat(writer.toString(), Matchers.containsString("""
- v1 := input
- v2 := v1.SimpleShape
+ v1 := input.SimpleShape
"""));
}
@@ -97,18 +114,17 @@ public void testFieldExpression() {
public void testSubexpression() {
var expr = "nested.nestedField";
- var writer = new GoWriter("test");
- var generator = new GoJmespathExpressionGenerator(testContext(), writer,
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
- JmespathExpression.parse(expr)
- );
- var actual = generator.generate("input");
+ "input"
+ ));
assertThat(actual.shape(), Matchers.equalTo(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String"))));
- assertThat(actual.ident(), Matchers.equalTo("v3"));
+ assertThat(actual.ident(), Matchers.equalTo("v2"));
assertThat(writer.toString(), Matchers.containsString("""
- v1 := input
- v2 := v1.Nested
- v3 := v2.NestedField
+ v1 := input.Nested
+ v2 := v1.NestedField
"""));
}
@@ -116,20 +132,19 @@ public void testSubexpression() {
public void testKeysFunctionExpression() {
var expr = "keys(objectMap)";
- var writer = new GoWriter("test");
- var generator = new GoJmespathExpressionGenerator(testContext(), writer,
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
- JmespathExpression.parse(expr)
- );
- var actual = generator.generate("input");
- assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.listOf(ShapeUtil.STRING_SHAPE)));
- assertThat(actual.ident(), Matchers.equalTo("v3"));
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(listOf(ShapeUtil.STRING_SHAPE)));
+ assertThat(actual.ident(), Matchers.equalTo("v2"));
assertThat(writer.toString(), Matchers.containsString("""
- v1 := input
- v2 := v1.ObjectMap
- var v3 []string
- for k := range v2 {
- v3 = append(v3, k)
+ v1 := input.ObjectMap
+ var v2 []string
+ for k := range v1 {
+ v2 = append(v2, k)
}
"""));
}
@@ -138,24 +153,343 @@ public void testKeysFunctionExpression() {
public void testProjectionExpression() {
var expr = "objectList[*].key";
- var writer = new GoWriter("test");
- var generator = new GoJmespathExpressionGenerator(testContext(), writer,
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
- JmespathExpression.parse(expr)
- );
- var actual = generator.generate("input");
+ "input"
+ ));
assertThat(actual.shape(), Matchers.equalTo(
- ShapeUtil.listOf(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String")))));
- assertThat(actual.ident(), Matchers.equalTo("v3"));
+ listOf(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String")))));
+ assertThat(actual.ident(), Matchers.equalTo("v2"));
assertThat(writer.toString(), Matchers.containsString("""
- v1 := input
- v2 := v1.ObjectList
- var v3 []*string
+ v1 := input.ObjectList
+ var v2 []string
+ for _, v := range v1 {
+ v3 := v.Key
+ if v3 != nil {
+ v2 = append(v2, *v3)
+ }
+ }
+ """));
+ }
+
+ @Test
+ public void testNopFlattenExpression() {
+ var expr = "objectList[].key";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(
+ listOf(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String")))));
+ assertThat(actual.ident(), Matchers.equalTo("v2"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.ObjectList
+ var v2 []string
+ for _, v := range v1 {
+ v3 := v.Key
+ if v3 != nil {
+ v2 = append(v2, *v3)
+ }
+ }
+ """));
+ }
+
+ @Test
+ public void testActualFlattenExpression() {
+ var expr = "objectList[].innerObjectList[].innerKey";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(
+ listOf(TEST_MODEL.expectShape(ShapeId.from("smithy.api#String")))));
+ assertThat(actual.ident(), Matchers.equalTo("v5"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.ObjectList
+ var v2 [][]types.InnerObject
+ for _, v := range v1 {
+ v3 := v.InnerObjectList
+ v2 = append(v2, v3)
+ }
+ var v4 []types.InnerObject
for _, v := range v2 {
- v1 := v
- v2 := v1.Key
- v3 = append(v3, v2)
+ v4 = append(v4, v...)
+ }
+ var v5 []string
+ for _, v := range v4 {
+ v6 := v.InnerKey
+ if v6 != nil {
+ v5 = append(v5, *v6)
+ }
}
"""));
}
+
+ @Test
+ public void testLengthFunctionExpression() {
+ var expr = "length(objectList)";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.INT_SHAPE));
+ assertThat(actual.ident(), Matchers.equalTo("v2"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.ObjectList
+ v2 := len(v1)
+ """));
+ }
+
+ @Test
+ public void testLengthFunctionStringPtr() {
+ var expr = "length(simpleShape)";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.INT_SHAPE));
+ assertThat(actual.ident(), Matchers.equalTo("v2"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.SimpleShape
+ var _v1 string
+ if v1 != nil {
+ _v1 = *v1
+ }
+ v2 := len(_v1)
+ """));
+ }
+
+ @Test
+ public void testComparatorInt() {
+ var expr = "length(objectList) > `0`";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.BOOL_SHAPE));
+ assertThat(actual.ident(), Matchers.equalTo("v4"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.ObjectList
+ v2 := len(v1)
+ v3 := 0
+ v4 := int64(v2) > int64(v3)
+ """));
+ }
+
+ @Test
+ public void testComparatorStringLHSNil() {
+ var expr = "nested.nestedField == 'foo'";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.BOOL_SHAPE));
+ assertThat(actual.ident(), Matchers.equalTo("v4"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.Nested
+ v2 := v1.NestedField
+ v3 := "foo"
+ var v4 bool
+ if v2 != nil {
+ v4 = string(*v2) == string(v3)
+ }
+ """));
+ }
+
+ @Test
+ public void testComparatorStringRHSNil() {
+ var expr = "'foo' == nested.nestedField";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.BOOL_SHAPE));
+ assertThat(actual.ident(), Matchers.equalTo("v4"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := "foo"
+ v2 := input.Nested
+ v3 := v2.NestedField
+ var v4 bool
+ if v3 != nil {
+ v4 = string(v1) == string(*v3)
+ }
+ """));
+ }
+
+ @Test
+ public void testComparatorStringBothNil() {
+ var expr = "nested.nestedField == simpleShape";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.BOOL_SHAPE));
+ assertThat(actual.ident(), Matchers.equalTo("v4"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.Nested
+ v2 := v1.NestedField
+ v3 := input.SimpleShape
+ var v4 bool
+ if v2 != nil && v3 != nil {
+ v4 = string(*v2) == string(*v3)
+ }
+ """));
+ }
+
+ @Test
+ public void testContainsFunctionExpression() {
+ var expr = "contains(objectList[].key, 'foo')";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.BOOL_SHAPE));
+ assertThat(actual.ident(), Matchers.equalTo("v5"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.ObjectList
+ var v2 []string
+ for _, v := range v1 {
+ v3 := v.Key
+ if v3 != nil {
+ v2 = append(v2, *v3)
+ }
+ }
+ v4 := "foo"
+ var v5 bool
+ for _, v := range v2 {
+ if v == v4 {
+ v5 = true
+ break
+ }
+ }
+ """));
+ }
+
+ @Test
+ public void testAndExpression() {
+ var expr = "length(objectList) > `0` && length(objectList) <= `10`";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(ShapeUtil.BOOL_SHAPE));
+ assertThat(actual.ident(), Matchers.equalTo("v9"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.ObjectList
+ v2 := len(v1)
+ v3 := 0
+ v4 := int64(v2) > int64(v3)
+ v5 := input.ObjectList
+ v6 := len(v5)
+ v7 := 10
+ v8 := int64(v6) <= int64(v7)
+ v9 := v4 && v8
+ """));
+ }
+
+ @Test
+ public void testFilterExpression() {
+ var expr = "objectList[?length(innerObjectList) > `0`]";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#ObjectList"))));
+ assertThat(actual.ident(), Matchers.equalTo("v2"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.ObjectList
+ var v2 []types.Object
+ for _, v := range v1 {
+ v3 := v.InnerObjectList
+ v4 := len(v3)
+ v5 := 0
+ v6 := int64(v4) > int64(v5)
+ if v6 {
+ v2 = append(v2, v)
+ }
+ }
+ """));
+ }
+
+ @Test
+ public void testNot() {
+ var expr = "objectList[?!(length(innerObjectList) > `0`)]";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape(), Matchers.equalTo(TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#ObjectList"))));
+ assertThat(actual.ident(), Matchers.equalTo("v2"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.ObjectList
+ var v2 []types.Object
+ for _, v := range v1 {
+ v3 := v.InnerObjectList
+ v4 := len(v3)
+ v5 := 0
+ v6 := int64(v4) > int64(v5)
+ v7 := !v6
+ if v7 {
+ v2 = append(v2, v)
+ }
+ }
+ """));
+ }
+
+ @Test
+ public void testMultiSelect() {
+ var expr = "[simpleShape, simpleShape2]";
+
+ var writer = testWriter();
+ var generator = new GoJmespathExpressionGenerator(testContext(), writer);
+ var actual = generator.generate(JmespathExpression.parse(expr), new GoJmespathExpressionGenerator.Variable(
+ TEST_MODEL.expectShape(ShapeId.from("smithy.go.test#Struct")),
+ "input"
+ ));
+ assertThat(actual.shape().toShapeId().toString(), Matchers.equalTo("smithy.go.synthetic#StringList"));
+ assertThat(actual.ident(), Matchers.equalTo("v3"));
+ assertThat(writer.toString(), Matchers.containsString("""
+ v1 := input.SimpleShape
+ v2 := input.SimpleShape2
+ v3 := []*string{v1,v2}
+ """));
+ }
}