diff --git a/codegen/gradle.properties b/codegen/gradle.properties index d4081d1c..8292229a 100644 --- a/codegen/gradle.properties +++ b/codegen/gradle.properties @@ -1,2 +1,2 @@ -smithyVersion=1.49.0 +smithyVersion=1.50.0 smithyGradleVersion=0.7.0 diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java index 2c849c75..fd399da6 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/CodegenVisitor.java @@ -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; @@ -75,6 +76,7 @@ final class CodegenVisitor extends ShapeVisitor.Default { private final List runtimePlugins = new ArrayList<>(); private final ProtocolDocumentGenerator protocolDocumentGenerator; private final EventStreamGenerator eventStreamGenerator; + private final GoCodegenContext ctx; CodegenVisitor(PluginContext context) { // Load all integrations. @@ -154,6 +156,15 @@ final class CodegenVisitor extends ShapeVisitor.Default { 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( @@ -359,12 +370,9 @@ public Void serviceShape(ServiceShape shape) { TopDownIndex topDownIndex = model.getKnowledge(TopDownIndex.class); Set 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()); } }); 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 new file mode 100644 index 00000000..661545af --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/GoJmespathExpressionGenerator.java @@ -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. + *
+ * 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 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) {} +} diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java index 74443210..51b580f0 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java @@ -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 runtimeClientPlugins; OperationGenerator( - GoSettings settings, - Model model, - SymbolProvider symbolProvider, + GoCodegenContext ctx, GoWriter writer, - ServiceShape service, OperationShape operation, - Symbol operationSymbol, - ApplicationProtocol applicationProtocol, ProtocolGenerator protocolGenerator, List 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; } @@ -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) diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java index 2a84e249..8e967926 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java @@ -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( diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SymbolUtils.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SymbolUtils.java index 8313fb66..a82c3928 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SymbolUtils.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SymbolUtils.java @@ -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(); + } } 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 4060747a..faf9980c 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 @@ -15,42 +15,60 @@ package software.amazon.smithy.go.codegen.endpoints; +import static software.amazon.smithy.go.codegen.GoWriter.emptyGoTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; +import static software.amazon.smithy.utils.StringUtils.capitalize; -import software.amazon.smithy.codegen.core.CodegenException; -import software.amazon.smithy.codegen.core.Symbol; +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.SmithyGoTypes; +import software.amazon.smithy.go.codegen.knowledge.GoPointableIndex; +import software.amazon.smithy.jmespath.JmespathExpression; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.rulesengine.language.EndpointRuleSet; +import software.amazon.smithy.rulesengine.language.syntax.Identifier; +import software.amazon.smithy.rulesengine.language.syntax.parameters.ParameterType; import software.amazon.smithy.rulesengine.traits.ContextParamTrait; +import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; +import software.amazon.smithy.rulesengine.traits.OperationContextParamDefinition; +import software.amazon.smithy.rulesengine.traits.OperationContextParamsTrait; import software.amazon.smithy.rulesengine.traits.StaticContextParamDefinition; import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait; /** - * Generates operation-specific bindings (@contextParam + @staticContextParam) as a receiver method on the operation's - * input structure. + * Generates operation-specific bindings (@operationContextParam, @contextParam, @staticContextParam) as a receiver + * method on the operation's input structure. */ public class EndpointParameterOperationBindingsGenerator { + private final GoCodegenContext ctx; private final OperationShape operation; private final StructureShape input; - private final Symbol inputSymbol; + + private final EndpointRuleSet rules; public EndpointParameterOperationBindingsGenerator( + GoCodegenContext ctx, OperationShape operation, - StructureShape input, - Symbol inputSymbol + StructureShape input ) { + this.ctx = ctx; this.operation = operation; this.input = input; - this.inputSymbol = inputSymbol; + + this.rules = ctx.settings().getService(ctx.model()) + .expectTrait(EndpointRuleSetTrait.class) + .getEndpointRuleSet(); } private boolean hasBindings() { var hasContextBindings = input.getAllMembers().values().stream().anyMatch(it -> it.hasTrait(ContextParamTrait.class)); - return hasContextBindings || operation.hasTrait(StaticContextParamsTrait.class); + return hasContextBindings + || operation.hasTrait(StaticContextParamsTrait.class) + || operation.hasTrait(OperationContextParamsTrait.class); } public GoWriter.Writable generate() { @@ -62,13 +80,60 @@ public GoWriter.Writable generate() { func (in $P) bindEndpointParams(p *EndpointParameters) { $W $W + $W } """, - inputSymbol, + ctx.symbolProvider().toSymbol(input), + generateOperationContextParamBindings(), generateContextParamBindings(), generateStaticContextParamBindings()); } + private GoWriter.Writable generateOperationContextParamBindings() { + if (!operation.hasTrait(OperationContextParamsTrait.class)) { + return emptyGoTemplate(); + } + + var params = operation.expectTrait(OperationContextParamsTrait.class); + return GoWriter.ChainWritable.of( + params.getParameters().entrySet().stream() + .map(it -> generateOpContextParamBinding(it.getKey(), it.getValue())) + .toList() + ).compose(false); + } + + private GoWriter.Writable generateOpContextParamBinding(String paramName, OperationContextParamDefinition def) { + var param = rules.getParameters().get(Identifier.of(paramName)).get(); + var expr = JmespathExpression.parse(def.getPath()); + + return writer -> { + var generator = new GoJmespathExpressionGenerator(ctx, writer, input, expr); + + writer.write("func() {"); // contain the scope for each binding + var result = generator.generate("in"); + + if (param.getType().equals(ParameterType.STRING_ARRAY)) { + // projections can result in either []string OR []*string -- if the latter, we have to unwrap + var target = result.shape().asListShape().get().getMember().getTarget(); + if (GoPointableIndex.of(ctx.model()).isPointable(target)) { + writer.write(""" + deref := []string{} + for _, v := range $L { + if v != nil { + deref = append(deref, *v) + } + } + p.$L = deref""", result.ident(), capitalize(paramName)); + } else { + writer.write("p.$L = $L", capitalize(paramName), result.ident()); + } + } else { + writer.write("p.$L = $L", capitalize(paramName), result.ident()); + } + writer.write("}()"); + }; + } + private GoWriter.Writable generateContextParamBindings() { return writer -> { input.getAllMembers().values().forEach(it -> { @@ -90,7 +155,7 @@ private GoWriter.Writable generateStaticContextParamBindings() { StaticContextParamsTrait params = operation.expectTrait(StaticContextParamsTrait.class); return writer -> { params.getParameters().forEach((k, v) -> { - writer.write("p.$L = $W", k, generateStaticLiteral(v)); + writer.write("p.$L = $W", capitalize(k), generateStaticLiteral(v)); }); }; } @@ -103,7 +168,11 @@ private GoWriter.Writable generateStaticLiteral(StaticContextParamDefinition lit } else if (value.isBooleanNode()) { writer.writeInline("$T($L)", SmithyGoTypes.Ptr.Bool, value.expectBooleanNode().getValue()); } else { - throw new CodegenException("unrecognized static context param value type"); + writer.writeInline("[]string{$W}", GoWriter.ChainWritable.of( + value.expectArrayNode().getElements().stream() + .map(it -> goTemplate("$S,", it.expectStringNode().getValue())) + .toList() + ).compose(false)); } }; } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParametersGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParametersGenerator.java index 2834cf44..3c71893f 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParametersGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParametersGenerator.java @@ -20,6 +20,7 @@ import static software.amazon.smithy.go.codegen.GoWriter.goDocTemplate; import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.SymbolUtils.pointerTo; +import static software.amazon.smithy.go.codegen.SymbolUtils.sliceOf; import java.io.Serializable; import java.util.Comparator; @@ -28,7 +29,6 @@ import java.util.Optional; import java.util.stream.Stream; import java.util.stream.StreamSupport; -import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoUniverseTypes; import software.amazon.smithy.go.codegen.GoWriter; @@ -156,7 +156,11 @@ private GoWriter.Writable generateDefaultValue(Parameter parameter, Value defaul SmithyGoDependency.SMITHY_PTR.func("String"), defaultValue.expectStringValue()); case BOOLEAN -> goTemplate("$T($L)", SmithyGoDependency.SMITHY_PTR.func("Bool"), defaultValue.expectBooleanValue()); - case STRING_ARRAY -> throw new CodegenException("unsupported endpoint parameter type stringArray"); + case STRING_ARRAY -> goTemplate("[]string{$W}", GoWriter.ChainWritable.of( + defaultValue.expectArrayValue().getValues().stream() + .map(it -> goTemplate("$S,", it.expectStringValue())) + .toList() + ).compose(false)); }; } @@ -201,7 +205,7 @@ public static Symbol parameterAsSymbol(Parameter parameter) { return switch (parameter.getType()) { case STRING -> pointerTo(GoUniverseTypes.String); case BOOLEAN -> pointerTo(GoUniverseTypes.Bool); - case STRING_ARRAY -> throw new CodegenException("unsupported endpoint parameter type stringArray"); + case STRING_ARRAY -> sliceOf(GoUniverseTypes.String); }; } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java index 5d7190c2..ccb105e6 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java @@ -32,6 +32,7 @@ import java.util.Optional; import java.util.TreeMap; import java.util.logging.Logger; +import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.go.codegen.GoWriter; import software.amazon.smithy.go.codegen.SmithyGoDependency; @@ -107,6 +108,8 @@ public GoWriter.Writable generateEmptyRules() { private GoWriter.Writable generateResolverType(GoWriter.Writable resolveMethodBody) { return goTemplate(""" + $stringSlice:W + // $resolverInterfaceType:T provides the interface for resolving service endpoints. type $resolverInterfaceType:T interface { $resolveEndpointMethodDocs:W @@ -134,6 +137,7 @@ private GoWriter.Writable generateResolverType(GoWriter.Writable resolveMethodBo commonCodegenArgs, MapUtils.of( "context", SymbolUtils.createValueSymbolBuilder("Context", SmithyGoDependency.CONTEXT).build(), + "stringSlice", generateStringSliceHelper(), "resolverTypeDocs", generateResolverTypeDocs(), "resolveEndpointMethodDocs", generateResolveEndpointMethodDocs(), "resolveMethodBody", resolveMethodBody)); @@ -181,7 +185,16 @@ private GoWriter.Writable generateResolveMethodBody(EndpointRuleSet ruleset) { if (!param.isRequired()) { continue; } - w.write("$L := *$L", getLocalVarParameterName(param), getMemberParameterName(param)); + switch (param.getType()) { + case STRING, BOOLEAN -> + w.write("$L := *$L", + getLocalVarParameterName(param), getMemberParameterName(param)); + case STRING_ARRAY -> { + w.write("$L := stringSlice($L)", + getLocalVarParameterName(param), getMemberParameterName(param)); + } + default -> throw new CodegenException("unrecognized parameter type"); + } } }, "rules", generateRulesList(ruleset.getRules(), scope))); @@ -574,4 +587,18 @@ public EndpointResolverGenerator build() { return new EndpointResolverGenerator(this); } } + + private GoWriter.Writable generateStringSliceHelper() { + return goTemplate(""" + type stringSlice []string + + func (s stringSlice) Get(i int) *string { + if i < 0 || i >= len(s) { + return nil + } + + v := s[i] + return &v + }"""); + } } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointTestsGenerator.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointTestsGenerator.java index 2ed2fdca..e7e4605a 100644 --- a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointTestsGenerator.java +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointTestsGenerator.java @@ -21,6 +21,7 @@ import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; import static software.amazon.smithy.go.codegen.GoWriter.joinWritables; import static software.amazon.smithy.go.codegen.endpoints.EndpointParametersGenerator.getExportedParameterName; +import static software.amazon.smithy.go.codegen.util.NodeUtil.writableStringSlice; import java.util.ArrayList; import java.util.List; @@ -148,16 +149,12 @@ private GoWriter.Writable generateParameterValues(Parameters parameters, Endpoin private GoWriter.Writable generateParameterValue(Node value) { return switch (value.getType()) { - case STRING -> (GoWriter w) -> { - w.write("$T($S)", - SymbolUtils.createValueSymbolBuilder("String", SmithyGoDependency.SMITHY_PTR).build(), - value.expectStringNode().getValue()); - }; - case BOOLEAN -> (GoWriter w) -> { - w.write("$T($L)", - SymbolUtils.createValueSymbolBuilder("Bool", SmithyGoDependency.SMITHY_PTR).build(), - value.expectBooleanNode().getValue()); - }; + case STRING -> goTemplate("$T($S)", + SmithyGoDependency.SMITHY_PTR.func("String"), value.expectStringNode().getValue()); + case BOOLEAN -> goTemplate("$T($L)", + SmithyGoDependency.SMITHY_PTR.func("Bool"), value.expectBooleanNode().getValue()); + // only array parameter type is STRING_ARRAY + case ARRAY -> writableStringSlice(value.expectArrayNode()); default -> throw new CodegenException("Unhandled member type: " + value.getType()); }; } diff --git a/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/NodeUtil.java b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/NodeUtil.java new file mode 100644 index 00000000..63006837 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/NodeUtil.java @@ -0,0 +1,33 @@ +/* + * 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.util; + +import static software.amazon.smithy.go.codegen.GoWriter.goTemplate; + +import software.amazon.smithy.go.codegen.GoWriter; +import software.amazon.smithy.model.node.ArrayNode; + +public final class NodeUtil { + private NodeUtil() {} + + public static GoWriter.Writable writableStringSlice(ArrayNode node) { + return goTemplate("[]string{$W}", GoWriter.ChainWritable.of( + node.getElements().stream() + .map(it -> goTemplate("$S,", it.expectStringNode().getValue())) + .toList() + ).compose(false)); + } +} 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 new file mode 100644 index 00000000..68944603 --- /dev/null +++ b/codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/util/ShapeUtil.java @@ -0,0 +1,52 @@ +/* + * 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.util; + +import software.amazon.smithy.codegen.core.CodegenException; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.CollectionShape; +import software.amazon.smithy.model.shapes.ListShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.StringShape; + +public final class ShapeUtil { + public static final StringShape STRING_SHAPE = StringShape.builder() + .id("smithy.go.synthetic#String") + .build(); + + private ShapeUtil() {} + + public static ListShape listOf(Shape member) { + return ListShape.builder() + .id("smithy.go.synthetic#" + member.getId().getName() + "List") + .member(member.getId()) + .build(); + } + + public static Shape expectMember(Model model, Shape shape, String memberName) { + var optMember = shape.getMember(memberName); + if (optMember.isEmpty()) { + throw new CodegenException("expected member " + memberName + " in shape " + shape); + } + + var member = optMember.get(); + return model.expectShape(member.getTarget()); + } + + public static Shape expectMember(Model model, CollectionShape shape) { + return model.expectShape(shape.getMember().getTarget()); + } +}