diff --git a/docs/source/spec/core/selectors.rst b/docs/source/spec/core/selectors.rst index 4fb32d85511..da7cb4cd9a7 100644 --- a/docs/source/spec/core/selectors.rst +++ b/docs/source/spec/core/selectors.rst @@ -258,6 +258,17 @@ an identifier: resource:test(-[identifier]->) +Relationships from a shape to the traits applied to the shape can be traversed +using a directed relationship named ``trait``. It is atypical to traverse +``trait`` relationships, therefore they are only yielded by selectors when +explicitly requested using a ``trait`` directed relationship. The following +selector finds all service shapes that have a protocol trait applied to it +(that is, a trait that is marked with the :ref:`protocolDefinition-trait`): + +:: + + service:test(-[trait]-> [trait|protocolDefinition]) + .. _selector-relationships: @@ -353,6 +364,12 @@ The table below lists the labeled directed relationships from each shape. - - The shape targeted by the member. Note that member targets have no relationship name. + * - ``*`` + - trait + - Each trait applied to a shape. The neighbor shape is the shape that + defines the trait. This kind of relationship is only traversed if the + ``trait`` relationship is explicitly stated as a desired directed + neighbor relationship type. .. important:: @@ -496,6 +513,20 @@ the member does not have the ``length`` trait: :test(> string:not([trait|length])) :test(:not([trait|length])) +The following selector finds all service shapes that do not have a +protocol trait applied to it: + +:: + + service:not(:test(-[trait]-> [trait|protocolDefinition])) + +The following selector finds all traits that are not attached to any shape +in the model: + +:: + + :not(* -[trait]-> *)[trait|trait] + :of ~~~ @@ -576,6 +607,7 @@ Selectors are defined by the following ABNF_ grammar. :/ "instanceOperation" :/ "resource" :/ "bound" + :/ "trait" attr :"[" `attr_key` *(`comparator` `attr_value` ["i"]) "]" attr_key :`id_attribute` / `trait_attribute` / `service_attribute` id_attribute :"id" ["|" ("namespace" / "name" / "member")] diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ValidatorDefinition.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ValidatorDefinition.java index afa9f446b47..69e2ea6c81c 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/ValidatorDefinition.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/ValidatorDefinition.java @@ -57,7 +57,7 @@ List map(Model model, List events) { // If there's a selector, create a list of candidate shape IDs that can be emitted. if (selector != null) { NeighborProvider provider = model.getKnowledge(NeighborProviderIndex.class).getProvider(); - candidates = selector.select(provider, model).stream() + candidates = selector.select(model, provider).stream() .map(Shape::getId) .collect(Collectors.toSet()); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborProvider.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborProvider.java index e500bf0da1d..1a1c323b648 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborProvider.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/NeighborProvider.java @@ -15,6 +15,7 @@ package software.amazon.smithy.model.neighbor; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -57,6 +58,36 @@ static NeighborProvider precomputed(Model model) { return precomputed(model, of(model)); } + /** + * Creates a NeighborProvider that includes {@link RelationshipType#TRAIT} + * relationships. + * + * @param model Model to use to look up trait shapes. + * @param neighborProvider Provider to wrap. + * @return Returns the created neighbor provider. + */ + static NeighborProvider withTraitRelationships(Model model, NeighborProvider neighborProvider) { + return shape -> { + List relationships = neighborProvider.getNeighbors(shape); + + // Don't copy the array unless the shape has traits. + if (shape.getAllTraits().isEmpty()) { + return relationships; + } + + // The delegate might have returned an immutable list, so copy first. + relationships = new ArrayList<>(relationships); + for (ShapeId trait : shape.getAllTraits().keySet()) { + Relationship traitRel = model.getShape(trait) + .map(target -> Relationship.create(shape, RelationshipType.TRAIT, target)) + .orElseGet(() -> Relationship.createInvalid(shape, RelationshipType.TRAIT, trait)); + relationships.add(traitRel); + } + + return relationships; + }; + } + /** * Creates a NeighborProvider that precomputes the neighbors of a model. * diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/RelationshipType.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/RelationshipType.java index 1a9e19e693f..ed1087214bf 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/RelationshipType.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/RelationshipType.java @@ -16,6 +16,7 @@ package software.amazon.smithy.model.neighbor; import java.util.Optional; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.selector.Selector; import software.amazon.smithy.model.shapes.ListShape; import software.amazon.smithy.model.shapes.MapShape; @@ -25,6 +26,7 @@ import software.amazon.smithy.model.shapes.SetShape; import software.amazon.smithy.model.shapes.StructureShape; import software.amazon.smithy.model.shapes.UnionShape; +import software.amazon.smithy.model.traits.TraitDefinition; /** * Defines the relationship types between neighboring shapes. @@ -187,7 +189,19 @@ public enum RelationshipType { * shapes. They reference the {@link MemberShape member} shapes that define * the members of the union. */ - UNION_MEMBER("member", RelationshipDirection.DIRECTED); + UNION_MEMBER("member", RelationshipDirection.DIRECTED), + + /** + * Relationships that exist between a shape and traits bound to the + * shape. They reference shapes marked with the {@link TraitDefinition} + * trait. + * + *

This kind of relationship is not returned by default from a + * {@link NeighborProvider}. You must explicitly wrap a {@link NeighborProvider} + * with {@link NeighborProvider#withTraitRelationships(Model, NeighborProvider)} + * in order to yield trait relationships. + */ + TRAIT("trait", RelationshipDirection.DIRECTED); private String selectorLabel; private RelationshipDirection direction; diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/AndSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/AndSelector.java index ffe0ddb8495..8542ad2d2f1 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/AndSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/AndSelector.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Set; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.utils.SetUtils; @@ -40,9 +41,9 @@ static Selector of(List predicates) { } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { for (Selector selector : selectors) { - shapes = selector.select(neighborProvider, shapes); + shapes = selector.select(model, neighborProvider, shapes); if (shapes.isEmpty()) { return SetUtils.of(); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/AttributeSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/AttributeSelector.java index 64ebaa161e4..8d53018ff07 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/AttributeSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/AttributeSelector.java @@ -22,6 +22,7 @@ import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.shapes.ServiceShape; import software.amazon.smithy.model.shapes.Shape; @@ -75,7 +76,7 @@ interface Comparator extends BiFunction {} } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { return shapes.stream() .filter(shape -> matchesAttribute(key.apply(shape))) .collect(Collectors.toSet()); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/EachSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/EachSelector.java index 1ec7d2706c7..2930885df5a 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/EachSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/EachSelector.java @@ -18,6 +18,7 @@ import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.shapes.Shape; @@ -36,9 +37,9 @@ static Selector of(List predicates) { } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { return selectors.stream() - .flatMap(selector -> selector.select(neighborProvider, shapes).stream()) + .flatMap(selector -> selector.select(model, neighborProvider, shapes).stream()) .collect(Collectors.toSet()); } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/NeighborSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/NeighborSelector.java index c8910f28e27..ec6e8ccd184 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/NeighborSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/NeighborSelector.java @@ -15,47 +15,62 @@ package software.amazon.smithy.model.selector; +import java.util.HashSet; import java.util.List; -import java.util.Optional; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.neighbor.Relationship; import software.amazon.smithy.model.neighbor.RelationshipType; import software.amazon.smithy.model.shapes.Shape; -import software.amazon.smithy.utils.OptionalUtils; /** * Traverses into the neighbors of shapes with an optional list of * neighbor rel filters. */ final class NeighborSelector implements Selector { + private final List relTypes; + private final boolean includeTraits; NeighborSelector(List relTypes) { this.relTypes = relTypes; + includeTraits = relTypes.contains("trait"); } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { - return shapes.stream() - .flatMap(shape -> neighborProvider.getNeighbors(shape).stream().flatMap(this::mapNeighbor)) - .collect(Collectors.toSet()); + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { + NeighborProvider resolvedProvider = createProvider(model, neighborProvider); + + Set result = new HashSet<>(); + for (Shape shape : shapes) { + for (Relationship rel : resolvedProvider.getNeighbors(shape)) { + if (rel.getNeighborShape().isPresent()) { + Shape target = createNeighbor(rel, rel.getNeighborShape().get()); + if (target != null) { + result.add(target); + } + } + } + } + + return result; } - private Stream mapNeighbor(Relationship rel) { - return OptionalUtils.stream(rel.getNeighborShape() - .flatMap(target -> createNeighbor(rel, target))); + // Enable trait relationships only if explicitly asked for in a selector. + private NeighborProvider createProvider(Model model, NeighborProvider neighborProvider) { + return includeTraits + ? NeighborProvider.withTraitRelationships(model, neighborProvider) + : neighborProvider; } - private Optional createNeighbor(Relationship rel, Shape target) { + private Shape createNeighbor(Relationship rel, Shape target) { if (rel.getRelationshipType() != RelationshipType.MEMBER_CONTAINER && (relTypes.isEmpty() || relTypes.contains(getRelType(rel)))) { - return Optional.of(target); + return target; } - return Optional.empty(); + return null; } private static String getRelType(Relationship rel) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/NotSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/NotSelector.java index bc949fcde53..a5c318de94a 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/NotSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/NotSelector.java @@ -18,6 +18,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.shapes.Shape; @@ -32,10 +33,10 @@ final class NotSelector implements Selector { } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { Set result = new HashSet<>(shapes); for (Selector predicate : selectors) { - result.removeAll(predicate.select(neighborProvider, shapes)); + result.removeAll(predicate.select(model, neighborProvider, shapes)); } return result; } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/OfSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/OfSelector.java index dd0ab315074..05a81f69cf9 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/OfSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/OfSelector.java @@ -19,6 +19,7 @@ import java.util.List; import java.util.Optional; import java.util.Set; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.neighbor.RelationshipType; import software.amazon.smithy.model.shapes.Shape; @@ -44,7 +45,7 @@ final class OfSelector implements Selector { } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { Set result = new HashSet<>(); // Filter out non-member shapes, and member shapes that cannot @@ -55,7 +56,7 @@ public Set select(NeighborProvider neighborProvider, Set shapes) { // If the parent provides a result for the predicate, then the // Shape is not filtered out. boolean anyMatch = selectors.stream() - .anyMatch(selector -> !selector.select(neighborProvider, parentSet).isEmpty()); + .anyMatch(selector -> !selector.select(model, neighborProvider, parentSet).isEmpty()); if (anyMatch) { result.add(shape); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/PathFinder.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/PathFinder.java index 28e9cac669a..1030f80b675 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/PathFinder.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/PathFinder.java @@ -110,7 +110,7 @@ public List search(ToShapeId startingShape, Selector targetSelector) { } // Find all shapes that match the selector then work backwards from there. - Set candidates = targetSelector.select(neighborProvider, model); + Set candidates = targetSelector.select(model, neighborProvider); if (candidates.isEmpty()) { LOGGER.info(() -> "No shapes matched the PathFinder selector of `" + targetSelector + "`"); return ListUtils.of(); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/Selector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/Selector.java index 85b66e14bd4..b9e808e204f 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/Selector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/Selector.java @@ -28,26 +28,27 @@ @FunctionalInterface public interface Selector { /** A selector that always returns all provided values. */ - Selector IDENTITY = (visitor, shapes) -> shapes; + Selector IDENTITY = (model, visitor, shapes) -> shapes; /** * Matches a selector to a set of shapes. * + * @param model Model used to resolve shapes with. * @param neighborProvider Provides neighbors for shapes. * @param shapes Matching context of shapes. * @return Returns the matching shapes. */ - Set select(NeighborProvider neighborProvider, Set shapes); + Set select(Model model, NeighborProvider neighborProvider, Set shapes); /** * Matches a selector against a model using a custom neighbor visitor. * - * @param neighborProvider Provides neighbors for shapes * @param model Model to query. + * @param neighborProvider Provides neighbors for shapes * @return Returns the matching shapes. */ - default Set select(NeighborProvider neighborProvider, Model model) { - return select(neighborProvider, model.toSet()); + default Set select(Model model, NeighborProvider neighborProvider) { + return select(model, neighborProvider, model.toSet()); } /** @@ -57,7 +58,7 @@ default Set select(NeighborProvider neighborProvider, Model model) { * @return Returns the matching shapes. */ default Set select(Model model) { - return select(NeighborProvider.of(model), model); + return select(model, NeighborProvider.of(model)); } /** diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/ShapeTypeCategorySelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/ShapeTypeCategorySelector.java index 7fff811a378..1ed7986f103 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/ShapeTypeCategorySelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/ShapeTypeCategorySelector.java @@ -17,6 +17,7 @@ import java.util.Set; import java.util.stream.Collectors; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.shapes.Shape; @@ -28,7 +29,7 @@ final class ShapeTypeCategorySelector implements Selector { } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { return shapes.stream().filter(shapeCategory::isInstance).collect(Collectors.toSet()); } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/ShapeTypeSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/ShapeTypeSelector.java index 423c0ff3e89..5149876ccba 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/ShapeTypeSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/ShapeTypeSelector.java @@ -17,6 +17,7 @@ import java.util.Set; import java.util.stream.Collectors; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.model.shapes.ShapeType; @@ -29,7 +30,7 @@ final class ShapeTypeSelector implements Selector { } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { return shapes.stream() .filter(shape -> shape.getType() == shapeType) .collect(Collectors.toSet()); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/TestSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/TestSelector.java index 68bd9550a47..8005e0009b8 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/TestSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/TestSelector.java @@ -18,6 +18,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.shapes.Shape; import software.amazon.smithy.utils.SetUtils; @@ -36,12 +37,12 @@ final class TestSelector implements Selector { } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { Set result = new HashSet<>(); for (Shape shape : shapes) { Set attempt = SetUtils.of(shape); for (Selector predicate : selectors) { - if (predicate.select(neighborProvider, attempt).size() > 0) { + if (predicate.select(model, neighborProvider, attempt).size() > 0) { result.add(shape); break; } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/selector/WrappedSelector.java b/smithy-model/src/main/java/software/amazon/smithy/model/selector/WrappedSelector.java index bf2965dfcc8..2e3a5d81476 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/selector/WrappedSelector.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/selector/WrappedSelector.java @@ -16,6 +16,7 @@ package software.amazon.smithy.model.selector; import java.util.Set; +import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; import software.amazon.smithy.model.shapes.Shape; @@ -35,8 +36,8 @@ final class WrappedSelector implements Selector { } @Override - public Set select(NeighborProvider neighborProvider, Set shapes) { - return delegate.select(neighborProvider, shapes); + public Set select(Model model, NeighborProvider neighborProvider, Set shapes) { + return delegate.select(model, neighborProvider, shapes); } @Override diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/TraitTargetValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/TraitTargetValidator.java index f15cd47907a..18aba000f7b 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/TraitTargetValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/TraitTargetValidator.java @@ -78,6 +78,6 @@ private boolean matchesSelector( Model model, NeighborProvider neighborProvider ) { - return check.selector.select(neighborProvider, model).contains(check.shape); + return check.selector.select(model, neighborProvider).contains(check.shape); } } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NeighborProviderTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NeighborProviderTest.java new file mode 100644 index 00000000000..eedc0d27da7 --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NeighborProviderTest.java @@ -0,0 +1,45 @@ +package software.amazon.smithy.model.neighbor; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasSize; + +import java.util.List; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.StringShape; +import software.amazon.smithy.model.traits.SensitiveTrait; + +public class NeighborProviderTest { + @Test + public void canGetTraitRelationshipsFromStrings() { + StringShape stringShape = StringShape.builder() + .id(ShapeId.from("smithy.example#Foo")) + .addTrait(new SensitiveTrait()) + .build(); + Model model = Model.assembler().addShape(stringShape).assemble().unwrap(); + + NeighborProvider provider = NeighborProvider.of(model); + provider = NeighborProvider.withTraitRelationships(model, provider); + List relationships = provider.getNeighbors(stringShape); + + assertThat(relationships, hasSize(1)); + assertThat(relationships.get(0).getNeighborShapeId(), equalTo(SensitiveTrait.ID)); + } + + @Test + public void canGetTraitRelationshipsFromShapeWithNoTraits() { + StringShape stringShape = StringShape.builder() + .id(ShapeId.from("smithy.example#Foo")) + .build(); + Model model = Model.assembler().addShape(stringShape).assemble().unwrap(); + + NeighborProvider provider = NeighborProvider.of(model); + provider = NeighborProvider.withTraitRelationships(model, provider); + List relationships = provider.getNeighbors(stringShape); + + assertThat(relationships, empty()); + } +} diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/selector/NeighborSelectorTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/selector/NeighborSelectorTest.java index 18ca28438b0..139dbf42677 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/selector/NeighborSelectorTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/selector/NeighborSelectorTest.java @@ -82,4 +82,24 @@ public void returnsEmptyForUnknownRelationships() { assertThat(result, empty()); } + + @Test + public void canQueryTraitRelationships() { + Set result1 = selectIds("string -[trait]-> [trait|deprecated]"); + + assertThat(result1, contains("smithy.example#myTrait")); + + Set result2 = selectIds(":test(string -[trait]-> [trait|deprecated])"); + + assertThat(result2, contains("smithy.example#MyString")); + } + + @Test + public void canQueryTraitRelationshipsForProtocolServices() { + Set result1 = selectIds("service:test(-[trait]-> [trait|protocolDefinition])"); + Set result2 = selectIds("service:not(:test(-[trait]-> [trait|protocolDefinition]))"); + + assertThat(result1, contains("smithy.example#MyService1")); + assertThat(result2, contains("smithy.example#MyService2")); + } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/selector/neighbor-test.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/selector/neighbor-test.smithy index 9cc844c603c..83665e3fefc 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/selector/neighbor-test.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/selector/neighbor-test.smithy @@ -18,3 +18,23 @@ structure Output { structure Error { foo: smithy.api#String, } + +@trait +@deprecated +structure myTrait {} + +@myTrait +string MyString + +@trait(selector: "service") +@protocolDefinition +structure myProtocol {} + +@myProtocol +service MyService1 { + version: "2020-01-01" +} + +service MyService2 { + version: "2020-01-01" +}