diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CodegenUtils.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CodegenUtils.java index b1b301d5081..a30ac89929b 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CodegenUtils.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/CodegenUtils.java @@ -17,7 +17,9 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; +import software.amazon.smithy.codegen.core.CodegenException; import software.amazon.smithy.codegen.core.Symbol; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.EventStreamIndex; @@ -135,6 +137,40 @@ static void writeStreamingMemberType( writer.write("export interface $1L extends $1LType {}", typeName); } + /** + * Returns the list of function parameter key-value pairs to be written for + * provided parameters map. + * + * @param paramsMap Map of paramters to generate a parameters string for. + * @return The list of parameters to be written. + */ + static List getFunctionParametersList(Map paramsMap) { + List functionParametersList = new ArrayList(); + + if (!paramsMap.isEmpty()) { + for (Map.Entry param : paramsMap.entrySet()) { + String key = param.getKey(); + Object value = param.getValue(); + if (value instanceof Symbol) { + String symbolName = ((Symbol) value).getName(); + if (key.equals(symbolName)) { + functionParametersList.add(key); + } else { + functionParametersList.add(String.format("%s: %s", key, symbolName)); + } + } else if (value instanceof String) { + functionParametersList.add(String.format("%s: '%s'", key, value)); + } else { + // Future support for param type should be added in else if. + throw new CodegenException("Plugin function parameters not supported for type " + + value.getClass()); + } + } + } + + return functionParametersList; + } + /** * Ease the input streaming member restriction so that users don't need to construct a stream every time. * This is used for inline type declarations (such as parameters) that need to take more permissive inputs 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 f5974648e6d..15df0b388af 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 @@ -19,6 +19,7 @@ import static software.amazon.smithy.typescript.codegen.CodegenUtils.writeStreamingMemberType; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import software.amazon.smithy.codegen.core.Symbol; @@ -248,7 +249,10 @@ private void addCommandSpecificPlugins() { // the service's middleware stack. for (RuntimeClientPlugin plugin : runtimePlugins) { plugin.getPluginFunction().ifPresent(symbol -> { - List additionalParameters = plugin.getAdditionalPluginFunctionParameters(); + Map paramsMap = plugin.getAdditionalPluginFunctionParameters( + model, service, operation); + List additionalParameters = CodegenUtils.getFunctionParametersList(paramsMap); + String additionalParamsString = additionalParameters.isEmpty() ? "" : ", { " + String.join(", ", additionalParameters) + "}"; diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServiceGenerator.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServiceGenerator.java index 1077ed36c89..4d6d85cb221 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServiceGenerator.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/ServiceGenerator.java @@ -17,6 +17,7 @@ import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Consumer; @@ -313,7 +314,10 @@ private void generateConstructor() { for (RuntimeClientPlugin plugin : runtimePlugins) { if (plugin.getResolveFunction().isPresent()) { configVariable++; - List additionalParameters = plugin.getAdditionalResolveFunctionParameters(); + Map paramsMap = plugin.getAdditionalResolveFunctionParameters( + model, service, null); + List additionalParameters = CodegenUtils.getFunctionParametersList(paramsMap); + String additionalParamsString = additionalParameters.isEmpty() ? "" : ", " + String.join(", ", additionalParameters); diff --git a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPlugin.java b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPlugin.java index b4b2977892d..a89d81c67df 100644 --- a/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPlugin.java +++ b/smithy-typescript-codegen/src/main/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPlugin.java @@ -15,8 +15,8 @@ package software.amazon.smithy.typescript.codegen.integration; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -28,7 +28,6 @@ import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.typescript.codegen.TypeScriptDependency; -import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.SmithyUnstableApi; import software.amazon.smithy.utils.StringUtils; @@ -49,9 +48,9 @@ public final class RuntimeClientPlugin implements ToSmithyBuilder additionalResolveFunctionParameters; + private final FunctionParamsSupplier additionalResolveFunctionParamsSupplier; private final SymbolReference pluginFunction; - private final List additionalPluginFunctionParameters; + private final FunctionParamsSupplier additionalPluginFunctionParamsSupplier; private final SymbolReference destroyFunction; private final BiPredicate servicePredicate; private final OperationPredicate operationPredicate; @@ -60,21 +59,13 @@ private RuntimeClientPlugin(Builder builder) { inputConfig = builder.inputConfig; resolvedConfig = builder.resolvedConfig; resolveFunction = builder.resolveFunction; - additionalResolveFunctionParameters = ListUtils.copyOf(builder.additionalResolveFunctionParameters); + additionalResolveFunctionParamsSupplier = builder.additionalResolveFunctionParamsSupplier; pluginFunction = builder.pluginFunction; - additionalPluginFunctionParameters = ListUtils.copyOf(builder.additionalPluginFunctionParameters); + additionalPluginFunctionParamsSupplier = builder.additionalPluginFunctionParamsSupplier; destroyFunction = builder.destroyFunction; operationPredicate = builder.operationPredicate; servicePredicate = builder.servicePredicate; - if (!additionalResolveFunctionParameters.isEmpty() && resolveFunction == null) { - throw new IllegalStateException("Additional parameters can only be set if a resolve function is set."); - } - - if (!additionalPluginFunctionParameters.isEmpty() && pluginFunction == null) { - throw new IllegalStateException("Additional parameters can only be set if a plugin function is set."); - } - boolean allNull = (inputConfig == null) && (resolvedConfig == null) && (resolveFunction == null); boolean allSet = (inputConfig != null) && (resolvedConfig != null) && (resolveFunction != null); if (!(allNull || allSet)) { @@ -102,6 +93,20 @@ public interface OperationPredicate { boolean test(Model model, ServiceShape service, OperationShape operation); } + @FunctionalInterface + public interface FunctionParamsSupplier { + /** + * Returns parameters to be passed to a function which can be computed dynamically. + * + * @param model Model the operation belongs to. + * @param service Service the operation belongs to. + * @param operation Operation to test. + * @return Returns the map of parameters to be passed to a function. The key is the key + * for a parameter, and value is the value for a parameter. + */ + Map apply(Model model, ServiceShape service, OperationShape operation); + } + /** * Gets the optionally present symbol reference that points to the * Input configuration interface for the plugin. @@ -163,7 +168,6 @@ public Optional getResolvedConfig() { * * @return Returns the optionally present resolve function. * @see #getInputConfig() - * @see #getAdditionalResolveFunctionParameters() * @see #getResolvedConfig() */ public Optional getResolveFunction() { @@ -173,14 +177,24 @@ public Optional getResolveFunction() { /** * Gets a list of additional parameters to be supplied to the * resolve function. These parameters are to be supplied to resolve - * function as Nth(N > 1) positional arguments. The list is empty if - * there are no additional parameters. + * function as second argument. The map is empty if there are + * no additional parameters. * - * @return Returns the optionally present list of parameters. - * @see #getResolveFunction() + * @param model Model the operation belongs to. + * @param service Service the operation belongs to. + * @param operation Operation to test against. + * @return Returns the optionally present map of parameters. The key is the key + * for a parameter, and value is the value for a parameter. */ - public List getAdditionalResolveFunctionParameters() { - return additionalResolveFunctionParameters; + public Map getAdditionalResolveFunctionParameters( + Model model, + ServiceShape service, + OperationShape operation + ) { + if (additionalResolveFunctionParamsSupplier != null) { + return additionalResolveFunctionParamsSupplier.apply(model, service, operation); + } + return new HashMap(); } /** @@ -211,14 +225,24 @@ public Optional getPluginFunction() { /** * Gets a list of additional parameters to be supplied to the * plugin function. These parameters are to be supplied to plugin - * function as an options hash. The list is empty if - * there are no additional parameters. + * function as second argument. The map is empty if there are + * no additional parameters. * - * @return Returns the optionally present list of parameters. - * @see #getPluginFunction() + * @param model Model the operation belongs to. + * @param service Service the operation belongs to. + * @param operation Operation to test against. + * @return Returns the optionally present map of parameters. The key is the key + * for a parameter, and value is the value for a parameter. */ - public List getAdditionalPluginFunctionParameters() { - return additionalPluginFunctionParameters; + public Map getAdditionalPluginFunctionParameters( + Model model, + ServiceShape service, + OperationShape operation + ) { + if (additionalPluginFunctionParamsSupplier != null) { + return additionalPluginFunctionParamsSupplier.apply(model, service, operation); + } + return new HashMap(); } /** @@ -284,7 +308,9 @@ public Builder toBuilder() { .inputConfig(inputConfig) .resolvedConfig(resolvedConfig) .resolveFunction(resolveFunction) + .additionalResolveFunctionParamsSupplier(additionalResolveFunctionParamsSupplier) .pluginFunction(pluginFunction) + .additionalPluginFunctionParamsSupplier(additionalPluginFunctionParamsSupplier) .destroyFunction(destroyFunction); // Set these directly since their setters have mutual side-effects. @@ -300,9 +326,7 @@ public String toString() { + "inputConfig=" + inputConfig + ", resolvedConfig=" + resolvedConfig + ", resolveFunction=" + resolveFunction - + ", additionalResolveFunctionParameters=" + additionalResolveFunctionParameters + ", pluginFunction=" + pluginFunction - + ", additionalPluginFunctionParameters=" + additionalPluginFunctionParameters + ", destroyFunction=" + destroyFunction + '}'; } @@ -319,9 +343,9 @@ public boolean equals(Object o) { return Objects.equals(inputConfig, that.inputConfig) && Objects.equals(resolvedConfig, that.resolvedConfig) && Objects.equals(resolveFunction, that.resolveFunction) - && Objects.equals(additionalResolveFunctionParameters, that.additionalResolveFunctionParameters) + && Objects.equals(additionalResolveFunctionParamsSupplier, that.additionalResolveFunctionParamsSupplier) && Objects.equals(pluginFunction, that.pluginFunction) - && Objects.equals(additionalPluginFunctionParameters, that.additionalPluginFunctionParameters) + && Objects.equals(additionalPluginFunctionParamsSupplier, that.additionalPluginFunctionParamsSupplier) && Objects.equals(destroyFunction, that.destroyFunction) && servicePredicate.equals(that.servicePredicate) && operationPredicate.equals(that.operationPredicate); @@ -339,9 +363,9 @@ public static final class Builder implements SmithyBuilder private SymbolReference inputConfig; private SymbolReference resolvedConfig; private SymbolReference resolveFunction; - private List additionalResolveFunctionParameters = new ArrayList<>(); + private FunctionParamsSupplier additionalResolveFunctionParamsSupplier; private SymbolReference pluginFunction; - private List additionalPluginFunctionParameters = new ArrayList<>(); + private FunctionParamsSupplier additionalPluginFunctionParamsSupplier; private SymbolReference destroyFunction; private BiPredicate servicePredicate = (model, service) -> true; private OperationPredicate operationPredicate = (model, service, operation) -> false; @@ -433,13 +457,17 @@ public Builder resolveFunction(SymbolReference resolveFunction) { * {@link #inputConfig} must also be set. * * @param resolveFunction Function used to convert input to resolved. - * @param additionalParameters Additional parameters to be generated as resolve function input. + * @param additionalResolveFunctionParamsSupplier Function which returns params to be passed + * as resolve function input. * @return Returns the builder. * @see #getResolveFunction() */ - public Builder resolveFunction(SymbolReference resolveFunction, String... additionalParameters) { + public Builder resolveFunction( + SymbolReference resolveFunction, + FunctionParamsSupplier additionalResolveFunctionParamsSupplier + ) { this.resolveFunction = resolveFunction; - this.additionalResolveFunctionParameters = ListUtils.of(additionalParameters); + this.additionalResolveFunctionParamsSupplier = additionalResolveFunctionParamsSupplier; return this; } @@ -466,27 +494,37 @@ public Builder resolveFunction(Symbol resolveFunction) { * {@link #inputConfig} must also be set. * * @param resolveFunction Function used to convert input to resolved. - * @param additionalParameters Additional parameters to be generated as resolve function input. + * @param additionalResolveFunctionParamsSupplier Function which returns params to be passed + * as resolve function input. * @return Returns the builder. * @see #getResolveFunction() */ - public Builder resolveFunction(Symbol resolveFunction, String... additionalParameters) { - return resolveFunction(SymbolReference.builder().symbol(resolveFunction).build(), additionalParameters); + public Builder resolveFunction( + Symbol resolveFunction, + FunctionParamsSupplier additionalResolveFunctionParamsSupplier + ) { + return resolveFunction( + SymbolReference.builder().symbol(resolveFunction).build(), + additionalResolveFunctionParamsSupplier + ); } /** - * Set additional positional input parameters to resolve function. Set - * this with no arguments to remove the current parameters. + * Set function which returns input parameters to resolve function. Set + * function to return empty map to remove the current parameters. * *

If this is set, then all of {@link #resolveFunction}, * {@link #resolvedConfig} and {@link #inputConfig} must also be set. * - * @param additionalParameters Additional parameters to be generated as resolve function input. + * @param additionalResolveFunctionParamsSupplier Function which returns params to be passed + * as resolve function input. * @return Returns the builder. * @see #getResolveFunction() */ - public Builder additionalResolveFunctionParameters(String... additionalParameters) { - this.additionalResolveFunctionParameters = ListUtils.of(additionalParameters); + public Builder additionalResolveFunctionParamsSupplier( + FunctionParamsSupplier additionalResolveFunctionParamsSupplier + ) { + this.additionalResolveFunctionParamsSupplier = additionalResolveFunctionParamsSupplier; return this; } @@ -508,13 +546,16 @@ public Builder pluginFunction(SymbolReference pluginFunction) { * commands to use a specific middleware function. * * @param pluginFunction Plugin function symbol to invoke. - * @param additionalParameters Additional parameters to be generated as plugin function input. + * @param pluginFunctionParamsSupplier Function which returns params to be passed as plugin function input. * @return Returns the builder. * @see #getPluginFunction() */ - public Builder pluginFunction(SymbolReference pluginFunction, String... additionalParameters) { + public Builder pluginFunction( + SymbolReference pluginFunction, + FunctionParamsSupplier pluginFunctionParamsSupplier + ) { this.pluginFunction = pluginFunction; - this.additionalPluginFunctionParameters = ListUtils.of(additionalParameters); + this.additionalPluginFunctionParamsSupplier = pluginFunctionParamsSupplier; return this; } @@ -535,24 +576,34 @@ public Builder pluginFunction(Symbol pluginFunction) { * use a specific middleware function. * * @param pluginFunction Plugin function symbol to invoke. - * @param additionalParameters Additional parameters to be generated as plugin function input. + * @param additionalPluginFunctionParamsSupplier Function which returns params to be passed + * as plugin function input. * @return Returns the builder. * @see #getPluginFunction() */ - public Builder pluginFunction(Symbol pluginFunction, String... additionalParameters) { - return pluginFunction(SymbolReference.builder().symbol(pluginFunction).build(), additionalParameters); + public Builder pluginFunction( + Symbol pluginFunction, + FunctionParamsSupplier additionalPluginFunctionParamsSupplier + ) { + return pluginFunction( + SymbolReference.builder().symbol(pluginFunction).build(), + additionalPluginFunctionParamsSupplier + ); } /** - * Set additional positional input parameters to plugin function. Set - * this with no arguments to remove the current parameters. + * Set function which returns input parameters to plugin function. Set + * function to return empty map to remove the current parameters. * - * @param additionalParameters Additional parameters to be generated as plugin function input. + * @param additionalPluginFunctionParamsSupplier Function which returns params to be passed + * as plugin function input. * @return Returns the builder. * @see #getPluginFunction() */ - public Builder additionalPluginFunctionParameters(String... additionalParameters) { - this.additionalPluginFunctionParameters = ListUtils.of(additionalParameters); + public Builder additionalPluginFunctionParamsSupplier( + FunctionParamsSupplier additionalPluginFunctionParamsSupplier + ) { + this.additionalPluginFunctionParamsSupplier = additionalPluginFunctionParamsSupplier; return this; } diff --git a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPluginTest.java b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPluginTest.java index 3bc88d0dd70..ef92cbeaf3f 100644 --- a/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPluginTest.java +++ b/smithy-typescript-codegen/src/test/java/software/amazon/smithy/typescript/codegen/integration/RuntimeClientPluginTest.java @@ -3,6 +3,9 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import java.util.HashMap; +import java.util.Map; + import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; import software.amazon.smithy.codegen.core.Symbol; @@ -67,10 +70,17 @@ public void allowsConfigurableServicePredicate() { @Test public void configuresWithDefaultConventions() { + Map resolveFunctionParams = new HashMap() {{ + put("resolveFunctionParam", "resolveFunctionParam"); + }}; + Map pluginFunctionParams = new HashMap() {{ + put("pluginFunctionParam", "pluginFunctionParam"); + }}; + RuntimeClientPlugin plugin = RuntimeClientPlugin.builder() .withConventions("foo/baz", "1.0.0", "Foo") - .additionalResolveFunctionParameters("resolveFunctionParam") - .additionalPluginFunctionParameters("pluginFunctionParam") + .additionalResolveFunctionParamsSupplier((m, s, o) -> resolveFunctionParams) + .additionalPluginFunctionParamsSupplier((m, s, o) -> pluginFunctionParams) .build(); assertThat(plugin.getInputConfig().get().getSymbol().getNamespace(), equalTo("foo/baz")); @@ -82,12 +92,14 @@ public void configuresWithDefaultConventions() { assertThat(plugin.getResolveFunction().get().getSymbol().getNamespace(), equalTo("foo/baz")); assertThat(plugin.getResolveFunction().get().getSymbol().getName(), equalTo("resolveFooConfig")); - assertThat(plugin.getAdditionalResolveFunctionParameters(), equalTo(ListUtils.of("resolveFunctionParam"))); + assertThat(plugin.getAdditionalResolveFunctionParameters(null, null, null), + equalTo(resolveFunctionParams)); assertThat(plugin.getPluginFunction().get().getSymbol().getNamespace(), equalTo("foo/baz")); assertThat(plugin.getPluginFunction().get().getSymbol().getName(), equalTo("getFooPlugin")); - assertThat(plugin.getAdditionalPluginFunctionParameters(), equalTo(ListUtils.of("pluginFunctionParam"))); + assertThat(plugin.getAdditionalPluginFunctionParameters(null, null, null), + equalTo(pluginFunctionParams)); assertThat(plugin.getInputConfig().get().getSymbol().getDependencies().get(0).getPackageName(), equalTo("foo/baz"));