From 8d32b185b6eaf12c6457528f980397cbff15f4cd Mon Sep 17 00:00:00 2001 From: Chase Coalwell Date: Thu, 17 Sep 2020 10:37:17 -0700 Subject: [PATCH 1/3] Add flattenNamespaces transformer Add license header --- .../guides/building-models/build-config.rst | 56 ++++++ .../build/transforms/FlattenNamespaces.java | 178 ++++++++++++++++++ .../transforms/FlattenNamespacesTest.java | 134 +++++++++++++ .../build/transforms/flatten-namespaces.json | 40 ++++ .../smithy/build/transforms/no-service.json | 8 + 5 files changed, 416 insertions(+) create mode 100644 smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java create mode 100644 smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java create mode 100644 smithy-build/src/test/resources/software/amazon/smithy/build/transforms/flatten-namespaces.json create mode 100644 smithy-build/src/test/resources/software/amazon/smithy/build/transforms/no-service.json diff --git a/docs/source/1.0/guides/building-models/build-config.rst b/docs/source/1.0/guides/building-models/build-config.rst index 38d973a2757..58bf181f2a3 100644 --- a/docs/source/1.0/guides/building-models/build-config.rst +++ b/docs/source/1.0/guides/building-models/build-config.rst @@ -862,6 +862,62 @@ key is not in the provided ``keys`` list. } } +.. _flattenNamespaces: + +flattenNamespaces +----------------- + +Flattens namespaces of any shapes connected to a service into a target +namespace. Shapes not connected to a service will not be flattened. + +.. list-table:: + :header-rows: 1 + :widths: 10 20 70 + + * - Property + - Type + - Description + * - namespace + - ``string`` + - The target namespace. + * - service + - ``string`` + - The service to be flattened. + * - includeTagged + - ``[string]`` + - The set of tags that, if found on a shape not connected to the service, + forces the shape to have its namespace flattened into the target + namespace. + + +The following example will flatten the namespaces of the shapes connected to +the ``ns.bar#MyService`` service into the target namespace, ``ns.foo``. Shapes +tagged with ``baz`` or ``qux`` will also be flattened into the ``ns.foo`` +namespace, so long as they don't conflict with a shape within the service closure. + +.. tabs:: + + .. code-tab:: json + + { + "version": "1.0", + "projections": { + "exampleProjection": { + "transforms": [ + { + "name": "flattenNamespaces", + "args": { + "namespace": "ns.foo", + "service": "ns.bar#MyService", + "includeTagged": ["baz", "qux"] + } + } + ] + } + } + } + + .. _removeTraitDefinitions-transform: removeTraitDefinitions diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java new file mode 100644 index 00000000000..8db59c15633 --- /dev/null +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java @@ -0,0 +1,178 @@ +/* + * 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.Collections; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.NeighborProviderIndex; +import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.neighbor.Walker; +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.transform.ModelTransformer; +import software.amazon.smithy.utils.FunctionalUtils; +import software.amazon.smithy.utils.Pair; + +/** + * {@code flattenNamespaces} updates a model by flattening the namespaces of + * shapes connected to a service into a single, target namespace. When + * configuring the transformer, a service and target namespace must be + * specified. Optionally, tags can be specified for including any additional + * shapes that should be flattened into the the target namespace. Any shape + * from outside the service closure that is included via the application of a + * tag will not be included if it conflicts with a shape in the service closure. + */ +public final class FlattenNamespaces extends ConfigurableProjectionTransformer { + + /** + * {@code removeTraitShapes} configuration settings. + */ + public static final class Config { + + private String namespace; + private ShapeId service; + private Set tags = Collections.emptySet(); + + /** + * Sets the target namespace that existing namespaces will be flattened + * into. + * + * @param namespace The target namespace to use in the model. + */ + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + /** + * Gets the target namespace that existing namespaces will be flattened + * into. + * + * @return the target namespace to be used in the model. + */ + public String getNamespace() { + return namespace; + } + + /** + * Sets the service ShapeId that will be flattened into the target + * namespace. + * + * @param service The ID of the service. + */ + public void setService(ShapeId service) { + this.service = service; + } + + /** + * @return Gets the service shape ID of the service that will have + * its shape namespaces updated. + */ + public ShapeId getService() { + return service; + } + + /** + * Sets the set of tags that are retained in the model. + * + * @param tags The tags to retain in the model. + */ + public void setIncludeTagged(Set tags) { + this.tags = tags; + } + + /** + * Gets the set of tags that are retained in the model. + * + * @return Returns the tags to retain. + */ + public Set getIncludeTagged() { + return tags; + } + } + + @Override + public Class getConfigType() { + return Config.class; + } + + @Override + protected Model transformWithConfig(TransformContext context, Config config) { + Model model = context.getModel(); + Map shapesToRename = getRenamedShapes(config, model); + return ModelTransformer.create().renameShapes(model, shapesToRename); + } + + @Override + public String getName() { + return "flattenNamespaces"; + } + + private Map getRenamedShapes(Config config, Model model) { + // If no service has been specified, or the service is not present in + // the model, return an empty map. + if (config.service == null || !model.getShape(config.getService()).isPresent()) { + return Collections.emptyMap(); + } + + Map shapesToRename = getRenamedShapesConnectedToService(config, model); + Set taggedShapesToInclude = getTaggedShapesToInclude(config.getIncludeTagged(), model); + + for (ShapeId id : taggedShapesToInclude) { + ShapeId updatedShapeId = updateNamespace(id, config.getNamespace()); + // If new shape ID already exists in map of shapes to rename, skip + // including the additional shape to avoid a conflict. + if (!shapesToRename.containsValue(updatedShapeId)) { + shapesToRename.put(id, updatedShapeId); + } + } + + return shapesToRename; + } + + private ShapeId updateNamespace(ShapeId shapeId, String namespace) { + if (shapeId.getMember().isPresent()) { + return ShapeId.fromParts(namespace, shapeId.getName(), shapeId.getMember().get()); + } + return ShapeId.fromParts(namespace, shapeId.getName()); + } + + private Map getRenamedShapesConnectedToService(Config config, Model model) { + Walker shapeWalker = new Walker(model.getKnowledge(NeighborProviderIndex.class, NeighborProviderIndex::new) + .getProvider()); + ServiceShape service = model.expectShape(config.getService(), ServiceShape.class); + return shapeWalker.walkShapes(service).stream() + .filter(FunctionalUtils.not(Prelude::isPreludeShape)) + .map(shape -> Pair.of(shape.getId(), updateNamespace(shape.getId(), config.getNamespace()))) + .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); + } + + private Set getTaggedShapesToInclude(Set tags, Model model) { + return model.shapes() + .filter(FunctionalUtils.not(Prelude::isPreludeShape)) + .filter(shape -> isTagged(tags, shape)) + .map(Shape::getId) + .collect(Collectors.toSet()); + } + + private boolean isTagged(Set tags, Shape shape) { + return shape.getTags().stream().anyMatch(tags::contains); + } +} diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java new file mode 100644 index 00000000000..e9f4d0857ca --- /dev/null +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java @@ -0,0 +1,134 @@ +/* + * 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 static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Paths; +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.node.ObjectNode; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.utils.FunctionalUtils; + +public class FlattenNamespacesTest { + + @Test + public void flattenNamespacesOfShapesConnectedToSpecifiedService() throws Exception { + Model model = Model.assembler() + .addImport(Paths.get(getClass().getResource("flatten-namespaces.json").toURI())) + .assemble() + .unwrap(); + ObjectNode config = Node.objectNode() + .withMember("namespace", Node.from("ns.qux")) + .withMember("service", Node.from("ns.foo#MyService")); + TransformContext context = TransformContext.builder() + .model(model) + .settings(config) + .build(); + Model result = new FlattenNamespaces().transform(context); + List ids = result.shapes() + .filter(FunctionalUtils.not(Prelude::isPreludeShape)) + .map(Shape::getId) + .map(Object::toString) + .collect(Collectors.toList()); + + assertThat(ids, containsInAnyOrder("ns.qux#MyService", "ns.qux#MyOperation", "ns.qux#MyOperationOutput", + "ns.qux#MyOperationOutput$foo", "ns.corge#UnconnectedFromService", "ns.grault#MyOperationOutput")); + assertThat(ids, not(containsInAnyOrder("ns.foo#MyService", "ns.bar#MyOperation", "ns.baz#MyOperationOutput", + "ns.baz#MyOperationOutput$foo", "ns.qux#UnconnectedFromService"))); + } + + @Test + public void includesAdditionalTaggedShapes() throws Exception { + Model model = Model.assembler() + .addImport(Paths.get(getClass().getResource("flatten-namespaces.json").toURI())) + .assemble() + .unwrap(); + ObjectNode config = Node.objectNode() + .withMember("namespace", Node.from("ns.qux")) + .withMember("service", Node.from("ns.foo#MyService")) + .withMember("includeTagged", Node.arrayNode().withValue(Node.from("included"))); + TransformContext context = TransformContext.builder() + .model(model) + .settings(config) + .build(); + Model result = new FlattenNamespaces().transform(context); + List ids = result.shapes() + .filter(FunctionalUtils.not(Prelude::isPreludeShape)) + .map(Shape::getId) + .map(Object::toString) + .collect(Collectors.toList()); + + assertThat(ids, containsInAnyOrder("ns.qux#MyService", "ns.qux#MyOperation", "ns.qux#MyOperationOutput", + "ns.qux#MyOperationOutput$foo", "ns.qux#UnconnectedFromService", "ns.grault#MyOperationOutput")); + assertThat(ids, not(containsInAnyOrder("ns.foo#MyService", "ns.bar#MyOperation", "ns.baz#MyOperationOutput", + "ns.baz#MyOperationOutput$foo", "ns.corge#UnconnectedFromService"))); + } + + @Test + public void doesNotIncludeAdditionalTaggedShapesWhenTheyConflict() throws Exception { + Model model = Model.assembler() + .addImport(Paths.get(getClass().getResource("flatten-namespaces.json").toURI())) + .assemble() + .unwrap(); + ObjectNode config = Node.objectNode() + .withMember("namespace", Node.from("ns.qux")) + .withMember("service", Node.from("ns.foo#MyService")) + .withMember("includeTagged", Node.arrayNode().withValue(Node.from("conflicting"))); + TransformContext context = TransformContext.builder() + .model(model) + .settings(config) + .build(); + Model result = new FlattenNamespaces().transform(context); + List ids = result.shapes() + .filter(FunctionalUtils.not(Prelude::isPreludeShape)) + .map(Shape::getId) + .map(Object::toString) + .collect(Collectors.toList()); + + assertThat(ids, containsInAnyOrder("ns.qux#MyService", "ns.qux#MyOperation", "ns.qux#MyOperationOutput", + "ns.qux#MyOperationOutput$foo", "ns.corge#UnconnectedFromService", "ns.grault#MyOperationOutput")); + assertThat(ids, not(containsInAnyOrder("ns.foo#MyService", "ns.bar#MyOperation", "ns.baz#MyOperationOutput", + "ns.baz#MyOperationOutput$foo", "ns.qux#UnconnectedFromService"))); + } + + @Test + public void doesNotModifyModelsWithoutServices() throws Exception { + Model model = Model.assembler() + .addImport(Paths.get(getClass().getResource("no-service.json").toURI())) + .assemble() + .unwrap(); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("namespace", Node.from("ns.bar"))) + .build(); + Model result = new FlattenNamespaces().transform(context); + + assertTrue(result.getShape(ShapeId.from("ns.foo#MyString")).isPresent()); + assertFalse(result.getShape(ShapeId.from("ns.bar#MyString")).isPresent()); + } +} diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/flatten-namespaces.json b/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/flatten-namespaces.json new file mode 100644 index 00000000000..7b2d1f1a2d1 --- /dev/null +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/flatten-namespaces.json @@ -0,0 +1,40 @@ +{ + "smithy": "1.0", + "shapes": { + "ns.foo#MyService": { + "type": "service", + "version": "2017-01-19", + "operations": [ + { + "target": "ns.bar#MyOperation" + } + ] + }, + "ns.bar#MyOperation": { + "type": "operation", + "output": { + "target": "ns.baz#MyOperationOutput" + } + }, + "ns.baz#MyOperationOutput": { + "type": "structure", + "members": { + "foo": { + "target": "smithy.api#String" + } + } + }, + "ns.corge#UnconnectedFromService": { + "type": "string", + "traits": { + "smithy.api#tags": ["included"] + } + }, + "ns.grault#MyOperationOutput": { + "type": "string", + "traits": { + "smithy.api#tags": ["conflicting"] + } + } + } +} diff --git a/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/no-service.json b/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/no-service.json new file mode 100644 index 00000000000..55d21962f81 --- /dev/null +++ b/smithy-build/src/test/resources/software/amazon/smithy/build/transforms/no-service.json @@ -0,0 +1,8 @@ +{ + "smithy": "1.0", + "shapes": { + "ns.foo#MyString": { + "type": "string" + } + } +} From 33259f847331b0527042eecc6324fb8264657e4c Mon Sep 17 00:00:00 2001 From: Chase Coalwell Date: Tue, 22 Sep 2020 15:25:10 -0700 Subject: [PATCH 2/3] Throw on invalid config, update docs --- .../guides/building-models/build-config.rst | 12 +++--- .../build/transforms/FlattenNamespaces.java | 11 +++-- .../transforms/FlattenNamespacesTest.java | 40 ++++++++++++++++--- 3 files changed, 49 insertions(+), 14 deletions(-) diff --git a/docs/source/1.0/guides/building-models/build-config.rst b/docs/source/1.0/guides/building-models/build-config.rst index 58bf181f2a3..eb620a8a343 100644 --- a/docs/source/1.0/guides/building-models/build-config.rst +++ b/docs/source/1.0/guides/building-models/build-config.rst @@ -879,21 +879,23 @@ namespace. Shapes not connected to a service will not be flattened. - Description * - namespace - ``string`` - - The target namespace. + - **REQUIRED** The target namespace. * - service - - ``string`` - - The service to be flattened. + - ``shapeId`` + - **REQUIRED** The service to be flattened. * - includeTagged - ``[string]`` - The set of tags that, if found on a shape not connected to the service, forces the shape to have its namespace flattened into the target - namespace. + namespace. When additional shapes are included, the shapes are replaced + entirely, along with any references to the shapes which may exist within + separate :ref:`service closure `. The following example will flatten the namespaces of the shapes connected to the ``ns.bar#MyService`` service into the target namespace, ``ns.foo``. Shapes tagged with ``baz`` or ``qux`` will also be flattened into the ``ns.foo`` -namespace, so long as they don't conflict with a shape within the service closure. +namespace, so long as they don't conflict with a shape within the :ref:`service closure `. .. tabs:: diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java index 8db59c15633..1eeb09dbbfd 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import software.amazon.smithy.build.SmithyBuildException; import software.amazon.smithy.build.TransformContext; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.knowledge.NeighborProviderIndex; @@ -115,6 +116,10 @@ public Class getConfigType() { @Override protected Model transformWithConfig(TransformContext context, Config config) { + if (config.getService() == null || config.getNamespace() == null) { + throw new SmithyBuildException( + "'namespace' and 'service'properties must be set on flattenNamespace transformer."); + } Model model = context.getModel(); Map shapesToRename = getRenamedShapes(config, model); return ModelTransformer.create().renameShapes(model, shapesToRename); @@ -126,10 +131,8 @@ public String getName() { } private Map getRenamedShapes(Config config, Model model) { - // If no service has been specified, or the service is not present in - // the model, return an empty map. - if (config.service == null || !model.getShape(config.getService()).isPresent()) { - return Collections.emptyMap(); + if (!model.getShape(config.getService()).isPresent()) { + throw new SmithyBuildException("Service not found in model when performing flattenNamespaces transform."); } Map shapesToRename = getRenamedShapesConnectedToService(config, model); diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java index e9f4d0857ca..5786fa5f515 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java @@ -24,8 +24,12 @@ import java.nio.file.Paths; import java.util.List; import java.util.stream.Collectors; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; +import software.amazon.smithy.build.SmithyBuildException; +import software.amazon.smithy.build.SourcesConflictException; import software.amazon.smithy.build.TransformContext; +import software.amazon.smithy.build.plugins.SourcesPlugin; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.node.Node; @@ -117,18 +121,44 @@ public void doesNotIncludeAdditionalTaggedShapesWhenTheyConflict() throws Except } @Test - public void doesNotModifyModelsWithoutServices() throws Exception { + public void throwsWhenServiceIsNotConfigured() { Model model = Model.assembler() - .addImport(Paths.get(getClass().getResource("no-service.json").toURI())) + .addUnparsedModel("N/A", "{ \"smithy\": \"1.0\" }") .assemble() .unwrap(); TransformContext context = TransformContext.builder() .model(model) .settings(Node.objectNode().withMember("namespace", Node.from("ns.bar"))) .build(); - Model result = new FlattenNamespaces().transform(context); + Assertions.assertThrows(SmithyBuildException.class, () -> new FlattenNamespaces().transform(context)); + } + + @Test + public void throwsWhenNamespaceIsNotConfigured() { + Model model = Model.assembler() + .addUnparsedModel("N/A", "{ \"smithy\": \"1.0\" }") + .assemble() + .unwrap(); + TransformContext context = TransformContext.builder() + .model(model) + .settings(Node.objectNode().withMember("service", Node.from("ns.foo#MyService"))) + .build(); + Assertions.assertThrows(SmithyBuildException.class, () -> new FlattenNamespaces().transform(context)); + } - assertTrue(result.getShape(ShapeId.from("ns.foo#MyString")).isPresent()); - assertFalse(result.getShape(ShapeId.from("ns.bar#MyString")).isPresent()); + @Test + public void throwsWhenServiceCannotBeFoundInModel() { + Model model = Model.assembler() + .addUnparsedModel("N/A", "{ \"smithy\": \"1.0\" }") + .assemble() + .unwrap(); + ObjectNode config = Node.objectNode() + .withMember("namespace", Node.from("ns.qux")) + .withMember("service", Node.from("ns.foo#MyService")); + TransformContext context = TransformContext.builder() + .model(model) + .settings(config) + .build(); + Assertions.assertThrows(SmithyBuildException.class, () -> new FlattenNamespaces().transform(context)); } } From fe2c03da6f8aede0627f0d97ee2773495935596f Mon Sep 17 00:00:00 2001 From: Chase Coalwell Date: Thu, 24 Sep 2020 09:28:22 -0700 Subject: [PATCH 3/3] Throw when service is invalid --- .../1.0/guides/building-models/build-config.rst | 7 ++++--- .../build/transforms/FlattenNamespaces.java | 8 ++++---- .../build/transforms/FlattenNamespacesTest.java | 17 +++++++++++++++++ 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/source/1.0/guides/building-models/build-config.rst b/docs/source/1.0/guides/building-models/build-config.rst index eb620a8a343..5ca324793ca 100644 --- a/docs/source/1.0/guides/building-models/build-config.rst +++ b/docs/source/1.0/guides/building-models/build-config.rst @@ -882,15 +882,16 @@ namespace. Shapes not connected to a service will not be flattened. - **REQUIRED** The target namespace. * - service - ``shapeId`` - - **REQUIRED** The service to be flattened. + - **REQUIRED** The service to be flattened. All shapes within this + :ref:`service closure ` will be replaced with equivalent + shapes in the target namespace. * - includeTagged - ``[string]`` - The set of tags that, if found on a shape not connected to the service, forces the shape to have its namespace flattened into the target namespace. When additional shapes are included, the shapes are replaced entirely, along with any references to the shapes which may exist within - separate :ref:`service closure `. - + separate :ref:`service closures `. The following example will flatten the namespaces of the shapes connected to the ``ns.bar#MyService`` service into the target namespace, ``ns.foo``. Shapes diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java index 1eeb09dbbfd..98f29c36849 100644 --- a/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java +++ b/smithy-build/src/main/java/software/amazon/smithy/build/transforms/FlattenNamespaces.java @@ -118,7 +118,7 @@ public Class getConfigType() { protected Model transformWithConfig(TransformContext context, Config config) { if (config.getService() == null || config.getNamespace() == null) { throw new SmithyBuildException( - "'namespace' and 'service'properties must be set on flattenNamespace transformer."); + "'namespace' and 'service' properties must be set on flattenNamespace transformer."); } Model model = context.getModel(); Map shapesToRename = getRenamedShapes(config, model); @@ -132,7 +132,8 @@ public String getName() { private Map getRenamedShapes(Config config, Model model) { if (!model.getShape(config.getService()).isPresent()) { - throw new SmithyBuildException("Service not found in model when performing flattenNamespaces transform."); + throw new SmithyBuildException("Configured service, " + config.getService() + + ", not found in model when performing flattenNamespaces transform."); } Map shapesToRename = getRenamedShapesConnectedToService(config, model); @@ -158,8 +159,7 @@ private ShapeId updateNamespace(ShapeId shapeId, String namespace) { } private Map getRenamedShapesConnectedToService(Config config, Model model) { - Walker shapeWalker = new Walker(model.getKnowledge(NeighborProviderIndex.class, NeighborProviderIndex::new) - .getProvider()); + Walker shapeWalker = new Walker(NeighborProviderIndex.of(model).getProvider()); ServiceShape service = model.expectShape(config.getService(), ServiceShape.class); return shapeWalker.walkShapes(service).stream() .filter(FunctionalUtils.not(Prelude::isPreludeShape)) diff --git a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java index 5786fa5f515..0f8b391626c 100644 --- a/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java +++ b/smithy-build/src/test/java/software/amazon/smithy/build/transforms/FlattenNamespacesTest.java @@ -32,6 +32,7 @@ import software.amazon.smithy.build.plugins.SourcesPlugin; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.loader.Prelude; +import software.amazon.smithy.model.node.ExpectationNotMetException; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.shapes.Shape; @@ -161,4 +162,20 @@ public void throwsWhenServiceCannotBeFoundInModel() { .build(); Assertions.assertThrows(SmithyBuildException.class, () -> new FlattenNamespaces().transform(context)); } + + @Test + public void throwsWhenServiceIsInvalidInModel() { + Model model = Model.assembler() + .addUnparsedModel("N/A", "{ \"smithy\": \"1.0\", \"shapes\": { \"ns.foo#InvalidService\": { \"type\": \"string\" } } }") + .assemble() + .unwrap(); + ObjectNode config = Node.objectNode() + .withMember("namespace", Node.from("ns.qux")) + .withMember("service", Node.from("ns.foo#InvalidService")); + TransformContext context = TransformContext.builder() + .model(model) + .settings(config) + .build(); + Assertions.assertThrows(ExpectationNotMetException.class, () -> new FlattenNamespaces().transform(context)); + } }