Skip to content

Commit a2bb933

Browse files
authored
Process Operation Context Params in Endpoints (#1379)
1 parent e94028b commit a2bb933

File tree

4 files changed

+186
-17
lines changed

4 files changed

+186
-17
lines changed

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,14 @@ private void generateEndpointParameterInstructionProvider() {
299299
}
300300
paramNames.add(name);
301301
});
302+
303+
parameterFinder.getOperationContextParamValues(operation).forEach((name, jmesPathForInputInJs) -> {
304+
writer.write(
305+
"""
306+
$L: { type: \"operationContextParams\", name: $L },
307+
""",
308+
name, jmesPathForInputInJs);
309+
});
302310
}
303311
writer.write("})")
304312
.dedent();

smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait;
3333
import software.amazon.smithy.rulesengine.traits.ContextParamTrait;
3434
import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait;
35+
import software.amazon.smithy.rulesengine.traits.OperationContextParamsTrait;
3536
import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait;
3637
import software.amazon.smithy.utils.SmithyInternalApi;
3738

@@ -152,6 +153,68 @@ public Map<String, String> getContextParams(Shape operationInput) {
152153
return map;
153154
}
154155

156+
/**
157+
* Get map of params to JavaScript equivalent of provided JMESPath expressions.
158+
*/
159+
public Map<String, String> getOperationContextParamValues(OperationShape operation) {
160+
Map<String, String> map = new HashMap<>();
161+
162+
Optional<OperationContextParamsTrait> trait = operation.getTrait(OperationContextParamsTrait.class);
163+
if (trait.isPresent()) {
164+
trait.get().getParameters().forEach((name, definition) -> {
165+
String separator = ".";
166+
String value = "this" + separator + "input";
167+
String path = definition.getPath();
168+
169+
// Split JMESPath expression string on separator and add JavaScript equivalent.
170+
for (String part : path.split("[" + separator + "]")) {
171+
if (value.endsWith(")")) {
172+
// The value is an object, which needs to run on map.
173+
value += ".map(obj => obj";
174+
}
175+
176+
// Process keys https://jmespath.org/specification.html#keys
177+
if (part.startsWith("keys(")) {
178+
// Get provided object for which keys are to be extracted.
179+
String object = part.substring(5, part.length() - 1);
180+
value = "Object.keys(" + value + separator + object + ")";
181+
continue;
182+
}
183+
184+
// Process list wildcard expression https://jmespath.org/specification.html#wildcard-expressions
185+
if (part.equals("*") || part.equals("[*]")) {
186+
value = "Object.values(" + value + ")";
187+
continue;
188+
}
189+
190+
// Process hash wildcard expression https://jmespath.org/specification.html#wildcard-expressions
191+
if (part.endsWith("[*]")) {
192+
// Get key to run hash wildcard on.
193+
String key = part.substring(0, part.length() - 3);
194+
value = value + separator + key + separator + "map(obj => obj";
195+
continue;
196+
}
197+
198+
// Treat remaining part as identifier without spaces https://jmespath.org/specification.html#identifiers
199+
value += separator + part;
200+
}
201+
202+
// Remove no-op map, if it exists.
203+
if (value.endsWith(separator + "map(obj => obj")) {
204+
value = value.substring(0, value.length() - 15);
205+
}
206+
207+
// Close all open brackets.
208+
value += ")".repeat((int) (
209+
value.chars().filter(ch -> ch == '(').count() - value.chars().filter(ch -> ch == ')').count()));
210+
211+
map.put(name, value);
212+
});
213+
}
214+
215+
return map;
216+
}
217+
155218
private static class RuleSetParameterFinderVisitor extends NodeVisitor.Default<Void> {
156219
private final Map<String, String> map;
157220

smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/CommandGeneratorTest.java

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,46 +14,64 @@ public class CommandGeneratorTest {
1414
public void addsCommandSpecificPlugins() {
1515
testCommmandCodegen(
1616
"output-structure.smithy",
17-
"getSerdePlugin(config, this.serialize, this.deserialize)"
17+
new String[] {"getSerdePlugin(config, this.serialize, this.deserialize)"}
1818
);
1919
}
2020

2121
@Test
2222
public void writesSerializer() {
2323
testCommmandCodegen(
2424
"output-structure.smithy",
25-
".ser("
25+
new String[] {".ser("}
2626
);
2727
}
2828

2929
@Test
3030
public void writesDeserializer() {
3131
testCommmandCodegen(
3232
"output-structure.smithy",
33-
".de("
33+
new String[] {".de("}
3434
);
3535
}
3636

37-
private void testCommmandCodegen(String file, String expectedType) {
38-
Model model = Model.assembler()
39-
.addImport(getClass().getResource(file))
40-
.assemble()
41-
.unwrap();
37+
@Test
38+
public void writesOperationContextParamValues() {
39+
testCommmandCodegen(
40+
"endpointsV2/endpoints-operation-context-params.smithy",
41+
new String[] {
42+
"opContextParamIdentifier: { type: \"operationContextParams\", name: this.input.fooString }",
43+
"opContextParamSubExpression: { type: \"operationContextParams\", name: this.input.fooObj.bar }",
44+
"opContextParamWildcardExpressionList: { type: \"operationContextParams\", name: this.input.fooList }",
45+
"opContextParamWildcardExpressionListObj: { type: \"operationContextParams\", name: this.input.fooListObj.map(obj => obj.key) }",
46+
"opContextParamWildcardExpressionHash: { type: \"operationContextParams\", name: Object.values(this.input.fooObjObj).map(obj => obj.bar) }",
47+
"opContextParamKeys: { type: \"operationContextParams\", name: Object.keys(this.input.fooKeys) }",
48+
}
49+
);
50+
}
51+
52+
private void testCommmandCodegen(String filename, String[] expectedTypeArray) {
4253
MockManifest manifest = new MockManifest();
4354
PluginContext context = PluginContext.builder()
44-
.model(model)
45-
.fileManifest(manifest)
46-
.settings(Node.objectNodeBuilder()
47-
.withMember("service", Node.from("smithy.example#Example"))
48-
.withMember("package", Node.from("example"))
49-
.withMember("packageVersion", Node.from("1.0.0"))
50-
.build())
51-
.build();
55+
.pluginClassLoader(getClass().getClassLoader())
56+
.model(Model.assembler()
57+
.addImport(getClass().getResource(filename))
58+
.discoverModels()
59+
.assemble()
60+
.unwrap())
61+
.fileManifest(manifest)
62+
.settings(Node.objectNodeBuilder()
63+
.withMember("service", Node.from("smithy.example#Example"))
64+
.withMember("package", Node.from("example"))
65+
.withMember("packageVersion", Node.from("1.0.0"))
66+
.build())
67+
.build();
5268

5369
new TypeScriptCodegenPlugin().execute(context);
5470
String contents = manifest.getFileString(CodegenUtils.SOURCE_FOLDER + "//commands/GetFooCommand.ts").get();
5571

5672
assertThat(contents, containsString("as __MetadataBearer"));
57-
assertThat(contents, containsString(expectedType));
73+
for (String expectedType : expectedTypeArray) {
74+
assertThat(contents, containsString(expectedType));
75+
}
5876
}
5977
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
$version: "2.0"
2+
3+
namespace smithy.example
4+
5+
@smithy.rules#endpointRuleSet({
6+
version: "1.0",
7+
parameters: {
8+
opContextParamIdentifier: {
9+
type: "string",
10+
},
11+
opContextParamSubExpression: {
12+
type: "string",
13+
},
14+
opContextParamWildcardExpressionList: {
15+
type: "stringArray",
16+
},
17+
opContextParamWildcardExpressionListObj: {
18+
type: "stringArray",
19+
},
20+
opContextParamWildcardExpressionHash: {
21+
type: "stringArray",
22+
},
23+
opContextParamKeys: {
24+
type: "stringArray",
25+
},
26+
},
27+
rules: []
28+
})
29+
service Example {
30+
version: "1.0.0",
31+
operations: [GetFoo]
32+
}
33+
34+
@smithy.rules#operationContextParams(
35+
"opContextParamIdentifier": { path: "fooString" }
36+
"opContextParamSubExpression": { path: "fooObj.bar" }
37+
"opContextParamWildcardExpressionList": { path: "fooList[*]" }
38+
"opContextParamWildcardExpressionListObj": { path: "fooListObj[*].key" }
39+
"opContextParamWildcardExpressionHash": { path: "fooObjObj.*.bar" }
40+
"opContextParamKeys": { path: "keys(fooKeys)" }
41+
)
42+
operation GetFoo {
43+
input: GetFooInput,
44+
output: GetFooOutput,
45+
errors: [GetFooError]
46+
}
47+
48+
structure GetFooInput {
49+
fooKeys: FooObject,
50+
fooList: FooList,
51+
fooListObj: FooListObj,
52+
fooObj: FooObject,
53+
fooObjObj: FooObjectObject,
54+
fooString: String,
55+
}
56+
57+
structure FooObject {
58+
bar: String
59+
}
60+
61+
structure FooObjectObject {
62+
baz: FooObject
63+
}
64+
65+
list FooList {
66+
member: String
67+
}
68+
69+
list FooListObj {
70+
member: FooListObjMember
71+
}
72+
73+
structure FooListObjMember {
74+
key: String
75+
}
76+
77+
structure GetFooOutput {}
78+
79+
@error("client")
80+
structure GetFooError {}

0 commit comments

Comments
 (0)