diff --git a/docs/source/guides/building-models/build-config.rst b/docs/source/guides/building-models/build-config.rst index b551dea638c..6b19f8fe28a 100644 --- a/docs/source/guides/building-models/build-config.rst +++ b/docs/source/guides/building-models/build-config.rst @@ -67,8 +67,18 @@ The following is an example ``smithy-build.json`` configuration: "projection-name": { "imports": ["projection-specific-imports/"], "transforms": [ - {"name": "excludeShapesByTag", "args": ["internal", "beta", "..."]}, - {"name": "excludeTraitsByTag", "args": ["internal"]} + { + "name": "excludeShapesByTag", + "args": { + "tags": ["internal", "beta", "..."] + } + }, + { + "name": "excludeTraitsByTag", + "args": { + "tags": ["internal"] + } + } ], "plugins": { "plugin-name": { @@ -167,7 +177,6 @@ Transforms are applied to the model, in order. A transform accepts the following configuration: - .. list-table:: :header-rows: 1 :widths: 10 20 70 @@ -179,8 +188,8 @@ A transform accepts the following configuration: - ``string`` - The required name of the transform. * - args - - ``string[]`` - - Provides a list of arguments to pass to the transform. + - ``structure`` + - A structure that contains configuration key-value pairs. .. _apply-transform: @@ -188,10 +197,21 @@ A transform accepts the following configuration: apply ----- -Applies the transforms defined in the given projection names. Each provided -name must be a valid projection name. The transforms of the referenced -projections are applied in the order provided. No cycles are allowed in -``apply``. +Applies the transforms defined in the given projection names. + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - projections + - ``[string]`` + - The ordered list of projection names to apply. Each provided + name must be a valid projection name. The transforms of the + referenced projections are applied in the order provided. + No cycles are allowed in ``apply``. .. tabs:: @@ -210,7 +230,12 @@ projections are applied in the order provided. No cycles are allowed in "imports": ["projection-specific-imports/"], "transforms": [ {"name": "baz"}, - {"name": "apply", "args": ["my-abstract-projection"]}, + { + "name": "apply", + "args": { + "projections": ["my-abstract-projection"] + } + }, {"name": "bar"} ] } @@ -225,9 +250,20 @@ excludeShapesByTag Aliases: ``excludeByTag`` (deprecated) -Removes shapes if they are tagged with one or more of the given arguments via +Removes shapes if they are tagged with one or more of the given ``tags`` via the :ref:`tags trait `. +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - tags + - ``[string]`` + - The set of tags that causes shapes to be removed. + .. tabs:: .. code-tab:: json @@ -237,7 +273,12 @@ the :ref:`tags trait `. "projections": { "exampleProjection": { "transforms": [ - {"name": "excludeByTag", "args": ["foo", "baz"]} + { + "name": "excludeByTag", + "args": { + "tags": ["foo", "baz"] + } + } ] } } @@ -255,9 +296,20 @@ includeShapesByTag Aliases: ``includeByTag`` (deprecated) -Removes shapes that are not tagged with at least one of the given arguments +Removes shapes that are not tagged with at least one of the given ``tags`` via the :ref:`tags trait `. +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - tags + - ``[string]`` + - The set of tags that causes shapes to be retained in the model. + .. tabs:: .. code-tab:: json @@ -267,7 +319,12 @@ via the :ref:`tags trait `. "projections": { "exampleProjection": { "transforms": [ - {"name": "includeByTag", "args": ["foo", "baz"]} + { + "name": "includeByTag", + "args": { + "tags": ["foo", "baz"] + } + } ] } } @@ -286,6 +343,17 @@ includeNamespaces Filters out shapes that are not part of one of the given :ref:`namespaces `. Note that this does not filter out traits based on namespaces. +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - namespaces + - ``[string]`` + - The namespaces to include in the model. + .. tabs:: .. code-tab:: json @@ -295,7 +363,12 @@ Note that this does not filter out traits based on namespaces. "projections": { "exampleProjection": { "transforms": [ - {"name": "includeNamespaces", "args": ["com.foo.bar", "my.api"]} + { + "name": "includeNamespaces", + "args": { + "namespaces": ["com.foo.bar", "my.api"] + } + } ] } } @@ -311,8 +384,20 @@ Note that this does not filter out traits based on namespaces. includeServices --------------- -Filters out service shapes that are not included in the arguments list of -service shape IDs. +Filters out service shapes that are not included in the ``services`` list of +shape IDs. + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - services + - ``[string]`` + - The service shape IDs to include in the model. Each entry MUST be + a valid service shape ID. .. tabs:: @@ -323,7 +408,12 @@ service shape IDs. "projections": { "exampleProjection": { "transforms": [ - {"name": "includeServices", "args": ["my.api#MyService"]} + { + "name": "includeServices", + "args": { + "services": ["my.api#MyService"] + } + } ] } } @@ -336,8 +426,18 @@ excludeTags ----------- Removes tags from shapes and trait definitions that match any of the -provided arguments (a list of allowed tags). +provided ``tags``. +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - tags + - ``[string]`` + - The set of tags that are removed from the model. .. tabs:: @@ -348,7 +448,12 @@ provided arguments (a list of allowed tags). "projections": { "exampleProjection": { "transforms": [ - {"name": "excludeTags", "args": ["tagA", "tagB"]} + { + "name": "excludeTags", + "args": { + "tags": ["tagA", "tagB"] + } + } ] } } @@ -361,13 +466,27 @@ excludeTraits ------------- Removes trait definitions from a model if the trait name is present in the -provided list of arguments. Any instance of a removed trait is also removed +provided list of ``traits``. Any instance of a removed trait is also removed from shapes in the model. The shapes that make up trait definitions that are removed *are not* automatically removed from the model. Use ``removeUnusedShapes`` to remove orphaned shapes. +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - traits + - ``[string]`` + - The set of traits that are removed from the model. Arguments that + end with "#" exclude the traits of an entire namespace. Trait + shape IDs that are relative are assumed to be part of the + ``smithy.api`` prelude namespace. + .. tabs:: .. code-tab:: json @@ -377,7 +496,12 @@ orphaned shapes. "projections": { "exampleProjection": { "transforms": [ - {"name": "excludeTraits", "args": ["since", "com.foo#customTrait"]} + { + "name": "excludeTraits", + "args": { + "traits": ["since", "com.foo#customTrait"] + } + } ] } } @@ -397,7 +521,12 @@ all traits in the "example.foo" namespace: "projections": { "exampleProjection": { "transforms": [ - {"name": "excludeTraits", "args": ["example.foo#"]} + { + "name": "excludeTraits", + "args": { + "traits": ["example.foo#"] + } + } ] } } @@ -417,6 +546,17 @@ The shapes that make up trait definitions that are removed *are not* automatically removed from the model. Use ``removeUnusedShapes`` to remove orphaned shapes. +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - tags + - ``[string]`` + - The list of tags that, if present, cause a trait to be removed. + .. tabs:: .. code-tab:: json @@ -426,7 +566,12 @@ orphaned shapes. "projections": { "exampleProjection": { "transforms": [ - {"name": "excludeTraitsByTag", "args": ["internal"]} + { + "name": "excludeTraitsByTag", + "args": { + "tags": ["internal"] + } + } ] } } @@ -442,8 +587,19 @@ orphaned shapes. includeTags ----------- -Removes tags from shapes and trait definitions that are not in the -argument list (a list of allowed tags). +Removes tags from shapes and trait definitions that are not in the ``tags`` +list. + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - tags + - ``[string]`` + - The set of tags that are retained in the model. .. tabs:: @@ -454,7 +610,12 @@ argument list (a list of allowed tags). "projections": { "exampleProjection": { "transforms": [ - {"name": "includeTags", "args": ["foo", "baz"]} + { + "name": "includeTags", + "args": { + "tags": ["foo", "baz"] + } + } ] } } @@ -467,13 +628,27 @@ includeTraits ------------- Removes trait definitions from a model if the trait name is not present in the -provided list of arguments. Any instance of a removed trait is also removed +provided list of ``traits``. Any instance of a removed trait is also removed from shapes in the model. The shapes that make up trait definitions that are removed *are not* automatically removed from the model. Use ``removeUnusedShapes`` to remove orphaned shapes. +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - traits + - ``[string]`` + - The list of trait shape IDs to include. A trait ID that ends with "#" + will include all traits from a namespace. Trait shape IDs that are + relative are assumed to be part of the ``smithy.api`` + prelude namespace. + .. tabs:: .. code-tab:: json @@ -483,7 +658,12 @@ orphaned shapes. "projections": { "exampleProjection": { "transforms": [ - {"name": "includeTraits", "args": ["sensitive", "com.foo.baz#customTrait"]} + { + "name": "includeTraits", + "args": { + "traits": ["sensitive", "com.foo.baz#customTrait"] + } + } ] } } @@ -503,7 +683,12 @@ all traits in the "smithy.api" namespace: "projections": { "exampleProjection": { "transforms": [ - {"name": "includeTraits", "args": ["smithy.api#"]} + { + "name": "includeTraits", + "args": { + "traits": ["smithy.api#"] + } + } ] } } @@ -523,6 +708,18 @@ The shapes that make up trait definitions that are removed *are not* automatically removed from the model. Use ``removeUnusedShapes`` to remove orphaned shapes. +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - tags + - ``[string]`` + - The list of tags that must be present for a trait to be included + in the filtered model. + .. tabs:: .. code-tab:: json @@ -532,7 +729,12 @@ orphaned shapes. "projections": { "exampleProjection": { "transforms": [ - {"name": "includeTraitsByTag", "args": ["public"]} + { + "name": "includeTraitsByTag", + "args": { + "tags": ["public"] + } + } ] } } @@ -554,8 +756,21 @@ Removes shapes from the model that are not connected to any service shape or to a shape definition. You can *export* shapes that are not connected to any service shape by -applying specific tags to the shape and adding the list of export tags as -arguments to the transform. +applying specific tags to the shape and adding the list of export tags in +the ``exportTagged`` argument. + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - exportTagged + - ``[string]`` + - The set of tags that, if found on a shape, forces the shape to be + present in the transformed model regardless of if it was connected + to a service. The following example removes shapes that are not connected to any service, but keeps the shape if it has any of the provided tags: @@ -569,7 +784,15 @@ but keeps the shape if it has any of the provided tags: "projections": { "exampleProjection": { "transforms": [ - {"name": "removeUnusedShapes", "args": ["export-tag1", "another-export-tag"]} + { + "name": "removeUnusedShapes", + "args": { + "exportTagged": [ + "export-tag1", + "another-export-tag" + ] + } + } ] } } @@ -600,7 +823,12 @@ Consider the following ``smithy-build.json`` file: "projections": { "a": { "transforms": [ - {"${NAME_KEY}": "includeByTag", "args": ["${FOO}", "\\${BAZ}"]} + { + "${NAME_KEY}": "includeByTag", + "args": { + "tags": ["${FOO}", "\\${BAZ}"] + } + } ] } } @@ -616,7 +844,12 @@ environment variable set to "hi", this file is equivalent to: "projections": { "a": { "transforms": [ - {"name": "includeByTag", "args": ["Hi", "${BAZ}"]} + { + "name": "includeByTag", + "args": { + "tags": ["Hi", "${BAZ}"] + } + } ] } } diff --git a/docs/source/guides/building-models/gradle-plugin.rst b/docs/source/guides/building-models/gradle-plugin.rst index 8aedd739a9e..80f6e850544 100644 --- a/docs/source/guides/building-models/gradle-plugin.rst +++ b/docs/source/guides/building-models/gradle-plugin.rst @@ -239,10 +239,27 @@ projection. For example: "projections": { "external": { "transforms": [ - {"name": "excludeShapesByTag", "args": ["internal"]}, - {"name": "excludeTraitsByTag", "args": ["internal"]}, - {"name": "excludeMetadata", "args": ["suppressions", "validators"]}, - {"name": "removeUnusedShapes", "args": []} + { + "name": "excludeShapesByTag", + "args": { + "tags": ["internal"] + } + }, + { + "name": "excludeTraitsByTag", + "args": { + "tags": ["internal"] + } + }, + { + "name": "excludeMetadata", + "args": { + "keys": ["suppressions", "validators"] + } + }, + { + "name": "removeUnusedShapes" + } ] } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/ProjectionTransformer.java b/smithy-build/src/main/java/software/amazon/smithy/build/ProjectionTransformer.java index 7afec9c69dd..830b4d0f3b0 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/ProjectionTransformer.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/ProjectionTransformer.java @@ -17,14 +17,11 @@ import java.util.Collection; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Optional; import java.util.ServiceLoader; -import java.util.function.BiFunction; import java.util.function.Function; import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.utils.ListUtils; /** @@ -48,14 +45,13 @@ default Collection getAliases() { } /** - * Creates a function that transforms a model using the provided - * {@link ModelTransformer}. + * Transforms the given model using the provided {@link TransformContext}. * - * @param arguments Arguments used to create the ModelTransformer. + * @param context Transformation context. * @return Returns the created transformer. * @throws IllegalArgumentException if the arguments are invalid. */ - BiFunction createTransformer(List arguments); + Model transform(TransformContext context); /** * Creates a {@code ProjectionTransformer} factory function using SPI diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuild.java b/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuild.java index c82e258ea16..e10e3ee3a94 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuild.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuild.java @@ -39,6 +39,9 @@ * and writes the artifacts to a {@link FileManifest}. */ public final class SmithyBuild { + /** The version of Smithy build. */ + public static final String VERSION = "1.0"; + SmithyBuildConfig config; Path importBasePath; Path outputDirectory; diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java b/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java index 8b2ea110eff..776d4aacb75 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuildImpl.java @@ -18,6 +18,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; @@ -31,34 +32,33 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.function.BiConsumer; -import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.logging.Logger; import java.util.regex.Pattern; -import java.util.stream.Stream; import software.amazon.smithy.build.model.ProjectionConfig; import software.amazon.smithy.build.model.SmithyBuildConfig; import software.amazon.smithy.build.model.TransformConfig; +import software.amazon.smithy.build.transforms.Apply; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.ModelAssembler; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.model.validation.ValidatedResult; +import software.amazon.smithy.utils.Pair; import software.amazon.smithy.utils.SmithyBuilder; final class SmithyBuildImpl { private static final Logger LOGGER = Logger.getLogger(SmithyBuild.class.getName()); - private static final String APPLY_PROJECTIONS = "apply"; private static final Pattern PATTERN = Pattern.compile("^[A-Za-z0-9\\-_.]+$"); private final SmithyBuildConfig config; private final Function fileManifestFactory; private final Supplier modelAssemblerSupplier; private final Path outputDirectory; - private final Map> transformers = new HashMap<>(); + private final Map>> transformers = new HashMap<>(); private final ModelTransformer modelTransformer; private final Function> transformFactory; private final Function> pluginFactory; @@ -106,7 +106,9 @@ final class SmithyBuildImpl { : Paths.get(".").toAbsolutePath().normalize(); // Create the transformers for each projection. - config.getProjections().forEach((k, p) -> transformers.put(k, createTransformer(k, p, new LinkedHashSet<>()))); + config.getProjections().forEach((projectionName, projectionConfig) -> { + transformers.put(projectionName, createTransformers(projectionName, projectionConfig)); + }); pluginClassLoader = builder.pluginClassLoader; projectionFilter = builder.projectionFilter; @@ -305,10 +307,9 @@ private ProjectionResult applyProjection(String projectionName, ProjectionConfig // Create the base directory where all projection artifacts are stored. Path baseProjectionDir = outputDirectory.resolve(projectionName); - // Project the model and collect the results. - Model projectedModel = transformers - .get(projectionName) - .apply(modelTransformer, resolvedModel); + // Transform the model and collect the results. + Model projectedModel = applyProjectionTransforms( + resolvedModel, resolvedModel, projectionName, Collections.emptySet()); ValidatedResult modelResult = modelAssemblerSupplier.get().addModel(projectedModel).assemble(); @@ -327,6 +328,31 @@ private ProjectionResult applyProjection(String projectionName, ProjectionConfig return resultBuilder.build(); } + private Model applyProjectionTransforms( + Model inputModel, + Model originalModel, + String projectionName, + Set visited + ) { + // Transform the model and collect the results. + Model projectedModel = inputModel; + + for (Pair transformerBinding : transformers.get(projectionName)) { + TransformContext context = TransformContext.builder() + .model(projectedModel) + .originalModel(originalModel) + .transformer(modelTransformer) + .projectionName(projectionName) + .sources(sources) + .settings(transformerBinding.left) + .visited(visited) + .build(); + projectedModel = transformerBinding.right.transform(context); + } + + return projectedModel; + } + private void applyPlugin( String projectionName, ProjectionConfig projection, @@ -377,54 +403,55 @@ private Map resolvePlugins(ProjectionConfig projection) { return result; } - private BiFunction createTransformer( + // Creates pairs where the left value is the configuration arguments of the + // transformer, and the right value is the instantiated transformer. + private List> createTransformers( String projectionName, - ProjectionConfig projection, - Set visited + ProjectionConfig config ) { - if (visited.contains(projectionName)) { - visited.add(projectionName); - throw new SmithyBuildException(String.format("Cycle found in %s transforms: %s -> ...", - APPLY_PROJECTIONS, String.join(" -> ", visited))); - } + List> resolved = new ArrayList<>(config.getTransforms().size()); - visited.add(projectionName); + for (TransformConfig transformConfig : config.getTransforms()) { + String name = transformConfig.getName(); - // Create a composed transformer of each created transformer. - return projection.getTransforms().stream() - .flatMap(transform -> getTransform(projectionName, transform, visited)) - .reduce((a, b) -> (transformer, model) -> b.apply(transformer, a.apply(transformer, model))) - .orElse(((transformer, model) -> model)); - } + if (name.equals("apply")) { + resolved.add(createApplyTransformer(projectionName, transformConfig)); + continue; + } - private Stream> getTransform( - String projection, - TransformConfig config, - Set visited - ) { - String name = config.getName(); - - if (name.equals(APPLY_PROJECTIONS)) { - return config.getArgs().stream().map(arg -> { - // Copy the set of visited projections to a new set; - // visiting the same projection isn't a problem, it's - // cycles that's problematic. - ProjectionConfig targetProjection = findProjection(projection, arg); - return createTransformer(arg, targetProjection, new LinkedHashSet<>(visited)); - }); + ProjectionTransformer transformer = transformFactory.apply(name) + .orElseThrow(() -> new UnknownTransformException(String.format( + "Unable to find a transform for `%s` in the `%s` projection.", name, projectionName))); + resolved.add(Pair.of(transformConfig.getArgs(), transformer)); } - ProjectionTransformer transformer = transformFactory.apply(name) - .orElseThrow(() -> new UnknownTransformException("Unable to find a transform for `" + name + "`.")); - return Stream.of(transformer.createTransformer(config.getArgs())); + return resolved; } - private ProjectionConfig findProjection(String projection, String name) { - if (!config.getProjections().containsKey(name)) { - throw new UnknownProjectionException(String.format( - "Unable to find projection named `%s` referenced by `%s`", name, projection)); - } + // This is a somewhat hacky special case that allows the "apply" transform to apply + // transformations defined on other projections. + private Pair createApplyTransformer( + String projectionName, + TransformConfig transformConfig + ) { + Apply.ApplyCallback callback = (currentModel, projectionTarget, visited) -> { + if (projectionTarget.equals(projectionName)) { + throw new SmithyBuildException( + "Cannot recursively apply the same projection: " + projectionName); + } else if (visited.contains(projectionTarget)) { + visited.add(projectionTarget); + throw new SmithyBuildException(String.format( + "Cycle found in apply transforms: %s -> ...", String.join(" -> ", visited))); + } else if (!transformers.containsKey(projectionTarget)) { + throw new UnknownProjectionException(String.format( + "Unable to find projection named `%s` referenced by `%s`", + projectionTarget, projectionName)); + } + Set updatedVisited = new LinkedHashSet<>(visited); + updatedVisited.add(projectionTarget); + return applyProjectionTransforms(currentModel, model, projectionTarget, updatedVisited); + }; - return config.getProjections().get(name); + return Pair.of(transformConfig.getArgs(), new Apply(callback)); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/TransformContext.java b/smithy-build/src/main/java/software/amazon/smithy/build/TransformContext.java new file mode 100644 index 00000000000..c161959855a --- /dev/null +++ b/smithy-build/src/main/java/software/amazon/smithy/build/TransformContext.java @@ -0,0 +1,207 @@ +/* + * Copyright 2020 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.build; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.transform.ModelTransformer; +import software.amazon.smithy.utils.SetUtils; +import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.utils.ToSmithyBuilder; + +/** + * Context object used when applying a {@link ProjectionTransformer}. + * + *

Implementer's note: A context object is used to allow contextual + * information provided to projection transforms to evolve over time. + */ +public final class TransformContext implements ToSmithyBuilder { + + private final ObjectNode settings; + private final Model model; + private final Model originalModel; + private final Set sources; + private final String projectionName; + private final ModelTransformer transformer; + private final Set visited; + + private TransformContext(Builder builder) { + model = SmithyBuilder.requiredState("model", builder.model); + transformer = builder.transformer != null ? builder.transformer : ModelTransformer.create(); + settings = builder.settings; + originalModel = builder.originalModel; + sources = SetUtils.copyOf(builder.sources); + projectionName = builder.projectionName; + visited = new LinkedHashSet<>(builder.visited); + } + + /** + * @return Returns a TransformContext builder. + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public Builder toBuilder() { + return builder() + .settings(settings) + .model(model) + .originalModel(originalModel) + .sources(sources) + .projectionName(projectionName) + .transformer(transformer) + .visited(visited); + } + + /** + * Gets the arguments object of the transform. + * + * @return Returns the transformer arguments. + */ + public ObjectNode getSettings() { + return settings; + } + + /** + * Gets the model to transform. + * + * @return Returns the model to transform. + */ + public Model getModel() { + return model; + } + + /** + * Get the original model before applying the projection. + * + * @return The optionally provided original model. + */ + public Optional getOriginalModel() { + return Optional.ofNullable(originalModel); + } + + /** + * Gets the source models, or models that are considered the subject + * of the build. + * + *

This does not return an exhaustive set of model paths! There are + * typically two kinds of models that are added to a build: source + * models and discovered models. Discovered models are someone else's + * models. Source models are the models owned by the package being built. + * + * @return Returns the source models. + */ + public Set getSources() { + return sources; + } + + /** + * Gets the name of the projection being applied. + * + *

If no projection could be found, "source" is assumed. + * + * @return Returns the explicit or assumed projection name. + */ + public String getProjectionName() { + return projectionName; + } + + /** + * Gets the {@code ModelTransformer} that has been configured to aid + * in the transformation. + * + * @return Returns the model transformer. + */ + public ModelTransformer getTransformer() { + return transformer; + } + + /** + * Gets the set of previously visited transforms. + * + *

This method is used as bookkeeping for the {@code apply} + * plugin to detect cycles. + * + * @return Returns the ordered set of visited projections. + */ + public Set getVisited() { + return visited; + } + + /** + * Builds a {@link TransformContext}. + */ + public static final class Builder implements SmithyBuilder { + + private ObjectNode settings = Node.objectNode(); + private Model model; + private Model originalModel; + private Set sources = Collections.emptySet(); + private String projectionName = "source"; + private ModelTransformer transformer; + private Set visited = Collections.emptySet(); + + private Builder() {} + + @Override + public TransformContext build() { + return new TransformContext(this); + } + + public Builder settings(ObjectNode settings) { + this.settings = Objects.requireNonNull(settings); + return this; + } + + public Builder model(Model model) { + this.model = Objects.requireNonNull(model); + return this; + } + + public Builder originalModel(Model originalModel) { + this.originalModel = originalModel; + return this; + } + + public Builder sources(Set sources) { + this.sources = Objects.requireNonNull(sources); + return this; + } + + public Builder projectionName(String projectionName) { + this.projectionName = Objects.requireNonNull(projectionName); + return this; + } + + public Builder transformer(ModelTransformer transformer) { + this.transformer = transformer; + return this; + } + + public Builder visited(Set visited) { + this.visited = visited; + return this; + } + } +} diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/model/ConfigLoader.java b/smithy-build/src/main/java/software/amazon/smithy/build/model/ConfigLoader.java index 0e679c6ff5b..236376162ad 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/model/ConfigLoader.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/model/ConfigLoader.java @@ -16,19 +16,15 @@ package software.amazon.smithy.build.model; import java.nio.file.Path; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import software.amazon.smithy.build.SmithyBuildException; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.loader.ModelSyntaxException; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.NodeMapper; import software.amazon.smithy.model.node.NodeVisitor; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.node.StringNode; @@ -39,24 +35,6 @@ * Loads a {@link SmithyBuildConfig} from disk. */ final class ConfigLoader { - private static final String VERSION = "1.0"; - private static final String VERSION_KEY = "version"; - private static final String IMPORTS_KEY = "imports"; - private static final String OUTPUT_DIRECTORY_KEY = "outputDirectory"; - private static final String PROJECTIONS_KEY = "projections"; - private static final String PLUGINS_KEY = "plugins"; - private static final String ABSTRACT_KEY = "abstract"; - private static final String FILTERS_KEY = "filters"; - private static final String MAPPERS_KEY = "mappers"; - private static final String TRANSFORMS_KEY = "transforms"; - private static final String NAME_KEY = "name"; - private static final String ARGS_KEY = "args"; - - private static final List ROOT_KEYS = Arrays.asList( - VERSION_KEY, IMPORTS_KEY, OUTPUT_DIRECTORY_KEY, PROJECTIONS_KEY, PLUGINS_KEY); - private static final List PROJECTION_KEYS = Arrays.asList( - ABSTRACT_KEY, FILTERS_KEY, MAPPERS_KEY, TRANSFORMS_KEY, IMPORTS_KEY, PLUGINS_KEY); - private static final List TRANSFORM_KEYS = Arrays.asList(NAME_KEY, ARGS_KEY); private ConfigLoader() {} @@ -74,63 +52,8 @@ private static Node loadWithJson(Path path, String contents) { } private static SmithyBuildConfig load(ObjectNode node) { - SmithyBuildConfig.Builder builder = SmithyBuildConfig.builder(); - node.warnIfAdditionalProperties(ROOT_KEYS); - - node.expectStringMember(VERSION_KEY).expectOneOf(VERSION); - builder.imports(node.getArrayMember(IMPORTS_KEY) - .map(imports -> Node.loadArrayOfString(IMPORTS_KEY, imports)) - .orElse(Collections.emptyList())); - node.getStringMember(OUTPUT_DIRECTORY_KEY).map(StringNode::getValue).ifPresent(builder::outputDirectory); - builder.projections(node.getObjectMember(PROJECTIONS_KEY) - .map(ConfigLoader::loadProjections) - .orElse(Collections.emptyMap())); - builder.plugins(node.getObjectMember(PLUGINS_KEY) - .map(ConfigLoader::loadPlugins) - .orElse(Collections.emptyMap())); - return builder.build(); - } - - private static Map loadProjections(ObjectNode container) { - return container.getMembers().entrySet().stream() - .map(entry -> Pair.of(entry.getKey().getValue(), loadProjection(entry.getValue().expectObjectNode()))) - .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); - } - - private static ProjectionConfig loadProjection(ObjectNode members) { - members.warnIfAdditionalProperties(PROJECTION_KEYS); - ProjectionConfig.Builder builder = ProjectionConfig.builder() - .isAbstract(members.getBooleanMemberOrDefault(ABSTRACT_KEY)); - builder.transforms(members.getArrayMember(TRANSFORMS_KEY) - .map(ConfigLoader::loadTransforms) - .orElse(Collections.emptyList())); - builder.imports(members.getArrayMember(IMPORTS_KEY) - .map(imports -> Node.loadArrayOfString(IMPORTS_KEY, imports)) - .orElse(Collections.emptyList())); - builder.plugins(members.getObjectMember(PLUGINS_KEY) - .map(ConfigLoader::loadPlugins) - .orElse(Collections.emptyMap())); - return builder.build(); - } - - private static List loadTransforms(ArrayNode node) { - return node.getElements().stream() - .map(element -> { - ObjectNode objectNode = element.expectObjectNode(); - objectNode.warnIfAdditionalProperties(TRANSFORM_KEYS); - String name = objectNode.expectStringMember(NAME_KEY).getValue(); - List args = objectNode.getArrayMember(ARGS_KEY) - .map(argsNode -> argsNode.getElementsAs(StringNode::getValue)) - .orElseGet(Collections::emptyList); - return TransformConfig.builder().name(name).args(args).build(); - }) - .collect(Collectors.toList()); - } - - private static Map loadPlugins(ObjectNode container) { - return container.getMembers().entrySet().stream() - .map(entry -> Pair.of(entry.getKey().getValue(), entry.getValue().expectObjectNode())) - .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); + NodeMapper mapper = new NodeMapper(); + return mapper.deserialize(node, SmithyBuildConfig.class); } /** diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/model/ProjectionConfig.java b/smithy-build/src/main/java/software/amazon/smithy/build/model/ProjectionConfig.java index d7d8c1d84e6..b21e9a2cf3e 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/model/ProjectionConfig.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/model/ProjectionConfig.java @@ -21,10 +21,7 @@ import java.util.List; import java.util.Map; import software.amazon.smithy.build.SmithyBuildException; -import software.amazon.smithy.model.node.ArrayNode; -import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; -import software.amazon.smithy.model.node.ToNode; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.MapUtils; import software.amazon.smithy.utils.SmithyBuilder; @@ -32,7 +29,7 @@ /** * ProjectionConfig stored in a {@link SmithyBuildConfig}. */ -public final class ProjectionConfig implements ToNode { +public final class ProjectionConfig { private final boolean isAbstract; private final List imports; private final List transforms; @@ -83,28 +80,6 @@ public List getImports() { return imports; } - @Override - public Node toNode() { - ObjectNode.Builder result = Node.objectNodeBuilder(); - if (isAbstract) { - result.withMember("abstract", Node.from(true)); - } - - if (!imports.isEmpty()) { - result.withMember("imports", imports.stream().map(Node::from).collect(ArrayNode.collect())); - } - - return result - .withMember("transforms", createTransformerNode(getTransforms())) - .withMember("plugins", getPlugins().entrySet().stream() - .collect(ObjectNode.collectStringKeys(Map.Entry::getKey, Map.Entry::getValue))) - .build(); - } - - private static Node createTransformerNode(List values) { - return values.stream().map(TransformConfig::toNode).collect(ArrayNode.collect()); - } - /** * Builds a {@link ProjectionConfig}. */ @@ -133,7 +108,7 @@ public ProjectionConfig build() { * @param isAbstract Set to true to mark as abstract. * @return Returns the builder. */ - public Builder isAbstract(boolean isAbstract) { + public Builder setAbstract(boolean isAbstract) { this.isAbstract = isAbstract; return this; } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/model/SmithyBuildConfig.java b/smithy-build/src/main/java/software/amazon/smithy/build/model/SmithyBuildConfig.java index 31a71b3648d..64e1c8c97c7 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/model/SmithyBuildConfig.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/model/SmithyBuildConfig.java @@ -40,6 +40,7 @@ public final class SmithyBuildConfig implements ToSmithyBuilder { private static final Set BUILTIN_PLUGINS = SetUtils.of("build-info", "model", "sources"); + private final String version; private final List imports; private final String outputDirectory; private final Map projections; @@ -47,6 +48,8 @@ public final class SmithyBuildConfig implements ToSmithyBuilder { private final List imports = new ArrayList<>(); private final Map projections = new LinkedHashMap<>(); private final Map plugins = new LinkedHashMap<>(); + private String version; private String outputDirectory; private Path importBasePath; @@ -180,6 +194,17 @@ public SmithyBuildConfig build() { return new SmithyBuildConfig(this); } + /** + * Sets the builder config file version. + * + * @param version Version to set. + * @return Returns the builder. + */ + public Builder version(String version) { + this.version = version; + return this; + } + /** * Loads and merges the config file into the builder. * @@ -199,6 +224,7 @@ public Builder load(Path config) { */ public Builder merge(SmithyBuildConfig config) { config.getOutputDirectory().ifPresent(this::outputDirectory); + version(config.getVersion()); imports.addAll(config.getImports()); projections.putAll(config.getProjections()); plugins.putAll(config.getPlugins()); diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/model/TransformConfig.java b/smithy-build/src/main/java/software/amazon/smithy/build/model/TransformConfig.java index b2eac7d0191..8ec8ec31e4a 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/model/TransformConfig.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/model/TransformConfig.java @@ -15,23 +15,20 @@ package software.amazon.smithy.build.model; -import java.util.Collections; -import java.util.List; import software.amazon.smithy.model.node.Node; -import software.amazon.smithy.model.node.ToNode; -import software.amazon.smithy.utils.ListUtils; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.utils.SmithyBuilder; /** * Transform configuration found in a projection. */ -public final class TransformConfig implements ToNode { +public final class TransformConfig { private final String name; - private final List args; + private final ObjectNode args; private TransformConfig(Builder builder) { name = SmithyBuilder.requiredState("name", builder.name); - args = ListUtils.copyOf(builder.args); + args = builder.args; } public static Builder builder() { @@ -48,21 +45,13 @@ public String getName() { /** * @return Gets the args. */ - public List getArgs() { + public ObjectNode getArgs() { return args; } - @Override - public Node toNode() { - return Node.objectNodeBuilder() - .withMember("name", Node.from(getName())) - .withMember("args", Node.fromStrings(args)) - .build(); - } - public static final class Builder implements SmithyBuilder { private String name; - private List args = Collections.emptyList(); + private ObjectNode args = Node.objectNode(); private Builder() {} @@ -85,11 +74,22 @@ public Builder name(String name) { /** * Sets the args of the transform. * + *

If an array is provided, the array is automatically converted + * to an object with a key named "__args" that contains the array. + * This is a backward compatibility shim for older versions of + * Smithy Builder that only accepts a list of strings for + * projection transforms. + * * @param args Arguments to set. * @return Returns the builder. */ - public Builder args(List args) { - this.args = args; + public Builder args(Node args) { + if (args.isArrayNode()) { + this.args = Node.objectNode().withMember("__args", args); + } else { + this.args = args.expectObjectNode(); + } + return this; } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/plugins/BuildInfo.java b/smithy-build/src/main/java/software/amazon/smithy/build/plugins/BuildInfo.java new file mode 100644 index 00000000000..67926c16553 --- /dev/null +++ b/smithy-build/src/main/java/software/amazon/smithy/build/plugins/BuildInfo.java @@ -0,0 +1,180 @@ +/* + * Copyright 2020 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.build.plugins; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import software.amazon.smithy.build.SmithyBuild; +import software.amazon.smithy.build.model.ProjectionConfig; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.validation.ValidationEvent; + +/** + * POJO to represents a smithy-build-info.json file. + */ +public final class BuildInfo { + private String version = SmithyBuild.VERSION; + private String projectionName = "source"; + private ProjectionConfig projection; + private List validationEvents = Collections.emptyList(); + private List traitNames = Collections.emptyList(); + private List traitDefNames = Collections.emptyList(); + private List serviceShapeIds = Collections.emptyList(); + private List operationShapeIds = Collections.emptyList(); + private List resourceShapeIds = Collections.emptyList(); + private Map metadata = Collections.emptyMap(); + + /** + * @return Gets the version of the build-info file format. + */ + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + /** + * @return Gets the name of the projection used in this build. + */ + public String getProjectionName() { + return projectionName; + } + + public void setProjectionName(String projectionName) { + this.projectionName = projectionName; + } + + /** + * @return Gets the projection configuration. + */ + public ProjectionConfig getProjection() { + return projection; + } + + public void setProjection(ProjectionConfig projection) { + this.projection = projection; + } + + /** + * @return Gets the validation events encountered by the projection. + */ + public List getValidationEvents() { + return validationEvents; + } + + public void setValidationEvents(List validationEvents) { + this.validationEvents = validationEvents; + } + + /** + * @return Gets the shape ID of every trait used in the projected model. + */ + public List getTraitNames() { + return traitNames; + } + + public void setTraitNames(List traitNames) { + this.traitNames = traitNames; + } + + /** + * @return Gets the shape ID of every trait shape defined in the projection. + */ + public List getTraitDefNames() { + return traitDefNames; + } + + public void setTraitDefNames(List traitDefNames) { + this.traitDefNames = traitDefNames; + } + + /** + * @return Gets the shape ID of every service in the projection. + */ + public List getServiceShapeIds() { + return serviceShapeIds; + } + + public void setServiceShapeIds(List serviceShapeIds) { + this.serviceShapeIds = serviceShapeIds; + } + + /** + * @return Gets the shape ID of every operation in the projection. + */ + public List getOperationShapeIds() { + return operationShapeIds; + } + + public void setOperationShapeIds(List operationShapeIds) { + this.operationShapeIds = operationShapeIds; + } + + /** + * @return Gets the shape ID of every resource in the projection. + */ + public List getResourceShapeIds() { + return resourceShapeIds; + } + + public void setResourceShapeIds(List resourceShapeIds) { + this.resourceShapeIds = resourceShapeIds; + } + + /** + * @return Gets the model metadata in the projection. + */ + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } else if (!(o instanceof BuildInfo)) { + return false; + } + + BuildInfo buildInfo = (BuildInfo) o; + return Objects.equals(getVersion(), buildInfo.getVersion()) + && Objects.equals(getProjectionName(), buildInfo.getProjectionName()) + && Objects.equals(getProjection(), buildInfo.getProjection()) + && Objects.equals(getValidationEvents(), buildInfo.getValidationEvents()) + && Objects.equals(getTraitNames(), buildInfo.getTraitNames()) + && Objects.equals(getTraitDefNames(), buildInfo.getTraitDefNames()) + && Objects.equals(getServiceShapeIds(), buildInfo.getServiceShapeIds()) + && Objects.equals(getOperationShapeIds(), buildInfo.getOperationShapeIds()) + && Objects.equals(getResourceShapeIds(), buildInfo.getResourceShapeIds()) + && Objects.equals(getMetadata(), buildInfo.getMetadata()); + } + + @Override + public int hashCode() { + return Objects.hash(getVersion(), getProjectionName(), getProjection(), getValidationEvents(), + getTraitNames(), getTraitDefNames(), getServiceShapeIds(), getOperationShapeIds(), + getResourceShapeIds(), getMetadata()); + } +} diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/plugins/BuildInfoPlugin.java b/smithy-build/src/main/java/software/amazon/smithy/build/plugins/BuildInfoPlugin.java index 73987e6d9cc..8b508c3f6bd 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/plugins/BuildInfoPlugin.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/plugins/BuildInfoPlugin.java @@ -15,19 +15,18 @@ package software.amazon.smithy.build.plugins; -import java.util.Map; +import java.util.List; +import java.util.stream.Collectors; import software.amazon.smithy.build.PluginContext; import software.amazon.smithy.build.SmithyBuildPlugin; import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.Node; -import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.node.NodeMapper; import software.amazon.smithy.model.shapes.OperationShape; import software.amazon.smithy.model.shapes.ResourceShape; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.validation.ValidationEvent; /** * Writes a build info file for each projection. This file contains the @@ -39,11 +38,8 @@ * *

This plugin is only invoked if the projection and original model are * configured on the provided PluginContext. - * - *

TODO: define the schema of this build artifact. */ public final class BuildInfoPlugin implements SmithyBuildPlugin { - private static final String BUILD_INFO_VERSION = "1.0"; private static final String NAME = "build-info"; @Override @@ -64,44 +60,35 @@ public void execute(PluginContext context) { } private static Node serializeBuildInfo(PluginContext context) { - return Node.objectNodeBuilder() - .withMember("version", Node.from(BUILD_INFO_VERSION)) - .withMember("projectionName", Node.from(context.getProjectionName())) - .withMember("projection", context.getProjection().get().toNode()) - .withMember("validationEvents", context.getEvents().stream() - .map(ValidationEvent::toNode) - .collect(ArrayNode.collect())) - .withMember("traitNames", findTraitNames(context.getModel())) - .withMember("traitDefNames", context.getModel().getTraitShapes().stream() - .map(Shape::getId) - .map(ShapeId::toString) - .sorted() - .map(Node::from) - .collect(ArrayNode.collect())) - .withMember("serviceShapeIds", findShapeIds(context.getModel(), ServiceShape.class)) - .withMember("operationShapeIds", findShapeIds(context.getModel(), OperationShape.class)) - .withMember("resourceShapeIds", findShapeIds(context.getModel(), ResourceShape.class)) - .withMember("metadata", context.getModel().getMetadata().entrySet().stream() - .collect(ObjectNode.collectStringKeys(Map.Entry::getKey, Map.Entry::getValue))) - .build(); + BuildInfo info = new BuildInfo(); + info.setProjectionName(context.getProjectionName()); + info.setProjection(context.getProjection().orElse(null)); + info.setValidationEvents(context.getEvents()); + info.setTraitNames(findTraitNames(context.getModel())); + info.setTraitDefNames(context.getModel().getTraitShapes().stream() + .map(Shape::getId) + .sorted() + .collect(Collectors.toList())); + info.setServiceShapeIds(findShapeIds(context.getModel(), ServiceShape.class)); + info.setOperationShapeIds(findShapeIds(context.getModel(), OperationShape.class)); + info.setResourceShapeIds(findShapeIds(context.getModel(), ResourceShape.class)); + info.setMetadata(context.getModel().getMetadata()); + NodeMapper mapper = new NodeMapper(); + return mapper.serialize(info); } - private static Node findTraitNames(Model model) { + private static List findTraitNames(Model model) { return model.shapes() .flatMap(shape -> shape.getAllTraits().keySet().stream()) - .map(ShapeId::toString) .distinct() .sorted() - .map(Node::from) - .collect(ArrayNode.collect()); + .collect(Collectors.toList()); } - private static Node findShapeIds(Model model, Class clazz) { + private static List findShapeIds(Model model, Class clazz) { return model.shapes(clazz) .map(Shape::getId) - .map(Object::toString) .sorted() - .map(Node::from) - .collect(ArrayNode.collect()); + .collect(Collectors.toList()); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/plugins/ConfigurableSmithyBuildPlugin.java b/smithy-build/src/main/java/software/amazon/smithy/build/plugins/ConfigurableSmithyBuildPlugin.java new file mode 100644 index 00000000000..f68438b7cf4 --- /dev/null +++ b/smithy-build/src/main/java/software/amazon/smithy/build/plugins/ConfigurableSmithyBuildPlugin.java @@ -0,0 +1,67 @@ +/* + * Copyright 2020 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.build.plugins; + +import software.amazon.smithy.build.PluginContext; +import software.amazon.smithy.build.SmithyBuildPlugin; +import software.amazon.smithy.model.node.NodeMapper; + +/** + * An abstract class used to more easily implement a Smithy build plugin + * that expects configuration input in a specific type, {@code T}. + * + *

This class will automatically deserialize the given {@code Node} + * value in the {@code T} and invoke {@link #executeWithConfig(PluginContext, Object)} + * with the deserialized configuration of type {@code T}. + * + *

If your build plugin requires configuration, then you typically + * should just extend this class.

+ * + * @param The configuration setting type (e.g., a POJO). + */ +public abstract class ConfigurableSmithyBuildPlugin implements SmithyBuildPlugin { + /** + * Gets the configuration class type. + * + *

The referenced {@code configType} class must be a public POJO with a + * public, zero-arg constructor, getters, and setters. If the POJO has a + * public static {@code fromNode} method, it will be invoked and is + * expected to deserialize the Node. If the POJO has a public static + * {@code builder} method, it will be invoked, setters will be called + * on the builder POJO, and finally the result of calling the + * {@code build} method is used as the configuration type. Finally, + * the deserializer will attempt to create the type and call setters on + * it that correspond to property names. + * + * @return Returns the configuration class (a POJO with setters/getters). + */ + public abstract Class getConfigType(); + + @Override + public void execute(PluginContext context) { + NodeMapper mapper = new NodeMapper(); + T config = mapper.deserialize(context.getSettings(), getConfigType()); + executeWithConfig(context, config); + } + + /** + * Executes the plugin using the deserialized configuration object. + * + * @param context Plugin context. + * @param config Deserialized configuration object. + */ + protected abstract void executeWithConfig(PluginContext context, T config); +} diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/Apply.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/Apply.java new file mode 100644 index 00000000000..655458eb668 --- /dev/null +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/Apply.java @@ -0,0 +1,101 @@ +/* + * Copyright 2020 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.build.transforms; + +import java.util.List; +import java.util.Set; +import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.model.Model; + +/** + * Recursively applies transforms of other projections. + * + *

Note: this transform is special cased and not created using a + * normal factory. This is because this transformer needs to + * recursively transform models based on projections, and no other + * transform needs this functionality. We could *maybe* address + * this later if we really care that much. + */ +public class Apply extends BackwardCompatHelper { + + /** + * {@code apply} configuration. + */ + public static final class Config { + private List projections; + + /** + * Gets the ordered list of projections to apply by name. + * + * @return Returns the projection names to apply. + */ + public List getProjections() { + return projections; + } + + /** + * Sets the ordered list of projection names to apply. + * + * @param projections Projection names to apply. + */ + public void setProjections(List projections) { + this.projections = projections; + } + } + + @FunctionalInterface + public interface ApplyCallback { + Model apply(Model inputModel, String projectionName, Set visited); + } + + private final ApplyCallback applyCallback; + + /** + * Sets the function used to apply projections. + * + * @param applyCallback Takes the projection name, model, and returns the updated model. + */ + public Apply(ApplyCallback applyCallback) { + this.applyCallback = applyCallback; + } + + @Override + public Class getConfigType() { + return Config.class; + } + + @Override + public String getName() { + return "apply"; + } + + @Override + String getBackwardCompatibleNameMapping() { + return "projections"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Model current = context.getModel(); + Set visited = context.getVisited(); + + for (String projection : config.getProjections()) { + current = applyCallback.apply(current, projection, visited); + } + + return current; + } +} diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/BackwardCompatHelper.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/BackwardCompatHelper.java new file mode 100644 index 00000000000..dbcc599a699 --- /dev/null +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/BackwardCompatHelper.java @@ -0,0 +1,129 @@ +/* + * Copyright 2020 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.build.transforms; + +import java.util.logging.Logger; +import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.ObjectNode; + +/** + * Helper class used to allow older versions of smithy-build.json files to + * automatically rewrite a list of strings in an object to a member named + * "__args" that contains the list of strings. + * + *

For example, the following deprecated JSON: + * + *

{@code
+ * {
+ *     "version": "1.0",
+ *     "projections": {
+ *         "projection-name": {
+ *             "transforms": [
+ *                 {
+ *                     "name": "transform-name",
+ *                     "args": [
+ *                         "argument1",
+ *                         "argument2"
+ *                     ]
+ *                 }
+ *             }
+ *         }
+ *     }
+ * }
+ * }
+ * + *

Is rewritten to the following JSON using {@code ConfigLoader}: + * + *

{@code
+ * {
+ *     "version": "1.0",
+ *     "projections": {
+ *         "projection-name": {
+ *             "transforms": [
+ *                 {
+ *                     "name": "transform-name",
+ *                     "args": {
+ *                         "__args": [
+ *                             "argument1",
+ *                             "argument2"
+ *                         ]
+ *                     }
+ *                 }
+ *             }
+ *         }
+ *     }
+ * }
+ * }
+ * + *

And this, in turn, uses the result of {@link #getBackwardCompatibleNameMapping()} + * to rewrite the JSON to the preferred format: + * + *

{@code
+ * {
+ *     "version": "1.0",
+ *     "projections": {
+ *         "projection-name": {
+ *             "transforms": [
+ *                 {
+ *                     "name": "transform-name",
+ *                     "args": {
+ *                         "": [
+ *                             "argument1",
+ *                             "argument2"
+ *                         ]
+ *                     }
+ *                 }
+ *             }
+ *         }
+ *     }
+ * }
+ * }
+ * + * @param Type of configuration object to deserialize into. + */ +abstract class BackwardCompatHelper extends ConfigurableProjectionTransformer { + + private static final Logger LOGGER = Logger.getLogger(BackwardCompatHelper.class.getName()); + private static final String ARGS = "__args"; + + /** + * Gets the name that "__args" is to be rewritten to. + * + * @return Returns the name to rewrite. + */ + abstract String getBackwardCompatibleNameMapping(); + + @Override + public final Model transform(TransformContext context) { + ObjectNode original = context.getSettings(); + + if (!original.getMember(ARGS).isPresent()) { + return super.transform(context); + } + + LOGGER.warning(() -> String.format( + "Deprecated projection transform arguments detected for `%s`; change this list of strings " + + "to an object with a property named `%s`", getName(), getBackwardCompatibleNameMapping())); + + ObjectNode updated = original.toBuilder() + .withMember(getBackwardCompatibleNameMapping(), original.getMember(ARGS).get()) + .withoutMember(ARGS) + .build(); + + return super.transform(context.toBuilder().settings(updated).build()); + } +} diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ConfigurableProjectionTransformer.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ConfigurableProjectionTransformer.java new file mode 100644 index 00000000000..c0ab6b7fd84 --- /dev/null +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ConfigurableProjectionTransformer.java @@ -0,0 +1,70 @@ +/* + * Copyright 2020 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.build.transforms; + +import software.amazon.smithy.build.ProjectionTransformer; +import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.NodeMapper; + +/** + * An abstract class used to more easily implement a Smithy build projection + * transformer that expects configuration input in a specific type, {@code T}. + * + *

This class will automatically deserialize the given {@code Node} + * value in the {@code T} and invoke {@link #transformWithConfig(TransformContext, Object)} + * with the deserialized configuration of type {@code T}. + * + *

If your build transformer requires configuration, then you typically + * should just extend this class.

+ * + * @param The configuration setting type (e.g., a POJO). + */ +public abstract class ConfigurableProjectionTransformer implements ProjectionTransformer { + /** + * Gets the configuration class type. + * + *

The referenced {@code configType} class must be a public POJO with a + * public, zero-arg constructor, getters, and setters. If the POJO has a + * public static {@code fromNode} method, it will be invoked and is + * expected to deserialize the Node. If the POJO has a public static + * {@code builder} method, it will be invoked, setters will be called + * on the builder POJO, and finally the result of calling the + * {@code build} method is used as the configuration type. Finally, + * the deserializer will attempt to create the type and call setters on + * the instantiated object that correspond to property names (either named + * "set" + property name, or just property name). + * + * @return Returns the configuration class (a POJO with setters/getters). + */ + public abstract Class getConfigType(); + + @Override + public Model transform(TransformContext context) { + NodeMapper mapper = new NodeMapper(); + T config = mapper.deserialize(context.getSettings(), getConfigType()); + return transformWithConfig(context, config); + } + + /** + * Executes the transform using the deserialized configuration object. + * + * @param context Transform context. + * @param config Deserialized configuration object. + * @return Returns the transformed model. + */ + protected abstract Model transformWithConfig(TransformContext context, T config); +} diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeMetadata.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeMetadata.java index b1c3813210e..ad41576be4e 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeMetadata.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeMetadata.java @@ -15,24 +15,61 @@ package software.amazon.smithy.build.transforms; -import java.util.List; -import java.util.function.BiFunction; -import software.amazon.smithy.build.ProjectionTransformer; +import java.util.Collections; +import java.util.Set; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.transform.ModelTransformer; /** - * Removes metadata entries when a key matches any of the given arguments. + * {@code excludeMetadata} removes metadata entries when a metadata key + * matches {@code keys}. */ -public final class ExcludeMetadata implements ProjectionTransformer { +public final class ExcludeMetadata extends BackwardCompatHelper { + + /** + * {@code excludeMetadata} configuration settings. + */ + public static final class Config { + private Set keys = Collections.emptySet(); + + /** + * @return the list of keys to remove from metadata. + */ + public Set getKeys() { + return keys; + } + + /** + * Sets the list of keys to remove from metadata. + * + * @param keys Metadata keys to remove. + */ + public void setKeys(Set keys) { + this.keys = keys; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "excludeMetadata"; } @Override - public BiFunction createTransformer(List arguments) { - return (transformer, model) -> transformer.filterMetadata( - model, (key, value) -> !arguments.contains(key)); + String getBackwardCompatibleNameMapping() { + return "keys"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + Set keys = config.getKeys(); + return transformer.filterMetadata(model, (key, value) -> !keys.contains(key)); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeShapesByTag.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeShapesByTag.java index 3275b47646d..c8f1f7e3bf4 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeShapesByTag.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeShapesByTag.java @@ -17,21 +17,50 @@ import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.function.BiFunction; -import software.amazon.smithy.build.ProjectionTransformer; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.transform.ModelTransformer; /** - * Removes shapes if they are tagged with one or more of the given arguments. + * {@code excludeShapesByTag} removes shapes if they are tagged with one or more + * of the given arguments. * *

Prelude shapes are not removed by this transformer. */ -public final class ExcludeShapesByTag implements ProjectionTransformer { +public final class ExcludeShapesByTag extends BackwardCompatHelper { + + /** + * {@code excludeShapesByTag} configuration. + */ + public static final class Config { + private Set tags = Collections.emptySet(); + + /** + * Gets the set of tags that causes shapes to be removed. + * + * @return Returns the removal tags. + */ + public Set getTags() { + return tags; + } + + /** + * Sets the set of tags that causes shapes to be removed. + * + * @param tags Tags that cause shapes to be removed. + */ + public void setTags(Set tags) { + this.tags = tags; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "excludeShapesByTag"; @@ -43,10 +72,17 @@ public Collection getAliases() { } @Override - public BiFunction createTransformer(List arguments) { - Set includeTags = new HashSet<>(arguments); - return (transformer, model) -> transformer.filterShapes( - model, shape -> Prelude.isPreludeShape(shape) - || shape.getTags().stream().noneMatch(includeTags::contains)); + String getBackwardCompatibleNameMapping() { + return "tags"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Set includeTags = config.getTags(); + ModelTransformer transformer = context.getTransformer(); + Model model = context.getModel(); + return transformer.filterShapes(model, shape -> { + return Prelude.isPreludeShape(shape) || shape.getTags().stream().noneMatch(includeTags::contains); + }); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTags.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTags.java index 3877e9fc4ba..2064cb22518 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTags.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTags.java @@ -15,17 +15,59 @@ package software.amazon.smithy.build.transforms; +import java.util.Collections; +import java.util.Set; +import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.model.Model; + /** - * Removes tags from shapes and trait definitions that match any of the - * provided arguments (a list of allowed tags). + * {@code excludeTags} removes tags from shapes and trait definitions + * that match any of the provided {@code tags}. */ -public final class ExcludeTags extends AbstractTagMapper { - public ExcludeTags() { - super(true); +public final class ExcludeTags extends BackwardCompatHelper { + + /** + * {@code excludeTags} configuration. + */ + public static final class Config { + private Set tags = Collections.emptySet(); + + /** + * Gets the set of tags that are removed from the model. + * + * @return Returns the tags to remove. + */ + public Set getTags() { + return tags; + } + + /** + * Sets the set of tags that are removed from the model. + * + * @param tags The tags to remove from the model. + */ + public void setTags(Set tags) { + this.tags = tags; + } + } + + @Override + public Class getConfigType() { + return Config.class; } @Override public String getName() { return "excludeTags"; } + + @Override + String getBackwardCompatibleNameMapping() { + return "tags"; + } + + @Override + public Model transformWithConfig(TransformContext context, Config config) { + return TagUtils.excludeShapeTags(context.getTransformer(), context.getModel(), config.getTags()); + } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTraits.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTraits.java index c35a37c0ae2..e9e7dcd84f4 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTraits.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTraits.java @@ -15,11 +15,11 @@ package software.amazon.smithy.build.transforms; -import java.util.List; +import java.util.Collections; import java.util.Set; -import java.util.function.BiFunction; import java.util.logging.Logger; import java.util.stream.Collectors; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; @@ -27,37 +27,80 @@ import software.amazon.smithy.utils.Pair; /** - * Removes trait definitions and traits from shapes when a trait name - * matches any of the given arguments. + * {@code excludeTraits} removes trait definitions and traits from + * shapes when a trait name matches any of the values given in + * {@code traits}. * - *

End an arguments with "#" to exclude the traits of an entire - * namespace. + *

Arguments that end with "#" exclude the traits of an entire + * namespace. Trait shape IDs that are relative are assumed to be + * part of the {@code smithy.api} prelude namespace. */ -public final class ExcludeTraits extends AbstractTraitRemoval { +public final class ExcludeTraits extends BackwardCompatHelper { + private static final Logger LOGGER = Logger.getLogger(ExcludeTraits.class.getName()); + /** + * {@code excludeTraits} configuration settings. + */ + public static final class Config { + private Set traits = Collections.emptySet(); + + /** + * Gets the list of trait shape IDs/namespaces to exclude. + * + * @return shape IDs to exclude. + */ + public Set getTraits() { + return traits; + } + + /** + * Sets the list of trait shape IDs/namespaces to exclude. + * + *

Relative shape IDs are considered traits in the prelude + * namespace, {@code smithy.api}. Strings ending in "#" are + * used to exclude traits from an entire namespace. + * + * @param traits Traits to exclude. + */ + public void setTraits(Set traits) { + this.traits = traits; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "excludeTraits"; } @Override - public BiFunction createTransformer(List arguments) { - Pair, Set> namesAndNamespaces = parseTraits(arguments); + String getBackwardCompatibleNameMapping() { + return "traits"; + } + + @Override + public Model transformWithConfig(TransformContext context, Config config) { + Pair, Set> namesAndNamespaces = TraitRemovalUtils.parseTraits(config.getTraits()); Set names = namesAndNamespaces.getLeft(); Set namespaces = namesAndNamespaces.getRight(); LOGGER.info(() -> "Excluding traits by ID " + names + " and namespaces " + namespaces); - return (transformer, model) -> { - Set removeTraits = model.getTraitShapes().stream() - .filter(trait -> matchesTraitDefinition(trait, names, namespaces)) - .collect(Collectors.toSet()); + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + + Set removeTraits = model.getTraitShapes().stream() + .filter(trait -> TraitRemovalUtils.matchesTraitDefinition(trait, names, namespaces)) + .collect(Collectors.toSet()); - if (!removeTraits.isEmpty()) { - LOGGER.info(() -> "Excluding traits: " + removeTraits); - } + if (!removeTraits.isEmpty()) { + LOGGER.info(() -> "Excluding traits: " + removeTraits); + } - return transformer.removeShapes(model, removeTraits); - }; + return transformer.removeShapes(model, removeTraits); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTraitsByTag.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTraitsByTag.java index b5793c565b5..eaf2edfc285 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTraitsByTag.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/ExcludeTraitsByTag.java @@ -16,34 +16,75 @@ package software.amazon.smithy.build.transforms; import java.util.Collection; -import java.util.List; -import java.util.function.BiFunction; -import software.amazon.smithy.build.ProjectionTransformer; +import java.util.Collections; +import java.util.Set; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.traits.TraitDefinition; import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.utils.Tagged; /** - * Removes traits and trait definitions from a model if the trait definition - * contains any of the provided tags. + * {@code excludeTraitsByTag} removes traits and trait definitions + * from a model if the trait definition contains any of the provided + * {@code tags}. * *

This transformer will not remove prelude trait definitions. */ -public final class ExcludeTraitsByTag implements ProjectionTransformer { +public final class ExcludeTraitsByTag extends BackwardCompatHelper { + + /** + * {@code excludeTraitsByTag} configuration settings. + */ + public static final class Config { + private Set tags = Collections.emptySet(); + + /** + * @return the list of tags that, if present, cause the trait to be removed. + */ + public Set getTags() { + return tags; + } + + /** + * Sets the list of tags that, if present, cause the trait to be removed. + * + * @param tags Tags to set. + */ + public void setTags(Set tags) { + this.tags = tags; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "excludeTraitsByTag"; } @Override - public BiFunction createTransformer(List arguments) { - return (transformer, model) -> transformer.removeShapesIf( - model, - shape -> !Prelude.isPreludeShape(shape) - && shape.hasTrait(TraitDefinition.class) - && hasAnyTag(shape, arguments)); + String getBackwardCompatibleNameMapping() { + return "tags"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + Set tags = config.getTags(); + return transformer.removeShapesIf(model, shape -> removeIfPredicate(shape, tags)); + } + + private boolean removeIfPredicate(Shape shape, Collection tags) { + return !Prelude.isPreludeShape(shape) + && shape.hasTrait(TraitDefinition.class) + && hasAnyTag(shape, tags); } private boolean hasAnyTag(Tagged tagged, Collection tags) { diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeMetadata.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeMetadata.java index b99c4feee87..34e313d6d09 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeMetadata.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeMetadata.java @@ -15,17 +15,45 @@ package software.amazon.smithy.build.transforms; -import java.util.List; -import java.util.function.BiFunction; -import software.amazon.smithy.build.ProjectionTransformer; +import java.util.Collections; +import java.util.Set; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.transform.ModelTransformer; /** - * Removes metadata entries when a key does not match any of the given - * arguments. + * {@code includeMetadata} keeps only metadata keys specifically + * defined in the provided {@code keys} setting. */ -public final class IncludeMetadata implements ProjectionTransformer { +public final class IncludeMetadata extends BackwardCompatHelper { + + /** + * {@code includeMetadata} configuration settings. + */ + public static final class Config { + private Set keys = Collections.emptySet(); + + /** + * @return the list of keys to keep in metadata. + */ + public Set getKeys() { + return keys; + } + + /** + * Sets the list of keys to keep in metadata. + * + * @param keys Metadata keys to keep. + */ + public void setKeys(Set keys) { + this.keys = keys; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } @Override public String getName() { @@ -33,8 +61,15 @@ public String getName() { } @Override - public BiFunction createTransformer(List arguments) { - return (transformer, model) -> transformer.filterMetadata( - model, (key, value) -> arguments.contains(key)); + String getBackwardCompatibleNameMapping() { + return "keys"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + Set keys = config.getKeys(); + return transformer.filterMetadata(model, (key, value) -> keys.contains(key)); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeNamespaces.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeNamespaces.java index 7b61b409aaa..911700f9d22 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeNamespaces.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeNamespaces.java @@ -15,32 +15,66 @@ package software.amazon.smithy.build.transforms; -import java.util.HashSet; -import java.util.List; +import java.util.Collections; import java.util.Set; -import java.util.function.BiFunction; -import software.amazon.smithy.build.ProjectionTransformer; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.transform.ModelTransformer; /** - * Filters out shapes and trait definitions that are not part of one of the - * given namespaces. + * {@code includeNamespaces} filters out shapes and trait definitions + * that are not part of one of the given {@code namespaces}. * *

Note that this does not filter out prelude shapes or namespaces. */ -public final class IncludeNamespaces implements ProjectionTransformer { +public final class IncludeNamespaces extends BackwardCompatHelper { + + /** + * {@code includeNamespaces} configuration. + */ + public static final class Config { + private Set namespaces = Collections.emptySet(); + + /** + * @return Gets the list of namespaces to include. + */ + public Set getNamespaces() { + return namespaces; + } + + /** + * Sets the list of namespaces to include. + * + * @param namespaces Namespaces to include. + */ + public void setNamespaces(Set namespaces) { + this.namespaces = namespaces; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "includeNamespaces"; } @Override - public BiFunction createTransformer(List arguments) { - Set includeNamespaces = new HashSet<>(arguments); - return (transformer, model) -> transformer.filterShapes( - model, shape -> Prelude.isPreludeShape(shape) - || includeNamespaces.contains(shape.getId().getNamespace())); + String getBackwardCompatibleNameMapping() { + return "namespaces"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Set namespaces = config.getNamespaces(); + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + return transformer.filterShapes(model, shape -> { + return Prelude.isPreludeShape(shape) || namespaces.contains(shape.getId().getNamespace()); + }); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeServices.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeServices.java index db489f29d0d..c86160e09f5 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeServices.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeServices.java @@ -15,31 +15,63 @@ package software.amazon.smithy.build.transforms; -import java.util.List; +import java.util.Collections; import java.util.Set; -import java.util.function.BiFunction; -import java.util.stream.Collectors; -import software.amazon.smithy.build.ProjectionTransformer; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.transform.ModelTransformer; /** - * Filters out service shapes that are not included in the arguments list of - * service shape IDs. + * {@code includeServices} filters out service shapes that are not + * included in the list of shape IDs contained in the + * {@code services} property. */ -public final class IncludeServices implements ProjectionTransformer { +public final class IncludeServices extends BackwardCompatHelper { + + /** + * {@code includeServices} configuration. + */ + public static final class Config { + private Set services = Collections.emptySet(); + + /** + * @return Gets the list of service shapes IDs to include. + */ + public Set getServices() { + return services; + } + + /** + * Sets the list of service shapes IDs to include. + * + * @param services Services to include by shape ID. + */ + public void setServices(Set services) { + this.services = services; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "includeServices"; } @Override - public BiFunction createTransformer(List arguments) { - Set includeServices = arguments.stream() - .map(ShapeId::from) - .collect(Collectors.toSet()); - return (transformer, model) -> transformer.filterShapes( - model, shape -> !shape.isServiceShape() || includeServices.contains(shape.getId())); + String getBackwardCompatibleNameMapping() { + return "services"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Set services = config.getServices(); + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + return transformer.filterShapes(model, shape -> !shape.isServiceShape() || services.contains(shape.getId())); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeShapesByTag.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeShapesByTag.java index 8b83cafc98b..8f6ea1078c4 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeShapesByTag.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeShapesByTag.java @@ -17,22 +17,51 @@ import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.function.BiFunction; -import software.amazon.smithy.build.ProjectionTransformer; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.transform.ModelTransformer; /** - * Removes shapes and trait definitions that are not tagged with at - * least one of the given trait arguments. + * {@code includeShapesByTag} removes shapes and trait definitions + * that are not tagged with at least one of the tags provided + * in the {@code tags} argument. * *

Prelude shapes are not removed by this transformer. */ -public final class IncludeShapesByTag implements ProjectionTransformer { +public final class IncludeShapesByTag extends BackwardCompatHelper { + + /** + * {@code includeShapesByTag} configuration. + */ + public static final class Config { + private Set tags = Collections.emptySet(); + + /** + * Gets the set of tags that cause shapes to be included. + * + * @return Returns the inclusion tags. + */ + public Set getTags() { + return tags; + } + + /** + * Sets the set of tags that cause shapes to be included. + * + * @param tags Tags that cause shapes to be included. + */ + public void setTags(Set tags) { + this.tags = tags; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "includeShapesByTag"; @@ -44,10 +73,17 @@ public Collection getAliases() { } @Override - public BiFunction createTransformer(List arguments) { - Set includeTags = new HashSet<>(arguments); - return (transformer, model) -> transformer.filterShapes( - model, shape -> Prelude.isPreludeShape(shape) - || shape.getTags().stream().anyMatch(includeTags::contains)); + String getBackwardCompatibleNameMapping() { + return "tags"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Set includeTags = config.getTags(); + ModelTransformer transformer = context.getTransformer(); + Model model = context.getModel(); + return transformer.filterShapes(model, shape -> { + return Prelude.isPreludeShape(shape) || shape.getTags().stream().anyMatch(includeTags::contains); + }); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTags.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTags.java index fe5af2ade89..4260ca3055e 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTags.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTags.java @@ -15,17 +15,60 @@ package software.amazon.smithy.build.transforms; +import java.util.Collections; +import java.util.Set; +import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.model.Model; + /** - * Removes tags from shapes and trait definitions that are not in the - * argument list (a list of allowed tags). + * {@code includeTags} removes tags from shapes and trait + * definitions that are not in the set of tags defined in + * the {@code tags} property. */ -public final class IncludeTags extends AbstractTagMapper { - public IncludeTags() { - super(false); +public final class IncludeTags extends BackwardCompatHelper { + + /** + * {@code includeTags} configuration. + */ + public static final class Config { + private Set tags = Collections.emptySet(); + + /** + * Gets the set of tags that are retained in the model. + * + * @return Returns the tags to retain. + */ + public Set getTags() { + return tags; + } + + /** + * Sets the set of tags that are retained in the model. + * + * @param tags The tags to retain in the model. + */ + public void setTags(Set tags) { + this.tags = tags; + } + } + + @Override + public Class getConfigType() { + return Config.class; } @Override public String getName() { return "includeTags"; } + + @Override + String getBackwardCompatibleNameMapping() { + return "tags"; + } + + @Override + public Model transformWithConfig(TransformContext context, Config config) { + return TagUtils.includeShapeTags(context.getTransformer(), context.getModel(), config.getTags()); + } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTraits.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTraits.java index f28e6242dd6..451b7d28ea3 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTraits.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTraits.java @@ -15,11 +15,11 @@ package software.amazon.smithy.build.transforms; -import java.util.List; +import java.util.Collections; import java.util.Set; -import java.util.function.BiFunction; import java.util.logging.Logger; import java.util.stream.Collectors; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; @@ -28,24 +28,65 @@ import software.amazon.smithy.utils.Pair; /** - * Removes trait definitions when a trait name does not match one of the - * arguments (a list of trait names). Any instance of the trait is also - * removed from the model. + * {@code includeTraits} removes trait definitions when a trait name + * does not match one of the provided {@code traits} shape IDs. Any + * instance of the trait is also removed from the model. * - *

End an arguments with "#" to include the traits from an entire - * namespace. + *

End an argument with "#" to include the traits from an entire + * namespace. Trait shape IDs that are relative are assumed to be part + * of the {@code smithy.api} prelude namespace. */ -public final class IncludeTraits extends AbstractTraitRemoval { +public final class IncludeTraits extends BackwardCompatHelper { + private static final Logger LOGGER = Logger.getLogger(IncludeTraits.class.getName()); + /** + * {@code includeTraits} configuration settings. + */ + public static final class Config { + private Set traits = Collections.emptySet(); + + /** + * Gets the list of trait shape IDs to include. + * + * @return shape IDs to include. + */ + public Set getTraits() { + return traits; + } + + /** + * Sets the list of trait shape IDs to include. + * + *

End an argument with "#" to include the traits from an entire + * namespace. Trait shape IDs that are relative are assumed to be + * part of the {@code smithy.api} prelude namespace. + * + * @param traits Traits to include. + */ + public void setTraits(Set traits) { + this.traits = traits; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "includeTraits"; } @Override - public BiFunction createTransformer(List arguments) { - Pair, Set> namesAndNamespaces = parseTraits(arguments); + String getBackwardCompatibleNameMapping() { + return "traits"; + } + + @Override + public Model transformWithConfig(TransformContext context, Config config) { + Pair, Set> namesAndNamespaces = TraitRemovalUtils.parseTraits(config.getTraits()); Set names = namesAndNamespaces.getLeft(); Set namespaces = namesAndNamespaces.getRight(); LOGGER.info(() -> "Including traits by ID " + names + " and namespaces " + namespaces); @@ -53,16 +94,17 @@ public BiFunction createTransformer(List // Don't remove the trait definition trait because it breaks everything! names.add(TraitDefinition.ID); - return (transformer, model) -> { - Set removeTraits = model.getTraitShapes().stream() - .filter(trait -> !matchesTraitDefinition(trait, names, namespaces)) - .collect(Collectors.toSet()); + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + + Set removeTraits = model.getTraitShapes().stream() + .filter(trait -> !TraitRemovalUtils.matchesTraitDefinition(trait, names, namespaces)) + .collect(Collectors.toSet()); - if (!removeTraits.isEmpty()) { - LOGGER.info(() -> "Removing traits that are not explicitly allowed: " + removeTraits); - } + if (!removeTraits.isEmpty()) { + LOGGER.info(() -> "Removing traits that are not explicitly allowed: " + removeTraits); + } - return transformer.removeShapes(model, removeTraits); - }; + return transformer.removeShapes(model, removeTraits); } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTraitsByTag.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTraitsByTag.java index 0d652e762c6..3b4b0282e7e 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTraitsByTag.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/IncludeTraitsByTag.java @@ -16,34 +16,75 @@ package software.amazon.smithy.build.transforms; import java.util.Collection; -import java.util.List; -import java.util.function.BiFunction; -import software.amazon.smithy.build.ProjectionTransformer; +import java.util.Collections; +import java.util.Set; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.traits.TraitDefinition; import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.utils.Tagged; /** - * Removes trait definitions from a model if the definition does - * not contain at least one of the provided tags. + * {@code includeTraitsByTag} removes trait definitions from a model if + * the definition does not contain at least one of the provided {@code tags}. * *

This transformer does not remove prelude traits. */ -public final class IncludeTraitsByTag implements ProjectionTransformer { +public final class IncludeTraitsByTag extends BackwardCompatHelper { + + /** + * {@code includeTraitsByTag} configuration settings. + */ + public static final class Config { + private Set tags = Collections.emptySet(); + + /** + * @return the list of tags that must be present for a trait to be kept. + */ + public Set getTags() { + return tags; + } + + /** + * Sets the list of tags that must be present for a trait to be included + * in the filtered model. + * + * @param tags Tags to set. + */ + public void setTags(Set tags) { + this.tags = tags; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "includeTraitsByTag"; } @Override - public BiFunction createTransformer(List arguments) { - return (transformer, model) -> transformer.removeShapesIf( - model, - shape -> !Prelude.isPreludeShape(shape) - && shape.hasTrait(TraitDefinition.class) - && !hasAnyTag(shape, arguments)); + String getBackwardCompatibleNameMapping() { + return "tags"; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); + Set tags = config.getTags(); + return transformer.removeShapesIf(model, shape -> removeIfPredicate(shape, tags)); + } + + private boolean removeIfPredicate(Shape shape, Collection tags) { + return !Prelude.isPreludeShape(shape) + && shape.hasTrait(TraitDefinition.class) + && !hasAnyTag(shape, tags); } private boolean hasAnyTag(Tagged tagged, Collection tags) { diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/RemoveUnusedShapes.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/RemoveUnusedShapes.java index c8b9d4f69a7..4f7454e6dbf 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/RemoveUnusedShapes.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/RemoveUnusedShapes.java @@ -17,27 +17,55 @@ import java.util.Collection; import java.util.Collections; -import java.util.HashSet; -import java.util.List; import java.util.Set; -import java.util.function.BiFunction; import java.util.function.Predicate; -import software.amazon.smithy.build.ProjectionTransformer; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.transform.ModelTransformer; /** - * Removes shapes from the model that are not connected to any service shape. - * - *

You can export shapes that are not connected to any service shape by - * applying specific tags to the shape and adding the list of export tags as - * arguments to the treeShaker. + * {@code removeUnusedShapes} removes shapes from the model that are not + * connected to any service shape. * *

Shapes from the prelude *are* removed if they are not referenced as * part of a model. */ -public final class RemoveUnusedShapes implements ProjectionTransformer { +public final class RemoveUnusedShapes extends BackwardCompatHelper { + + /** + * {@code removeUnusedShapes} configuration settings. + */ + public static final class Config { + + private Set exportTagged = Collections.emptySet(); + + /** + * You can export shapes that are not connected to any service + * shape by applying specific tags to the shape and adding the list of + * export tags as arguments to the treeShaker. + * + * @param exportByTags Tags that cause shapes to be exported. + */ + public void setExportTagged(Set exportByTags) { + this.exportTagged = exportByTags; + } + + /** + * Gets the set of tags that are used to export shapes. + * + * @return the tags that are used to export shapes. + */ + public Set getExportTagged() { + return exportTagged; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + @Override public String getName() { return "removeUnusedShapes"; @@ -49,21 +77,24 @@ public Collection getAliases() { } @Override - public BiFunction createTransformer(List arguments) { - Set includeTags = new HashSet<>(arguments); - Predicate keepShapesByTag = shape -> includeTags.stream().noneMatch(shape::hasTag); - Predicate keepTraitDefsByTag = trait -> includeTags.stream().noneMatch(trait::hasTag); + public String getBackwardCompatibleNameMapping() { + return "exportTagged"; + } - return (transformer, model) -> { - int currentShapeCount; + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Predicate keepShapesByTag = shape -> config.getExportTagged().stream().noneMatch(shape::hasTag); + Predicate keepTraitDefsByTag = trait -> config.getExportTagged().stream().noneMatch(trait::hasTag); + Model model = context.getModel(); + ModelTransformer transformer = context.getTransformer(); - do { - currentShapeCount = model.toSet().size(); - model = transformer.removeUnreferencedShapes(model, keepShapesByTag); - model = transformer.removeUnreferencedTraitDefinitions(model, keepTraitDefsByTag); - } while (currentShapeCount != model.toSet().size()); + int currentShapeCount; + do { + currentShapeCount = model.toSet().size(); + model = transformer.removeUnreferencedShapes(model, keepShapesByTag); + model = transformer.removeUnreferencedTraitDefinitions(model, keepTraitDefsByTag); + } while (currentShapeCount != model.toSet().size()); - return model; - }; + return model; } } diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/AbstractTagMapper.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/TagUtils.java similarity index 63% rename from smithy-build/src/main/java/software/amazon/smithy/build/transforms/AbstractTagMapper.java rename to smithy-build/src/main/java/software/amazon/smithy/build/transforms/TagUtils.java index 888a5edb222..a410e0d4379 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/AbstractTagMapper.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/TagUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 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. @@ -17,31 +17,35 @@ import java.util.Collection; import java.util.HashSet; -import java.util.List; import java.util.Optional; import java.util.Set; -import java.util.function.BiFunction; -import software.amazon.smithy.build.ProjectionTransformer; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.traits.TagsTrait; import software.amazon.smithy.model.transform.ModelTransformer; -abstract class AbstractTagMapper implements ProjectionTransformer { - private final boolean exclude; +/** + * Utilities for {@link ExcludeTags} and {@link IncludeTags}. + */ +final class TagUtils { + + private TagUtils() {} - AbstractTagMapper(boolean exclude) { - this.exclude = exclude; + static Model excludeShapeTags(ModelTransformer transformer, Model model, Set tags) { + return includeExcludeShapeTags(transformer, model, tags, true); } - @Override - public BiFunction createTransformer(List arguments) { - Set tags = new HashSet<>(arguments); - return (transformer, model) -> removeShapeTags(transformer, model, tags); + static Model includeShapeTags(ModelTransformer transformer, Model model, Set tags) { + return includeExcludeShapeTags(transformer, model, tags, false); } - private Model removeShapeTags(ModelTransformer transformer, Model model, Set tags) { - return transformer.mapShapes(model, shape -> intersectIfChanged(shape.getTags(), tags) + private static Model includeExcludeShapeTags( + ModelTransformer transformer, + Model model, + Set tags, + boolean exclude + ) { + return transformer.mapShapes(model, shape -> intersectIfChanged(shape.getTags(), tags, exclude) .map(intersection -> { TagsTrait.Builder builder = TagsTrait.builder(); intersection.forEach(builder::addValue); @@ -50,7 +54,11 @@ private Model removeShapeTags(ModelTransformer transformer, Model model, Set> intersectIfChanged(Collection subject, Collection other) { + private static Optional> intersectIfChanged( + Collection subject, + Collection other, + boolean exclude + ) { Set temp = new HashSet<>(subject); if (exclude) { temp.removeAll(other); diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/AbstractTraitRemoval.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/TraitRemovalUtils.java similarity index 65% rename from smithy-build/src/main/java/software/amazon/smithy/build/transforms/AbstractTraitRemoval.java rename to smithy-build/src/main/java/software/amazon/smithy/build/transforms/TraitRemovalUtils.java index aeda2659121..bc1ddefb735 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/AbstractTraitRemoval.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/TraitRemovalUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * Copyright 2020 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. @@ -16,27 +16,26 @@ package software.amazon.smithy.build.transforms; import java.util.HashSet; -import java.util.List; import java.util.Set; -import software.amazon.smithy.build.ProjectionTransformer; -import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.traits.Trait; import software.amazon.smithy.utils.Pair; -abstract class AbstractTraitRemoval implements ProjectionTransformer { - Pair, Set> parseTraits(List arguments) { +/** + * Utilities for {@link IncludeTraits} and {@link ExcludeTraits}. + */ +final class TraitRemovalUtils { + + private TraitRemovalUtils() {} + + static Pair, Set> parseTraits(Set ids) { Set traitNames = new HashSet<>(); Set traitNamespaces = new HashSet<>(); - for (String arg : arguments) { + for (String arg : ids) { if (arg.endsWith("#")) { traitNamespaces.add(arg.substring(0, arg.length() - 1)); - } else if (arg.equals(Prelude.NAMESPACE)) { - // For backwards compatibility, support "smithy.api" instead - // of "smithy.api#". - traitNamespaces.add(arg); } else { traitNames.add(ShapeId.from(Trait.makeAbsoluteName(arg))); } @@ -45,7 +44,7 @@ Pair, Set> parseTraits(List arguments) { return Pair.of(traitNames, traitNamespaces); } - boolean matchesTraitDefinition(Shape traitShape, Set traitNames, Set traitNamespaces) { + static boolean matchesTraitDefinition(Shape traitShape, Set traitNames, Set traitNamespaces) { return traitNames.contains(traitShape.getId()) || traitNamespaces.contains(traitShape.getId().getNamespace()); } } diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/ProjectionConfigTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/ProjectionConfigTest.java index 4c1d582cb01..168e89b4569 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/ProjectionConfigTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/ProjectionConfigTest.java @@ -22,6 +22,7 @@ import org.junit.jupiter.api.Test; import software.amazon.smithy.build.model.ProjectionConfig; import software.amazon.smithy.build.model.TransformConfig; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.utils.ListUtils; public class ProjectionConfigTest { @@ -29,10 +30,10 @@ public class ProjectionConfigTest { public void buildsProjections() { TransformConfig t = TransformConfig.builder() .name("foo") - .args(ListUtils.of("baz")) + .args(Node.objectNode().withMember("__args", Node.fromStrings("baz"))) .build(); ProjectionConfig p = ProjectionConfig.builder() - .isAbstract(false) + .setAbstract(false) .transforms(ListUtils.of(t)) .build(); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/SmithyBuildTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/SmithyBuildTest.java index 5cd6944cfa3..3c516d1ecc3 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/SmithyBuildTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/SmithyBuildTest.java @@ -16,15 +16,12 @@ package software.amazon.smithy.build; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; @@ -79,8 +76,8 @@ public void loadsEmptyObject() throws Exception { @Test public void throwsForUnknownTransform() throws Exception { Assertions.assertThrows(UnknownTransformException.class, () -> { - SmithyBuildConfig config = SmithyBuildConfig.load( - Paths.get(getClass().getResource("unknown-transform.json").toURI())); + SmithyBuildConfig config = SmithyBuildConfig + .load(Paths.get(getClass().getResource("unknown-transform.json").toURI())); new SmithyBuild().config(config).build(); }); } @@ -185,6 +182,7 @@ public void ignoresUnknownPlugins() throws Exception { public void cannotSetFiltersOrMappersOnSourceProjection() { Throwable thrown = Assertions.assertThrows(SmithyBuildException.class, () -> { SmithyBuildConfig config = SmithyBuildConfig.builder() + .version(SmithyBuild.VERSION) .projections(MapUtils.of("source", ProjectionConfig.builder() .transforms(ListUtils.of(TransformConfig.builder().name("foo").build())) .build())) @@ -401,6 +399,18 @@ public void detectsMissingApplyProjection() throws Exception { assertThat(thrown.getMessage(), containsString("Unable to find projection named `bar` referenced by `foo`")); } + @Test + public void detectsDirectlyRecursiveApply() throws Exception { + Throwable thrown = Assertions.assertThrows(SmithyBuildException.class, () -> { + SmithyBuildConfig config = SmithyBuildConfig.builder() + .load(Paths.get(getClass().getResource("apply-direct-recursion.json").toURI())) + .build(); + new SmithyBuild().config(config).build(); + }); + + assertThat(thrown.getMessage(), containsString("Cannot recursively apply the same projection:")); + } + @Test public void appliesProjections() throws Exception { Model model = Model.assembler() @@ -432,6 +442,7 @@ public void appliesProjections() throws Exception { public void pluginsMustHaveValidNames() { Throwable thrown = Assertions.assertThrows(SmithyBuildException.class, () -> { SmithyBuildConfig config = SmithyBuildConfig.builder() + .version(SmithyBuild.VERSION) .plugins(MapUtils.of("!invalid", Node.objectNode())) .build(); new SmithyBuild().config(config).build(); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/model/SmithyBuildConfigTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/model/SmithyBuildConfigTest.java index 61ddf799c4c..8b7305e9abc 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/model/SmithyBuildConfigTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/model/SmithyBuildConfigTest.java @@ -16,7 +16,6 @@ package software.amazon.smithy.build.model; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.empty; @@ -31,9 +30,12 @@ import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.SmithyBuild; import software.amazon.smithy.build.SmithyBuildException; import software.amazon.smithy.build.SmithyBuildTest; import software.amazon.smithy.model.SourceException; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.utils.ListUtils; public class SmithyBuildConfigTest { @@ -86,7 +88,10 @@ public void canLoadJsonDataIntoBuilder() { @Test public void canAddImports() { String importPath = getResourcePath("simple-model.json"); - SmithyBuildConfig config = SmithyBuildConfig.builder().imports(ListUtils.of(importPath)).build(); + SmithyBuildConfig config = SmithyBuildConfig.builder() + .version(SmithyBuild.VERSION) + .imports(ListUtils.of(importPath)) + .build(); assertThat(config.getImports(), containsInAnyOrder(importPath)); } @@ -102,7 +107,9 @@ public void canAddImportsAndOutputDirViaJson() { @Test public void addsBuiltinPlugins() { - SmithyBuildConfig config = SmithyBuildConfig.builder().build(); + SmithyBuildConfig config = SmithyBuildConfig.builder() + .version(SmithyBuild.VERSION) + .build(); assertThat(config.getPlugins(), hasKey("build-info")); assertThat(config.getPlugins(), hasKey("model")); @@ -120,7 +127,8 @@ public void expandsEnvironmentVariables() { // Did the key expand? assertThat(transform.getName(), equalTo("includeByTag")); // Did the array and string values in it expand? - assertThat(transform.getArgs(), contains("Hi", "${BAZ}")); + assertThat(transform.getArgs(), equalTo(Node.objectNode() + .withMember("tags", Node.fromStrings("Hi", "${BAZ}")))); } @Test @@ -130,6 +138,16 @@ public void throwsForUnknownEnvironmentVariables() { }); } + @Test + public void rewritesArgsArrayToUnderscoreArgs() { + SmithyBuildConfig config = SmithyBuildConfig.load( + Paths.get(getResourcePath("rewrites-args-array.json"))); + ObjectNode node = config.getProjections().get("rewrite").getTransforms().get(0).getArgs(); + ObjectNode expected = Node.objectNode().withMember("__args", Node.fromStrings("sensitive")); + + Node.assertEquals(node, expected); + } + private String getResourcePath(String name) { return SmithyBuildTest.class.getResource(name).getPath(); } diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/plugins/ConfigurableSmithyBuildPluginTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/plugins/ConfigurableSmithyBuildPluginTest.java new file mode 100644 index 00000000000..34f0b7f690a --- /dev/null +++ b/smithy-build/src/test/java/software/amazon/smithy/build/plugins/ConfigurableSmithyBuildPluginTest.java @@ -0,0 +1,86 @@ +package software.amazon.smithy.build.plugins; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; + +import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.MockManifest; +import software.amazon.smithy.build.PluginContext; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.NodeMapper; + +public class ConfigurableSmithyBuildPluginTest { + @Test + public void loadsConfigurationClass() { + Model model = Model.assembler() + .addImport(getClass().getResource("sources/a.smithy").getPath()) + .assemble() + .unwrap(); + MockManifest manifest = new MockManifest(); + PluginContext context = PluginContext.builder() + .settings(Node.objectNode() + .withMember("foo", "hello") + .withMember("bar", 10) + .withMember("baz", true)) + .fileManifest(manifest) + .model(model) + .originalModel(model) + .build(); + new Configurable().execute(context); + + String manifestString = manifest.getFileString("Config").get(); + assertThat(manifestString, containsString("hello")); + assertThat(manifestString, containsString("10")); + assertThat(manifestString, containsString("true")); + } + + private static final class Configurable extends ConfigurableSmithyBuildPlugin { + @Override + public String getName() { + return "configurable"; + } + + @Override + public Class getConfigType() { + return Config.class; + } + + @Override + protected void executeWithConfig(PluginContext context, Config config) { + NodeMapper mapper = new NodeMapper(); + mapper.serialize(config); + context.getFileManifest().writeJson("Config", mapper.serialize(config)); + } + } + + public static final class Config { + private String foo; + private int bar; + private boolean baz; + + public String getFoo() { + return foo; + } + + public void setFoo(String foo) { + this.foo = foo; + } + + public int getBar() { + return bar; + } + + public void setBar(int bar) { + this.bar = bar; + } + + public boolean isBaz() { + return baz; + } + + public void setBaz(boolean baz) { + this.baz = baz; + } + } +} diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeMetadataTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeMetadataTest.java index f081c644237..bcb76784a29 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeMetadataTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeMetadataTest.java @@ -18,13 +18,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.Node; -import software.amazon.smithy.model.transform.ModelTransformer; public class ExcludeMetadataTest { @Test @@ -35,9 +34,11 @@ public void removesMetadataInList() { Model model = Model.builder() .metadata(metadata) .build(); - Model result = new ExcludeMetadata() - .createTransformer(Collections.singletonList("a")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("keys", Node.fromStrings("a"))) + .build(); + Model result = new ExcludeMetadata().transform(context); assertFalse(result.getMetadata().containsKey("a")); assertTrue(result.getMetadata().containsKey("b")); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeShapesByTagTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeShapesByTagTest.java index 0625158e1eb..7ac3a25a72d 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeShapesByTagTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeShapesByTagTest.java @@ -19,13 +19,13 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import java.util.Collections; import java.util.Optional; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.traits.TagsTrait; -import software.amazon.smithy.model.transform.ModelTransformer; public class ExcludeShapesByTagTest { @@ -42,9 +42,11 @@ public void removesTraitsNotInList() { Model model = Model.builder() .addShapes(stringA, stringB) .build(); - Model result = new ExcludeShapesByTag() - .createTransformer(Collections.singletonList("foo")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("tags", Node.fromStrings("foo"))) + .build(); + Model result = new ExcludeShapesByTag().transform(context); assertThat(result.getShape(stringA.getId()), is(Optional.empty())); assertThat(result.getShape(stringB.getId()), not(Optional.empty())); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeTraitsByTagTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeTraitsByTagTest.java index d934d2ea930..30b14200c4d 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeTraitsByTagTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeTraitsByTagTest.java @@ -19,14 +19,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Paths; -import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.transform.ModelTransformer; public class ExcludeTraitsByTagTest { @Test @@ -35,9 +35,11 @@ public void removesTraitsByTagInList() throws Exception { .addImport(Paths.get(getClass().getResource("tree-shaking-traits.json").toURI())) .assemble() .unwrap(); - Model result = new ExcludeTraitsByTag() - .createTransformer(Collections.singletonList("qux")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("tags", Node.fromStrings("qux"))) + .build(); + Model result = new ExcludeTraitsByTag().transform(context); Set traits = result.getTraitShapes().stream() .map(Shape::getId) .collect(Collectors.toSet()); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeTraitsTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeTraitsTest.java index 40823c39450..3b6dc0ffaca 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeTraitsTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/ExcludeTraitsTest.java @@ -20,16 +20,16 @@ import static org.hamcrest.Matchers.not; import static org.junit.jupiter.api.Assertions.assertFalse; -import java.util.Collections; import java.util.Optional; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.traits.DocumentationTrait; import software.amazon.smithy.model.traits.SensitiveTrait; -import software.amazon.smithy.model.transform.ModelTransformer; public class ExcludeTraitsTest { @@ -44,9 +44,11 @@ public void removesTraitsInList() { .addShape(stringShape) .assemble() .unwrap(); - Model result = new ExcludeTraits() - .createTransformer(Collections.singletonList("documentation")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("traits", Node.fromStrings("documentation"))) + .build(); + Model result = new ExcludeTraits().transform(context); assertThat(result.expectShape(ShapeId.from("ns.foo#baz")).getTrait(DocumentationTrait.class), is(Optional.empty())); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeMetadataTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeMetadataTest.java index f897d7ffeda..7c05af36625 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeMetadataTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeMetadataTest.java @@ -18,13 +18,12 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.Node; -import software.amazon.smithy.model.transform.ModelTransformer; public class IncludeMetadataTest { @Test @@ -35,9 +34,11 @@ public void removesMetadataNotInList() { Model model = Model.builder() .metadata(metadata) .build(); - Model result = new IncludeMetadata() - .createTransformer(Collections.singletonList("b")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("keys", Node.fromStrings("b"))) + .build(); + Model result = new IncludeMetadata().transform(context); assertFalse(result.getMetadata().containsKey("a")); assertTrue(result.getMetadata().containsKey("b")); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeNamespacesTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeNamespacesTest.java index 22c5dace61c..d0bd1c39011 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeNamespacesTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeNamespacesTest.java @@ -19,12 +19,12 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import java.util.Arrays; import java.util.Optional; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.StringShape; -import software.amazon.smithy.model.transform.ModelTransformer; public class IncludeNamespacesTest { @@ -35,9 +35,11 @@ public void removesShapesNotInNamespaces() { StringShape string3 = StringShape.builder().id("ns.bar#yuck").build(); StringShape string4 = StringShape.builder().id("ns.qux#yuck").build(); Model model = Model.builder().addShapes(string1, string2, string3, string4).build(); - Model result = new IncludeNamespaces() - .createTransformer(Arrays.asList("ns.foo", "ns.bar")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("namespaces", Node.fromStrings("ns.foo", "ns.bar"))) + .build(); + Model result = new IncludeNamespaces().transform(context); assertThat(result.getShape(string1.getId()), not(Optional.empty())); assertThat(result.getShape(string2.getId()), not(Optional.empty())); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeServicesTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeServicesTest.java index 1aa1e4dbea9..3325d42fd9c 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeServicesTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeServicesTest.java @@ -19,13 +19,13 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import java.util.Collections; import java.util.Optional; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.StringShape; -import software.amazon.smithy.model.transform.ModelTransformer; public class IncludeServicesTest { @@ -35,9 +35,11 @@ public void removesTraitsNotInList() { ServiceShape serviceB = ServiceShape.builder().id("ns.foo#bar").version("1").build(); StringShape string = StringShape.builder().id("ns.foo#yuck").build(); Model model = Model.builder().addShapes(serviceA, serviceB, string).build(); - Model result = new IncludeServices() - .createTransformer(Collections.singletonList("ns.foo#baz")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("services", Node.fromStrings("ns.foo#baz"))) + .build(); + Model result = new IncludeServices().transform(context); assertThat(result.getShape(serviceA.getId()), not(Optional.empty())); assertThat(result.getShape(string.getId()), not(Optional.empty())); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeShapesByTagTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeShapesByTagTest.java index fb3b602e648..39b4ad01f78 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeShapesByTagTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeShapesByTagTest.java @@ -19,13 +19,13 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import java.util.Collections; import java.util.Optional; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.traits.TagsTrait; -import software.amazon.smithy.model.transform.ModelTransformer; public class IncludeShapesByTagTest { @@ -42,9 +42,11 @@ public void removesTraitsNotInList() { Model model = Model.builder() .addShapes(stringA, stringB) .build(); - Model result = new IncludeShapesByTag() - .createTransformer(Collections.singletonList("foo")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("tags", Node.fromStrings("foo"))) + .build(); + Model result = new IncludeShapesByTag().transform(context); assertThat(result.getShape(stringA.getId()), not(Optional.empty())); assertThat(result.getShape(stringB.getId()), is(Optional.empty())); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTagsTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTagsTest.java index 64202a7fc6b..ff4adb9d196 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTagsTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTagsTest.java @@ -19,13 +19,13 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.equalTo; -import java.util.Collections; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.traits.TagsTrait; -import software.amazon.smithy.model.transform.ModelTransformer; public class IncludeTagsTest { @@ -41,9 +41,11 @@ public void removesTagsFromShapesNotInList() { .build(); Shape shape3 = StringShape.builder().id("ns.foo#shape3").build(); Model model = Model.builder().addShapes(shape1, shape2, shape3).build(); - Model result = new IncludeTags() - .createTransformer(Collections.singletonList("foo")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("tags", Node.fromStrings("foo"))) + .build(); + Model result = new IncludeTags().transform(context); assertThat(result.expectShape(shape1.getId()).getTags(), contains("foo")); assertThat(result.expectShape(shape2.getId()), equalTo(shape2)); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTraitsByTagTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTraitsByTagTest.java index 8bd4f8d7195..c0c2cd6ceef 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTraitsByTagTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTraitsByTagTest.java @@ -19,14 +19,14 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Paths; -import java.util.Collections; import java.util.Set; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.transform.ModelTransformer; public class IncludeTraitsByTagTest { @Test @@ -35,9 +35,11 @@ public void removesTraitsNoFoundWithTags() throws Exception { .addImport(Paths.get(getClass().getResource("tree-shaking-traits.json").toURI())) .assemble() .unwrap(); - Model result = new IncludeTraitsByTag() - .createTransformer(Collections.singletonList("baz")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("tags", Node.fromStrings("baz"))) + .build(); + Model result = new IncludeTraitsByTag().transform(context); Set traits = result.getTraitShapes().stream() .map(Shape::getId) .map(ShapeId::toString) diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTraitsTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTraitsTest.java index c58764e6ef8..7ca0b706fa7 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTraitsTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/IncludeTraitsTest.java @@ -21,17 +21,16 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.util.Collections; import java.util.Optional; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.model.shapes.StringShape; import software.amazon.smithy.model.traits.DocumentationTrait; import software.amazon.smithy.model.traits.SensitiveTrait; -import software.amazon.smithy.model.transform.ModelTransformer; -import software.amazon.smithy.utils.ListUtils; public class IncludeTraitsTest { @@ -46,9 +45,11 @@ public void removesTraitsNotInList() { .addShape(stringShape) .assemble() .unwrap(); - Model result = new IncludeTraits() - .createTransformer(ListUtils.of("documentation")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("traits", Node.fromStrings("documentation"))) + .build(); + Model result = new IncludeTraits().transform(context); assertThat(result.expectShape(ShapeId.from("ns.foo#baz")).getTrait(DocumentationTrait.class), not(Optional.empty())); @@ -70,9 +71,11 @@ public void includesBuiltinTraits() { .addShape(stringShape) .assemble() .unwrap(); - Model result = new IncludeTraits() - .createTransformer(Collections.singletonList("smithy.api")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("traits", Node.fromStrings("smithy.api#"))) + .build(); + Model result = new IncludeTraits().transform(context); assertThat(result.expectShape(ShapeId.from("ns.foo#baz")).getTrait(DocumentationTrait.class), not(Optional.empty())); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/RemoveUnusedShapesTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/RemoveUnusedShapesTest.java index 01c442de52d..ae0c7b0f926 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/RemoveUnusedShapesTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/RemoveUnusedShapesTest.java @@ -21,15 +21,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import java.nio.file.Paths; -import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.utils.FunctionalUtils; public class RemoveUnusedShapesTest { @@ -40,9 +40,11 @@ public void treeShakerWithExports() throws Exception { .addImport(Paths.get(getClass().getResource("tree-shaker.json").toURI())) .assemble() .unwrap(); - Model result = new RemoveUnusedShapes() - .createTransformer(Collections.singletonList("export")) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("exportTagged", Node.fromStrings("export"))) + .build(); + Model result = new RemoveUnusedShapes().transform(context); List ids = result.shapes() .filter(FunctionalUtils.not(Prelude::isPreludeShape)) .map(Shape::getId) @@ -58,9 +60,11 @@ public void shouldRetainUsedTraitsAndShapesUsedBySaidTraits() throws Exception { .addImport(Paths.get(getClass().getResource("tree-shaking-traits.json").toURI())) .assemble() .unwrap(); - Model result = new RemoveUnusedShapes() - .createTransformer(Collections.emptyList()) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("exportTagged", Node.arrayNode())) + .build(); + Model result = new RemoveUnusedShapes().transform(context); assertTrue(result.getTraitDefinition("ns.foo#bar").isPresent()); assertTrue(result.getShape(ShapeId.from("ns.foo#bar")).isPresent()); @@ -74,9 +78,11 @@ public void shouldPruneUnusedTraitsAndShapesUsedBySaidTraits() throws Exception .addImport(Paths.get(getClass().getResource("tree-shaking-traits.json").toURI())) .assemble() .unwrap(); - Model result = new RemoveUnusedShapes() - .createTransformer(Collections.emptyList()) - .apply(ModelTransformer.create(), model); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("exports", Node.arrayNode())) + .build(); + Model result = new RemoveUnusedShapes().transform(context); assertFalse(result.getTraitDefinition("ns.foo#quux").isPresent()); assertFalse(result.getShape(ShapeId.from("ns.foo#QuuxTraitShape")).isPresent()); diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/apply-cycle.json b/smithy-build/src/test/resources/software/amazon/smithy/build/apply-cycle.json index 19dba5f80af..498c25f3334 100644 --- a/smithy-build/src/test/resources/software/amazon/smithy/build/apply-cycle.json +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/apply-cycle.json @@ -3,12 +3,22 @@ "projections": { "foo": { "transforms": [ - {"name": "apply", "args": ["bar"]} + { + "name": "apply", + "args": { + "projections": ["bar"] + } + } ] }, "bar": { "transforms": [ - {"name": "apply", "args": ["foo"]} + { + "name": "apply", + "args": { + "projections": ["foo"] + } + } ] } } diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/apply-direct-recursion.json b/smithy-build/src/test/resources/software/amazon/smithy/build/apply-direct-recursion.json new file mode 100644 index 00000000000..71a26b074ee --- /dev/null +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/apply-direct-recursion.json @@ -0,0 +1,17 @@ +{ + "version": "1.0", + "projections": { + "foo": { + "transforms": [ + { + "name": "apply", + "args": { + "projections": [ + "foo" + ] + } + } + ] + } + } +} diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/apply-invalid-projection.json b/smithy-build/src/test/resources/software/amazon/smithy/build/apply-invalid-projection.json index a5ad6a174c4..9a68ef76d02 100644 --- a/smithy-build/src/test/resources/software/amazon/smithy/build/apply-invalid-projection.json +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/apply-invalid-projection.json @@ -4,7 +4,14 @@ "foo": { "transforms": [ // Does not exist. - {"name": "apply", "args": ["bar"]} + { + "name": "apply", + "args": { + "projections": [ + "bar" + ] + } + } ] } } diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/apply-multiple-projections.json b/smithy-build/src/test/resources/software/amazon/smithy/build/apply-multiple-projections.json index 5735d2669ec..1d36a4dc194 100644 --- a/smithy-build/src/test/resources/software/amazon/smithy/build/apply-multiple-projections.json +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/apply-multiple-projections.json @@ -3,24 +3,74 @@ "projections": { "a": { "transforms": [ - {"name": "includeByTag", "args": ["foo", "baz"]}, - {"name": "apply", "args": ["excludeLength"]}, - {"name": "excludeTraits", "args": ["documentation"]}, + { + "name": "includeByTag", + "args": { + "projections": [ + "foo", + "baz" + ] + } + }, + { + "name": "apply", + "args": { + "projections": [ + "excludeLength" + ] + } + }, + { + "name": "excludeTraits", + "args": { + "projections": [ + "documentation" + ] + } + }, // No issue with applying it multiple times. - {"name": "apply", "args": ["excludeLength"]} + { + "name": "apply", + "args": { + "projections": [ + "excludeLength" + ] + } + } ] }, "excludeLength": { "abstract": true, "transforms": [ - {"name": "excludeTraits", "args": ["length"]}, - {"name": "apply", "args": ["excludeTags"]} + { + "name": "excludeTraits", + "args": { + "projections": [ + "length" + ] + } + }, + { + "name": "apply", + "args": { + "projections": [ + "excludeTags" + ] + } + } ] }, "excludeTags": { "abstract": true, "transforms": [ - {"name": "excludeTraits", "args": ["tags"]} + { + "name": "excludeTraits", + "args": { + "traits": [ + "tags" + ] + } + } ] } } diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/config-with-env.json b/smithy-build/src/test/resources/software/amazon/smithy/build/config-with-env.json index 937795e74bd..e571d2666c1 100644 --- a/smithy-build/src/test/resources/software/amazon/smithy/build/config-with-env.json +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/config-with-env.json @@ -3,7 +3,15 @@ "projections": { "a": { "transforms": [ - {"${NAME_KEY}": "includeByTag", "args": ["${FOO}", "\\${BAZ}"]} + { + "${NAME_KEY}": "includeByTag", + "args": { + "tags": [ + "${FOO}", + "\\${BAZ}" + ] + } + } ] } } diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/resource-model-config.json b/smithy-build/src/test/resources/software/amazon/smithy/build/resource-model-config.json index 6d814c26582..4e16dbac8d7 100644 --- a/smithy-build/src/test/resources/software/amazon/smithy/build/resource-model-config.json +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/resource-model-config.json @@ -3,12 +3,28 @@ "projections": { "valid": { "transforms": [ - {"name": "includeTraits", "args": ["sensitive", "required", "readonly"]} + { + "name": "includeTraits", + "args": { + "traits": [ + "sensitive", + "required", + "readonly" + ] + } + } ] }, "invalid": { "transforms": [ - {"name": "excludeTraits", "args": ["required"]} + { + "name": "excludeTraits", + "args": { + "traits": [ + "required" + ] + } + } ] } } diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/rewrites-args-array.json b/smithy-build/src/test/resources/software/amazon/smithy/build/rewrites-args-array.json new file mode 100644 index 00000000000..b5bfcf9824b --- /dev/null +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/rewrites-args-array.json @@ -0,0 +1,13 @@ +{ + "version": "1.0", + "projections": { + "rewrite": { + "transforms": [ + { + "name": "includeTraits", + "args": ["sensitive"] + } + ] + } + } +} diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/simple-config.json b/smithy-build/src/test/resources/software/amazon/smithy/build/simple-config.json index b98429dc22a..b36baac562a 100644 --- a/smithy-build/src/test/resources/software/amazon/smithy/build/simple-config.json +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/simple-config.json @@ -3,13 +3,37 @@ "projections": { "a": { "transforms": [ - {"name": "includeByTag", "args": ["foo", "baz"]}, - {"name": "includeTraits", "args": ["documentation", "format"]} + { + "name": "includeByTag", + "args": { + "tags": [ + "foo", + "baz" + ] + } + }, + { + "name": "includeTraits", + "args": { + "traits": [ + "documentation", + "format" + ] + } + } ] }, "b": { "transforms": [ - {"name": "includeTraits", "args": ["length", "range"]} + { + "name": "includeTraits", + "args": { + "traits": [ + "length", + "range" + ] + } + } ] } } diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/special-syntax.json b/smithy-build/src/test/resources/software/amazon/smithy/build/special-syntax.json index a9c89d18d36..5160dd954c9 100644 --- a/smithy-build/src/test/resources/software/amazon/smithy/build/special-syntax.json +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/special-syntax.json @@ -3,13 +3,23 @@ "projections": { "valid": { "transforms": [ - {"name": "includeTraits", "args": ["sensitive", "required"]} + { + "name": "includeTraits", + "args": { + "traits": ["sensitive", "required"] + } + } ] }, "invalid": { "transforms": [ // This is a comment. - {"name": "excludeTraits", "args": ["required"]} + { + "name": "excludeTraits", + "args": { + "traits": ["required"] + } + } ] } } diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/unknown-transform.json b/smithy-build/src/test/resources/software/amazon/smithy/build/unknown-transform.json index e0c82eb8fbf..695595a8d65 100644 --- a/smithy-build/src/test/resources/software/amazon/smithy/build/unknown-transform.json +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/unknown-transform.json @@ -3,7 +3,7 @@ "projections": { "foo": { "transforms": [ - {"name": "foo", "args": []} + {"name": "foo", "args": {}} ] } } diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java index 7e41e223d9d..bae1583c352 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/commands/BuildCommand.java @@ -88,6 +88,8 @@ public void execute(Arguments arguments, ClassLoader classLoader) { if (config != null) { Cli.stdout(String.format("Loading Smithy configs: [%s]", String.join(" ", config))); config.forEach(file -> configBuilder.load(Paths.get(file))); + } else { + configBuilder.version(SmithyBuild.VERSION); } if (output != null) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java b/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java index b9e940c4120..9e4bc59a7a0 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java @@ -60,7 +60,7 @@ final class DefaultNodeDeserializers { // Deserialize an exact type if it matches (i.e., the setter expects a Node value). private static final ObjectCreatorFactory EXACT_CREATOR_FACTORY = (nodeType, targetType) -> { - return targetType == nodeType.getNodeClass() + return Node.class.isAssignableFrom(targetType) && targetType.isAssignableFrom(nodeType.getNodeClass()) ? (node, target, param, pointer, mapper) -> node : null; };