Skip to content

Commit ee36afc

Browse files
authored
Implement BDD generator for @endpointBdd Smithy trait (#647)
* BDD implementation on Smithy-Go
1 parent 3dbea70 commit ee36afc

13 files changed

Lines changed: 843 additions & 14 deletions

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/OperationGenerator.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import software.amazon.smithy.model.shapes.UnionShape;
3838
import software.amazon.smithy.model.traits.DeprecatedTrait;
3939
import software.amazon.smithy.model.traits.StreamingTrait;
40+
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;
4041
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
4142

4243
/**
@@ -137,7 +138,8 @@ public void run() {
137138
}, true);
138139

139140
var rulesTrait = service.getTrait(EndpointRuleSetTrait.class);
140-
if (rulesTrait.isPresent()) {
141+
var bddTrait = service.getTrait(EndpointBddTrait.class);
142+
if (rulesTrait.isPresent() || bddTrait.isPresent()) {
141143
writer.write(new EndpointParameterOperationBindingsGenerator(ctx, operation, inputShape)
142144
.generate());
143145
}

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/SmithyGoDependency.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ public final class SmithyGoDependency {
7777
public static final GoDependency SMITHY_AUTH_BEARER = smithy("auth/bearer");
7878
public static final GoDependency SMITHY_ENDPOINTS = smithy("endpoints", "smithyendpoints");
7979
public static final GoDependency SMITHY_ENDPOINT_RULESFN = smithy("endpoints/private/rulesfn");
80+
public static final GoDependency SMITHY_ENDPOINT_BDD = smithy("endpoints/private/bdd");
8081
public static final GoDependency SMITHY_TRACING = smithy("tracing");
8182
public static final GoDependency SMITHY_METRICS = smithy("metrics");
8283

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointBddResolverGenerator.java

Lines changed: 541 additions & 0 deletions
Large diffs are not rendered by default.

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointClientPluginsGenerator.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
import software.amazon.smithy.model.Model;
2929
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
3030
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
31+
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameters;
3132
import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait;
33+
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;
3234
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
3335
import software.amazon.smithy.utils.ListUtils;
3436
import software.amazon.smithy.utils.StringUtils;
@@ -76,13 +78,19 @@ public List<RuntimeClientPlugin> getClientPlugins() {
7678
@Override
7779
public void processFinalizedModel(GoSettings settings, Model model) {
7880
var service = settings.getService(model);
79-
if (!service.hasTrait(EndpointRuleSetTrait.class) || !service.hasTrait(ClientContextParamsTrait.class)) {
81+
if ((!service.hasTrait(EndpointRuleSetTrait.class) && !service.hasTrait(EndpointBddTrait.class))
82+
|| !service.hasTrait(ClientContextParamsTrait.class)) {
8083
return;
8184
}
8285

83-
var ruleset = EndpointRuleSet.fromNode(service.expectTrait(EndpointRuleSetTrait.class).getRuleSet());
86+
Parameters parameters;
87+
if (service.hasTrait(EndpointRuleSetTrait.class)) {
88+
parameters = EndpointRuleSet.fromNode(
89+
service.expectTrait(EndpointRuleSetTrait.class).getRuleSet()).getParameters();
90+
} else {
91+
parameters = service.expectTrait(EndpointBddTrait.class).getParameters();
92+
}
8493
var ccParams = service.expectTrait(ClientContextParamsTrait.class).getParameters();
85-
var parameters = ruleset.getParameters();
8694
parameters.forEach(param -> {
8795
var ccParam = ccParams.get(param.getName().getName().getValue());
8896
if (ccParam == null || param.getBuiltIn().isPresent()) {

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterBindingsGenerator.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import software.amazon.smithy.go.codegen.integration.ProtocolGenerator;
2828
import software.amazon.smithy.rulesengine.language.syntax.parameters.Parameter;
2929
import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait;
30+
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;
3031
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
3132
import software.amazon.smithy.utils.MapUtils;
3233

@@ -70,6 +71,7 @@ func bindEndpointParams(ctx $context:T, input interface{}, options Options) (*En
7071
MapUtils.of(
7172
"context", GoStdlibTypes.Context.Context,
7273
"builtinBindings", context.getService().hasTrait(EndpointRuleSetTrait.class)
74+
|| context.getService().hasTrait(EndpointBddTrait.class)
7375
? generateBuiltinBindings()
7476
: emptyGoTemplate(),
7577
"clientContextBindings", generateClientContextBindings()

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointParameterOperationBindingsGenerator.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828
import software.amazon.smithy.model.node.Node;
2929
import software.amazon.smithy.model.shapes.OperationShape;
3030
import software.amazon.smithy.model.shapes.StructureShape;
31-
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
3231
import software.amazon.smithy.rulesengine.traits.ContextParamTrait;
33-
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
3432
import software.amazon.smithy.rulesengine.traits.OperationContextParamDefinition;
3533
import software.amazon.smithy.rulesengine.traits.OperationContextParamsTrait;
3634
import software.amazon.smithy.rulesengine.traits.StaticContextParamDefinition;
@@ -45,8 +43,6 @@ public class EndpointParameterOperationBindingsGenerator {
4543
private final OperationShape operation;
4644
private final StructureShape input;
4745

48-
private final EndpointRuleSet rules;
49-
5046
public EndpointParameterOperationBindingsGenerator(
5147
GoCodegenContext ctx,
5248
OperationShape operation,
@@ -55,10 +51,6 @@ public EndpointParameterOperationBindingsGenerator(
5551
this.ctx = ctx;
5652
this.operation = operation;
5753
this.input = input;
58-
59-
this.rules = ctx.settings().getService(ctx.model())
60-
.expectTrait(EndpointRuleSetTrait.class)
61-
.getEndpointRuleSet();
6254
}
6355

6456
private boolean hasBindings() {

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolutionGenerator.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import software.amazon.smithy.go.codegen.SymbolUtils;
2626
import software.amazon.smithy.go.codegen.integration.ProtocolGenerator;
2727
import software.amazon.smithy.rulesengine.language.EndpointRuleSet;
28+
import software.amazon.smithy.rulesengine.traits.EndpointBddTrait;
2829
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
2930
import software.amazon.smithy.rulesengine.traits.EndpointTestCase;
3031
import software.amazon.smithy.rulesengine.traits.EndpointTestsTrait;
@@ -87,6 +88,26 @@ public void generate(ProtocolGenerator.GenerationContext context) {
8788
var bindingGenerator = new EndpointParameterBindingsGenerator(context);
8889
var middlewareGenerator = new EndpointMiddlewareGenerator(context);
8990

91+
// Prefer BDD trait over tree-based ruleset when available
92+
var bddTrait = serviceShape.getTrait(EndpointBddTrait.class);
93+
if (bddTrait.isPresent()) {
94+
Optional<EndpointRuleSet> ruleset = serviceShape.getTrait(EndpointRuleSetTrait.class)
95+
.map((trait) -> EndpointRuleSet.fromNode(trait.getRuleSet()));
96+
if (ruleset.isEmpty()) {
97+
ruleset = Optional.of(EndpointRuleSet.builder()
98+
.version(bddTrait.get().getVersion().toString())
99+
.parameters(bddTrait.get().getParameters())
100+
.build());
101+
}
102+
var bddResolver = new EndpointBddResolverGenerator(this.fnProvider);
103+
writer
104+
.write("$W\n", parametersGenerator.generate(ruleset))
105+
.write("$W\n", bddResolver.generate(bddTrait.get()))
106+
.write("$W\n", bindingGenerator.generate())
107+
.write("$W", middlewareGenerator.generate());
108+
return;
109+
}
110+
90111
Optional<EndpointRuleSet> ruleset = serviceShape.getTrait(EndpointRuleSetTrait.class)
91112
.map((trait) -> EndpointRuleSet.fromNode(trait.getRuleSet()));
92113

@@ -106,6 +127,13 @@ public void generateTests(ProtocolGenerator.GenerationContext context) {
106127
var serviceShape = context.getService();
107128
Optional<EndpointRuleSet> ruleset = serviceShape.getTrait(EndpointRuleSetTrait.class)
108129
.map((trait) -> EndpointRuleSet.fromNode(trait.getRuleSet()));
130+
if (ruleset.isEmpty()) {
131+
ruleset = serviceShape.getTrait(EndpointBddTrait.class)
132+
.map((trait) -> EndpointRuleSet.builder()
133+
.version(trait.getVersion().toString())
134+
.parameters(trait.getParameters())
135+
.build());
136+
}
109137

110138
var testsGenerator = EndpointTestsGenerator.builder()
111139
.parametersType(parametersType)

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/EndpointResolverGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ public EndpointResolverGenerator build() {
605605
}
606606
}
607607

608-
private Writable generateStringSliceHelper() {
608+
static Writable generateStringSliceHelper() {
609609
return goTemplate("""
610610
type stringSlice []string
611611

codegen/smithy-go-codegen/src/main/java/software/amazon/smithy/go/codegen/endpoints/ExpressionGenerator.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,20 @@
2121
import java.util.List;
2222
import java.util.Map;
2323
import java.util.function.Function;
24+
import software.amazon.smithy.codegen.core.Symbol;
25+
import software.amazon.smithy.go.codegen.ChainWritable;
26+
import software.amazon.smithy.go.codegen.GoUniverseTypes;
2427
import software.amazon.smithy.go.codegen.GoWriter;
2528
import software.amazon.smithy.go.codegen.SmithyGoDependency;
2629
import software.amazon.smithy.go.codegen.SymbolUtils;
2730
import software.amazon.smithy.go.codegen.Writable;
2831
import software.amazon.smithy.model.SourceLocation;
32+
import software.amazon.smithy.rulesengine.language.evaluation.type.AnyType;
33+
import software.amazon.smithy.rulesengine.language.evaluation.type.ArrayType;
34+
import software.amazon.smithy.rulesengine.language.evaluation.type.OptionalType;
2935
import software.amazon.smithy.rulesengine.language.syntax.Identifier;
36+
import software.amazon.smithy.rulesengine.language.evaluation.type.Type;
37+
import software.amazon.smithy.rulesengine.language.evaluation.type.StringType;
3038
import software.amazon.smithy.rulesengine.language.syntax.expressions.Expression;
3139
import software.amazon.smithy.rulesengine.language.syntax.expressions.ExpressionVisitor;
3240
import software.amazon.smithy.rulesengine.language.syntax.expressions.Reference;
@@ -120,6 +128,95 @@ public Writable visitStringEquals(Expression left, Expression right) {
120128
public Writable visitLibraryFunction(FunctionDefinition fnDef, List<Expression> args) {
121129
return new FnGenerator(scope, fnProvider).generate(fnDef, args);
122130
}
131+
132+
@Override
133+
public Writable visitIte(Expression condition, Expression trueValue, Expression falseValue) {
134+
var generator = new ExpressionGenerator(scope, fnProvider);
135+
var resultType = trueValue.type();
136+
if (resultType instanceof OptionalType opt) {
137+
resultType = opt.inner();
138+
}
139+
var goType = goTypeForType(resultType);
140+
return goTemplate("""
141+
func() $goType:T {
142+
if $cond:W {
143+
return $trueVal:W
144+
}
145+
return $falseVal:W
146+
}()""",
147+
MapUtils.of(
148+
"goType", goType,
149+
"cond", generator.generate(condition),
150+
"trueVal", generator.generate(trueValue),
151+
"falseVal", generator.generate(falseValue)));
152+
}
153+
154+
@Override
155+
public Writable visitCoalesce(List<Expression> expressions) {
156+
if (expressions.size() == 1) {
157+
return new ExpressionGenerator(scope, fnProvider).generate(expressions.get(0));
158+
}
159+
160+
var generator = new ExpressionGenerator(scope, fnProvider);
161+
boolean allOptional = expressions.stream()
162+
.allMatch(e -> e.type() instanceof OptionalType);
163+
164+
// determine the inner value type from the first expression
165+
var firstType = expressions.get(0).type();
166+
var innerType = firstType instanceof OptionalType opt ? opt.inner() : firstType;
167+
var goType = goTypeForType(innerType);
168+
169+
// build the chain of if-statements for each arg
170+
var checks = new java.util.ArrayList<Writable>();
171+
for (int i = 0; i < expressions.size(); i++) {
172+
var expr = expressions.get(i);
173+
boolean isOptional = expr.type() instanceof OptionalType;
174+
boolean isLast = i == expressions.size() - 1;
175+
176+
if (!isOptional) {
177+
// required arg: just return it, nothing after matters
178+
checks.add(goTemplate("return $val:W",
179+
Map.of("val", generator.generate(expr))));
180+
break;
181+
}
182+
183+
// optional arg: get pointer form for nil-check
184+
var valWritable = generator.generate(expr);
185+
var optIdent = scope.getIdent(expr);
186+
if (optIdent.isPresent() && optIdent.get().startsWith("*")) {
187+
valWritable = goTemplate(optIdent.get().substring(1));
188+
}
189+
190+
if (isLast) {
191+
// last arg and optional: return as-is (pointer)
192+
checks.add(goTemplate("return $val:W",
193+
Map.of("val", valWritable)));
194+
} else if (allOptional) {
195+
// optional, not last, result is pointer: return pointer if non-nil
196+
checks.add(goTemplate("""
197+
if v := $val:W; v != nil {
198+
return v
199+
}""",
200+
Map.of("val", valWritable)));
201+
} else {
202+
// optional, not last, result is value: deref if non-nil
203+
checks.add(goTemplate("""
204+
if v := $val:W; v != nil {
205+
return *v
206+
}""",
207+
Map.of("val", valWritable)));
208+
}
209+
}
210+
211+
var retType = allOptional ? SymbolUtils.pointerTo(goType) : goType;
212+
return goTemplate("""
213+
func() $retType:P {
214+
$checks:W
215+
}()""",
216+
MapUtils.of(
217+
"retType", retType,
218+
"checks", ChainWritable.of(checks).compose(false)));
219+
}
123220
}
124221

125222
private record LiteralGeneratorVisitor(Scope scope, FnProvider fnProvider)
@@ -216,4 +313,23 @@ public Writable finishMultipartTemplate() {
216313
private static String getBuiltinMemberName(Identifier ident) {
217314
return StringUtils.capitalize(ident.getName().toString());
218315
}
316+
317+
static Symbol goTypeForType(Type type) {
318+
if (type instanceof StringType) {
319+
return GoUniverseTypes.String;
320+
}
321+
if (type instanceof software.amazon.smithy.rulesengine.language.evaluation.type.BooleanType) {
322+
return GoUniverseTypes.Bool;
323+
}
324+
if (type instanceof ArrayType arr) {
325+
return SymbolUtils.sliceOf(goTypeForType(arr.getMember()));
326+
}
327+
if (type instanceof OptionalType opt) {
328+
return SymbolUtils.pointerTo(goTypeForType(opt.inner()));
329+
}
330+
if (type instanceof AnyType) {
331+
return GoUniverseTypes.Any;
332+
}
333+
throw new UnsupportedOperationException("unsupported Smithy type for Go mapping: " + type);
334+
}
219335
}

0 commit comments

Comments
 (0)