diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java index babd90a12a0..4eb9ca83a2c 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CommandGenerator.java @@ -299,6 +299,14 @@ private void generateEndpointParameterInstructionProvider() { } paramNames.add(name); }); + + parameterFinder.getOperationContextParamValues(operation).forEach((name, jmesPathForInputInJs) -> { + writer.write( + """ + $L: { type: \"operationContextParams\", name: $L }, + """, + name, jmesPathForInputInJs); + }); } writer.write("})") .dedent(); diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java index 26b00c8cd6d..44831958b97 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/endpointsV2/RuleSetParameterFinder.java @@ -32,6 +32,7 @@ import software.amazon.smithy.rulesengine.traits.ClientContextParamsTrait; import software.amazon.smithy.rulesengine.traits.ContextParamTrait; import software.amazon.smithy.rulesengine.traits.EndpointRuleSetTrait; +import software.amazon.smithy.rulesengine.traits.OperationContextParamsTrait; import software.amazon.smithy.rulesengine.traits.StaticContextParamsTrait; import software.amazon.smithy.utils.SmithyInternalApi; @@ -152,6 +153,68 @@ public Map getContextParams(Shape operationInput) { return map; } + /** + * Get map of params to JavaScript equivalent of provided JMESPath expressions. + */ + public Map getOperationContextParamValues(OperationShape operation) { + Map map = new HashMap<>(); + + Optional trait = operation.getTrait(OperationContextParamsTrait.class); + if (trait.isPresent()) { + trait.get().getParameters().forEach((name, definition) -> { + String separator = "."; + String value = "this" + separator + "input"; + String path = definition.getPath(); + + // Split JMESPath expression string on separator and add JavaScript equivalent. + for (String part : path.split("[" + separator + "]")) { + if (value.endsWith(")")) { + // The value is an object, which needs to run on map. + value += ".map(obj => obj"; + } + + // Process keys https://jmespath.org/specification.html#keys + if (part.startsWith("keys(")) { + // Get provided object for which keys are to be extracted. + String object = part.substring(5, part.length() - 1); + value = "Object.keys(" + value + separator + object + ")"; + continue; + } + + // Process list wildcard expression https://jmespath.org/specification.html#wildcard-expressions + if (part.equals("*") || part.equals("[*]")) { + value = "Object.values(" + value + ")"; + continue; + } + + // Process hash wildcard expression https://jmespath.org/specification.html#wildcard-expressions + if (part.endsWith("[*]")) { + // Get key to run hash wildcard on. + String key = part.substring(0, part.length() - 3); + value = value + separator + key + separator + "map(obj => obj"; + continue; + } + + // Treat remaining part as identifier without spaces https://jmespath.org/specification.html#identifiers + value += separator + part; + } + + // Remove no-op map, if it exists. + if (value.endsWith(separator + "map(obj => obj")) { + value = value.substring(0, value.length() - 15); + } + + // Close all open brackets. + value += ")".repeat((int) ( + value.chars().filter(ch -> ch == '(').count() - value.chars().filter(ch -> ch == ')').count())); + + map.put(name, value); + }); + } + + return map; + } + private static class RuleSetParameterFinderVisitor extends NodeVisitor.Default { private final Map map; diff --git a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/CommandGeneratorTest.java b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/CommandGeneratorTest.java index 7b4e3ae6d4f..9289684b4a1 100644 --- a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/CommandGeneratorTest.java +++ b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/CommandGeneratorTest.java @@ -14,7 +14,7 @@ public class CommandGeneratorTest { public void addsCommandSpecificPlugins() { testCommmandCodegen( "output-structure.smithy", - "getSerdePlugin(config, this.serialize, this.deserialize)" + new String[] {"getSerdePlugin(config, this.serialize, this.deserialize)"} ); } @@ -22,7 +22,7 @@ public void addsCommandSpecificPlugins() { public void writesSerializer() { testCommmandCodegen( "output-structure.smithy", - ".ser(" + new String[] {".ser("} ); } @@ -30,30 +30,48 @@ public void writesSerializer() { public void writesDeserializer() { testCommmandCodegen( "output-structure.smithy", - ".de(" + new String[] {".de("} ); } - private void testCommmandCodegen(String file, String expectedType) { - Model model = Model.assembler() - .addImport(getClass().getResource(file)) - .assemble() - .unwrap(); + @Test + public void writesOperationContextParamValues() { + testCommmandCodegen( + "endpointsV2/endpoints-operation-context-params.smithy", + new String[] { + "opContextParamIdentifier: { type: \"operationContextParams\", name: this.input.fooString }", + "opContextParamSubExpression: { type: \"operationContextParams\", name: this.input.fooObj.bar }", + "opContextParamWildcardExpressionList: { type: \"operationContextParams\", name: this.input.fooList }", + "opContextParamWildcardExpressionListObj: { type: \"operationContextParams\", name: this.input.fooListObj.map(obj => obj.key) }", + "opContextParamWildcardExpressionHash: { type: \"operationContextParams\", name: Object.values(this.input.fooObjObj).map(obj => obj.bar) }", + "opContextParamKeys: { type: \"operationContextParams\", name: Object.keys(this.input.fooKeys) }", + } + ); + } + + private void testCommmandCodegen(String filename, String[] expectedTypeArray) { MockManifest manifest = new MockManifest(); PluginContext context = PluginContext.builder() - .model(model) - .fileManifest(manifest) - .settings(Node.objectNodeBuilder() - .withMember("service", Node.from("smithy.example#Example")) - .withMember("package", Node.from("example")) - .withMember("packageVersion", Node.from("1.0.0")) - .build()) - .build(); + .pluginClassLoader(getClass().getClassLoader()) + .model(Model.assembler() + .addImport(getClass().getResource(filename)) + .discoverModels() + .assemble() + .unwrap()) + .fileManifest(manifest) + .settings(Node.objectNodeBuilder() + .withMember("service", Node.from("smithy.example#Example")) + .withMember("package", Node.from("example")) + .withMember("packageVersion", Node.from("1.0.0")) + .build()) + .build(); new TypeScriptCodegenPlugin().execute(context); String contents = manifest.getFileString(CodegenUtils.SOURCE_FOLDER + "//commands/GetFooCommand.ts").get(); assertThat(contents, containsString("as __MetadataBearer")); - assertThat(contents, containsString(expectedType)); + for (String expectedType : expectedTypeArray) { + assertThat(contents, containsString(expectedType)); + } } } diff --git a/smithy-typescript-codegen/src/test/resources/software/amazon/smithy/typescript/codegen/endpointsV2/endpoints-operation-context-params.smithy b/smithy-typescript-codegen/src/test/resources/software/amazon/smithy/typescript/codegen/endpointsV2/endpoints-operation-context-params.smithy new file mode 100644 index 00000000000..f947e482e34 --- /dev/null +++ b/smithy-typescript-codegen/src/test/resources/software/amazon/smithy/typescript/codegen/endpointsV2/endpoints-operation-context-params.smithy @@ -0,0 +1,80 @@ +$version: "2.0" + +namespace smithy.example + +@smithy.rules#endpointRuleSet({ + version: "1.0", + parameters: { + opContextParamIdentifier: { + type: "string", + }, + opContextParamSubExpression: { + type: "string", + }, + opContextParamWildcardExpressionList: { + type: "stringArray", + }, + opContextParamWildcardExpressionListObj: { + type: "stringArray", + }, + opContextParamWildcardExpressionHash: { + type: "stringArray", + }, + opContextParamKeys: { + type: "stringArray", + }, + }, + rules: [] +}) +service Example { + version: "1.0.0", + operations: [GetFoo] +} + +@smithy.rules#operationContextParams( + "opContextParamIdentifier": { path: "fooString" } + "opContextParamSubExpression": { path: "fooObj.bar" } + "opContextParamWildcardExpressionList": { path: "fooList[*]" } + "opContextParamWildcardExpressionListObj": { path: "fooListObj[*].key" } + "opContextParamWildcardExpressionHash": { path: "fooObjObj.*.bar" } + "opContextParamKeys": { path: "keys(fooKeys)" } +) +operation GetFoo { + input: GetFooInput, + output: GetFooOutput, + errors: [GetFooError] +} + +structure GetFooInput { + fooKeys: FooObject, + fooList: FooList, + fooListObj: FooListObj, + fooObj: FooObject, + fooObjObj: FooObjectObject, + fooString: String, +} + +structure FooObject { + bar: String +} + +structure FooObjectObject { + baz: FooObject +} + +list FooList { + member: String +} + +list FooListObj { + member: FooListObjMember +} + +structure FooListObjMember { + key: String +} + +structure GetFooOutput {} + +@error("client") +structure GetFooError {}