diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/NeighborProviderIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/NeighborProviderIndex.java
index 40901019732..acc7a58eaac 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/NeighborProviderIndex.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/NeighborProviderIndex.java
@@ -32,6 +32,7 @@ public final class NeighborProviderIndex implements KnowledgeIndex {
private volatile NeighborProvider reversed;
private volatile NeighborProvider providerWithTraits;
private volatile NeighborProvider reversedWithTraits;
+ private volatile NeighborProvider providerWithIdRefs;
public NeighborProviderIndex(Model model) {
provider = NeighborProvider.precomputed(model);
@@ -76,6 +77,28 @@ public NeighborProvider getProviderWithTraitRelationships() {
return result;
}
+ /**
+ * Gets the neighbor provider that includes idRef relationships.
+ *
+ * @return Returns the provider.
+ */
+ public NeighborProvider getProviderWithIdRefRelationships() {
+ NeighborProvider result = providerWithIdRefs;
+
+ if (result == null) {
+ Model model = getOrThrowModel();
+ synchronized (this) {
+ result = providerWithIdRefs;
+ if (result == null) {
+ providerWithIdRefs = result = NeighborProvider.cached(
+ NeighborProvider.withIdRefRelationships(model, provider));
+ }
+ }
+ }
+
+ return result;
+ }
+
/**
* Gets a reversed, bottom up neighbor provider.
*
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeReferences.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeRelationships.java
similarity index 71%
rename from smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeReferences.java
rename to smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeRelationships.java
index dea9ad707d0..b864cfdba38 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeReferences.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/IdRefShapeRelationships.java
@@ -5,8 +5,10 @@
package software.amazon.smithy.model.neighbor;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.node.Node;
@@ -21,8 +23,7 @@
import software.amazon.smithy.model.traits.TraitDefinition;
/**
- * Finds all shapes referenced by {@link IdRefTrait} from within trait
- * values.
+ * Finds all {@link RelationshipType#ID_REF} relationships in a model.
*
*
This works by finding all paths from {@link TraitDefinition} shapes
* to shapes with {@link IdRefTrait}, then using those paths to search
@@ -30,46 +31,47 @@
* value. Because we don't have a fixed set of traits known to potentially have
* idRef members, this has to be done dynamically.
*/
-final class IdRefShapeReferences {
+final class IdRefShapeRelationships {
private static final Selector WITH_ID_REF = Selector.parse("[trait|idRef]");
private final Model model;
- private final Set found = new HashSet<>();
+ private final Map> relationships = new HashMap<>();
- IdRefShapeReferences(Model model) {
+ IdRefShapeRelationships(Model model) {
this.model = model;
}
- Set compute(Set connected) {
+ Map> getRelationships() {
PathFinder finder = PathFinder.create(model);
for (Shape traitDef : model.getShapesWithTrait(TraitDefinition.class)) {
if (traitDef.hasTrait(IdRefTrait.class)) {
// PathFinder doesn't handle the case where the trait def has the idRef
NodeQuery query = new NodeQuery().self();
- addReferences(traitDef, query, connected);
+ addRelationships(traitDef, query);
continue;
}
List paths = finder.search(traitDef, WITH_ID_REF);
if (!paths.isEmpty()) {
for (PathFinder.Path path : paths) {
NodeQuery query = buildNodeQuery(path);
- addReferences(traitDef, query, connected);
+ addRelationships(traitDef, query);
}
}
}
- return found;
+ return relationships;
}
- private void addReferences(Shape traitDef, NodeQuery query, Set connected) {
- model.getShapesWithTrait(traitDef.getId()).stream()
- .filter(connected::contains)
- .forEach(shape -> {
- Trait trait = shape.findTrait(traitDef.getId()).get(); // We already know the shape has the trait.
- Node node = trait.toNode();
- query.execute(node).forEach(n ->
- // Invalid shape ids are handled by the idRef trait validator, so ignore them here.
- n.asStringNode().flatMap(StringNode::asShapeId).ifPresent(found::add)
- );
- });
+ private void addRelationships(Shape traitDef, NodeQuery query) {
+ model.getShapesWithTrait(traitDef.getId()).forEach(shape -> {
+ Trait trait = shape.findTrait(traitDef.getId()).get(); // We already know the shape has the trait.
+ Node node = trait.toNode();
+ // Invalid shape ids are handled by the idRef trait validator, so ignore them here.
+ query.execute(node).forEach(n -> n.asStringNode()
+ .flatMap(StringNode::asShapeId)
+ .flatMap(model::getShape)
+ .map(referenced -> Relationship.create(shape, RelationshipType.ID_REF, referenced))
+ .ifPresent(rel -> relationships
+ .computeIfAbsent(rel.getShape().getId(), id -> new HashSet<>()).add(rel)));
+ });
}
private static NodeQuery buildNodeQuery(PathFinder.Path path) {
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 76978d85c55..00f8dedf367 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
@@ -26,6 +26,7 @@
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
+import software.amazon.smithy.model.traits.IdRefTrait;
import software.amazon.smithy.utils.ListUtils;
/**
@@ -91,6 +92,29 @@ static NeighborProvider withTraitRelationships(Model model, NeighborProvider nei
};
}
+ /**
+ * Creates a NeighborProvider that includes {@link RelationshipType#ID_REF}
+ * relationships.
+ *
+ * @param model Model to use to look up shapes referenced by {@link IdRefTrait}.
+ * @param neighborProvider Provider to wrap.
+ * @return Returns the created neighbor provider.
+ */
+ static NeighborProvider withIdRefRelationships(Model model, NeighborProvider neighborProvider) {
+ Map> idRefRelationships = new IdRefShapeRelationships(model).getRelationships();
+ return shape -> {
+ List relationships = neighborProvider.getNeighbors(shape);
+
+ if (!idRefRelationships.containsKey(shape.getId())) {
+ return relationships;
+ }
+
+ relationships = new ArrayList<>(relationships);
+ relationships.addAll(idRefRelationships.get(shape.getId()));
+ 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 97aaceb5b0d..633154676aa 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
@@ -26,8 +26,10 @@
import software.amazon.smithy.model.shapes.OperationShape;
import software.amazon.smithy.model.shapes.ResourceShape;
import software.amazon.smithy.model.shapes.SetShape;
+import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.shapes.UnionShape;
+import software.amazon.smithy.model.traits.IdRefTrait;
import software.amazon.smithy.model.traits.TraitDefinition;
import software.amazon.smithy.utils.SmithyInternalApi;
@@ -214,7 +216,37 @@ public enum RelationshipType {
* Relationship that exists between a structure or union and a mixin applied
* to the shape.
*/
- MIXIN("mixin", RelationshipDirection.DIRECTED);
+ MIXIN("mixin", RelationshipDirection.DIRECTED),
+
+ /**
+ * Relationships that exist between a shape and another shape referenced by an
+ * {@link IdRefTrait}.
+ *
+ * This relationship is formed by applying a trait with a value containing a
+ * reference to another {@link ShapeId}. For
+ * example:
+ *
+ * {@code
+ * @trait
+ * structure myRef {
+ * @idRef
+ * shape: String
+ * }
+ *
+ * // @myRef trait applied, and the value references the shape `Referenced`
+ * @myRef(shape: Referenced)
+ * structure WithMyRef {}
+ *
+ * string Referenced
+ * }
+ *
+ *
+ * This kind of relationship is not returned by default from a
+ * {@link NeighborProvider}. You must explicitly wrap a {@link NeighborProvider}
+ * with {@link NeighborProvider#withIdRefRelationships(Model, NeighborProvider)}
+ * in order to yield idRef relationships.
+ */
+ ID_REF(null, RelationshipDirection.DIRECTED);
private String selectorLabel;
private RelationshipDirection direction;
diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/UnreferencedShapes.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/UnreferencedShapes.java
index a5c96d8a546..3455d116435 100644
--- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/UnreferencedShapes.java
+++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/UnreferencedShapes.java
@@ -22,13 +22,14 @@
import software.amazon.smithy.model.knowledge.NeighborProviderIndex;
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.TraitDefinition;
import software.amazon.smithy.utils.FunctionalUtils;
/**
* Finds shapes that are not connected to a service shape, are not trait
- * definitions, and are not referenced by trait definitions.
+ * definitions, are not referenced by trait definitions, and are not
+ * referenced in trait values through
+ * {@link software.amazon.smithy.model.traits.IdRefTrait}.
*
*
Prelude shapes are never considered unreferenced.
*/
@@ -54,7 +55,7 @@ public UnreferencedShapes(Predicate keepFilter) {
* @return Returns the unreferenced shapes.
*/
public Set compute(Model model) {
- Walker shapeWalker = new Walker(NeighborProviderIndex.of(model).getProvider());
+ Walker shapeWalker = new Walker(NeighborProviderIndex.of(model).getProviderWithIdRefRelationships());
// Find all shapes connected to any service shape.
Set connected = new HashSet<>();
@@ -67,12 +68,6 @@ public Set compute(Model model) {
connected.addAll(shapeWalker.walkShapes(trait));
}
- for (ShapeId referencedThroughIdRef : new IdRefShapeReferences(model).compute(connected)) {
- // Referenced shapes may not exist in the model, but we don't want to throw.
- model.getShape(referencedThroughIdRef)
- .ifPresent(shape -> connected.addAll(shapeWalker.walkShapes(shape)));
- }
-
// Any shape that wasn't identified as connected to a service is considered unreferenced.
Set result = new HashSet<>();
for (Shape shape : model.toSet()) {
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
index eedc0d27da7..9162dc11e45 100644
--- 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
@@ -1,13 +1,18 @@
package software.amazon.smithy.model.neighbor;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import java.util.List;
+import java.util.stream.Collectors;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
import software.amazon.smithy.model.Model;
+import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;
import software.amazon.smithy.model.shapes.StringShape;
import software.amazon.smithy.model.traits.SensitiveTrait;
@@ -42,4 +47,95 @@ public void canGetTraitRelationshipsFromShapeWithNoTraits() {
assertThat(relationships, empty());
}
+
+ @ParameterizedTest
+ @CsvSource({
+ "One,Ref1",
+ "Two,Ref2",
+ "Three,Ref3",
+ "Four,Ref4",
+ "Five,Ref5",
+ "Six,Ref6",
+ "Seven,Ref7",
+ "Eight,Ref8",
+ "Nine,Ref9",
+ "Ten,Ref10",
+ "Eleven,Ref11",
+ "Twelve,Ref12",
+ "Thirteen,Ref13",
+ "Fourteen,Ref14"
+ })
+ public void canGetIdRefRelationships(String shapeName, String referencedShapeName) {
+ Model model = Model.assembler()
+ .addImport(getClass().getResource("idref-neighbors.smithy"))
+ .assemble()
+ .unwrap();
+ NeighborProvider provider = NeighborProvider.of(model);
+ provider = NeighborProvider.withIdRefRelationships(model, provider);
+
+ Shape shape = model.expectShape(ShapeId.fromParts("com.foo", shapeName));
+ Shape ref = model.expectShape(ShapeId.fromParts("com.foo", referencedShapeName));
+ List relationships = provider.getNeighbors(shape).stream()
+ .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+ .collect(Collectors.toList());
+
+ assertThat(relationships, containsInAnyOrder(
+ equalTo(Relationship.create(shape, RelationshipType.ID_REF, ref))));
+ }
+
+ @Test
+ public void canGetIdRefRelationshipsThroughTraitDefs() {
+ Model model = Model.assembler()
+ .addImport(getClass().getResource("idref-neighbors-in-trait-def.smithy"))
+ .assemble()
+ .unwrap();
+ NeighborProvider provider = NeighborProvider.of(model);
+ provider = NeighborProvider.withIdRefRelationships(model, provider);
+
+ Shape shape = model.expectShape(ShapeId.from("com.foo#WithRefStructTrait"));
+ Shape ref = model.expectShape(ShapeId.from("com.foo#OtherReferenced"));
+ List relationships = provider.getNeighbors(shape).stream()
+ .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+ .collect(Collectors.toList());
+
+ assertThat(relationships, containsInAnyOrder(
+ equalTo(Relationship.create(shape, RelationshipType.ID_REF, ref))));
+
+ Shape shape1 = model.expectShape(ShapeId.from("com.foo#refStruct$other"));
+ Shape ref1 = model.expectShape(ShapeId.from("com.foo#ReferencedInTraitDef"));
+ List relationships1 = provider.getNeighbors(shape1).stream()
+ .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+ .collect(Collectors.toList());
+
+ assertThat(relationships1, containsInAnyOrder(
+ equalTo(Relationship.create(shape1, RelationshipType.ID_REF, ref1))));
+ }
+
+ @Test
+ public void canGetIdRefRelationshipsThroughMultipleLevelsOfIdRef() {
+ Model model = Model.assembler()
+ .addImport(getClass().getResource("idref-neighbors-multiple-levels.smithy"))
+ .assemble()
+ .unwrap();
+ NeighborProvider provider = NeighborProvider.of(model);
+ provider = NeighborProvider.withIdRefRelationships(model, provider);
+
+ Shape shape = model.expectShape(ShapeId.from("com.foo#WithIdRef"));
+ Shape ref = model.expectShape(ShapeId.from("com.foo#Referenced"));
+ List relationships = provider.getNeighbors(shape).stream()
+ .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+ .collect(Collectors.toList());
+
+ assertThat(relationships, containsInAnyOrder(
+ Relationship.create(shape, RelationshipType.ID_REF, ref)));
+
+ Shape shape1 = model.expectShape(ShapeId.from("com.foo#ConnectedThroughReference"));
+ Shape ref1 = model.expectShape(ShapeId.from("com.foo#AnotherReferenced"));
+ List relationships1 = provider.getNeighbors(shape1).stream()
+ .filter(relationship -> relationship.getRelationshipType().equals(RelationshipType.ID_REF))
+ .collect(Collectors.toList());
+
+ assertThat(relationships1, containsInAnyOrder(
+ Relationship.create(shape1, RelationshipType.ID_REF, ref1)));
+ }
}
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NodeQueryTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NodeQueryTest.java
new file mode 100644
index 00000000000..180ad55811d
--- /dev/null
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/NodeQueryTest.java
@@ -0,0 +1,152 @@
+package software.amazon.smithy.model.neighbor;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.hamcrest.Matchers.hasSize;
+
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import software.amazon.smithy.model.node.Node;
+import software.amazon.smithy.model.node.StringNode;
+
+public class NodeQueryTest {
+ @Test
+ public void noQueriesGivesNoResults() {
+ Node node = Node.from("{}");
+ List result = new NodeQuery().execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void self() {
+ Node node = Node.from("{}");
+ List result = new NodeQuery().self().execute(node);
+ assertThat(result, containsInAnyOrder(node));
+ }
+
+ @Test
+ public void selfCanBeAppliedMultipleTimes() {
+ Node node = Node.from("{}");
+ List result = new NodeQuery().self().self().self().execute(node);
+ assertThat(result, containsInAnyOrder(node));
+ }
+
+ @Test
+ public void member() {
+ Node member = StringNode.from("bar");
+ Node node = Node.objectNode().withMember("foo", member);
+ List result = new NodeQuery().member("foo").execute(node);
+ assertThat(result, containsInAnyOrder(member));
+ }
+
+ @Test
+ public void anyMember() {
+ Node member1 = StringNode.from("member-one");
+ Node member2 = StringNode.from("member-two");
+ Node node = Node.objectNode().withMember("one", member1).withMember("two", member2);
+ List result = new NodeQuery().anyMember().execute(node);
+ assertThat(result, containsInAnyOrder(member1, member2));
+ }
+
+ @Test
+ public void anyElement() {
+ Node element1 = StringNode.from("element-one");
+ Node element2 = StringNode.from("element-two");
+ Node node = Node.arrayNode(element1, element2);
+ List result = new NodeQuery().anyElement().execute(node);
+ assertThat(result, containsInAnyOrder(element1, element2));
+ }
+
+ @Test
+ public void anyMemberName() {
+ StringNode key1 = StringNode.from("one");
+ StringNode key2 = StringNode.from("two");
+ Node member1 = StringNode.from("member-one");
+ Node member2 = StringNode.from("member-two");
+ Node node = Node.objectNode().withMember(key1, member1).withMember(key2, member2);
+ List result = new NodeQuery().anyMemberName().execute(node);
+ assertThat(result, containsInAnyOrder(key1, key2));
+ }
+
+ @Test
+ public void memberGivesNoResultsOnNonObjectNode() {
+ Node node = Node.from("[{\"foo\": 0}]");
+ List result = new NodeQuery().member("foo").execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void memberGivesNoResultsIfMemberNameNotFound() {
+ Node node = Node.from("{\"a\": 0, \"b\": 0}");
+ List result = new NodeQuery().member("foo").execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void anyMemberGivesNoResultsOnNonObjectNode() {
+ Node node = Node.from("[{\"foo\": 0}]");
+ List result = new NodeQuery().anyMember().execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void anyMemberGivesNoResultsOnEmptyObjectNode() {
+ Node node = Node.from("{}");
+ List result = new NodeQuery().anyMember().execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void anyElementGivesNoResultsOnNonArrayNode() {
+ Node node = Node.from("{\"foo\": [0]}");
+ List result = new NodeQuery().anyElement().execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void anyElementGivesNoResultsOnEmptyArrayNode() {
+ Node node = Node.from("[]");
+ List result = new NodeQuery().anyElement().execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void anyMemberNameGivesNoResultsOnNonObjectNode() {
+ Node node = Node.from("1");
+ List result = new NodeQuery().anyMemberName().execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void anyMemberNameGivesNoResultsOnEmptyObject() {
+ Node node = Node.from("{}");
+ List result = new NodeQuery().anyMemberName().execute(node);
+ assertThat(result, hasSize(0));
+ }
+
+ @Test
+ public void eachQueryExecuteOnResultOfPreviousQuery() {
+ Node element1 = Node.from(0);
+ Node element2 = Node.from("{}");
+ Node element3 = Node.from("element3");
+ Node obj = Node.objectNode().withMember("foo", Node.objectNode()
+ .withMember("arr1", Node.arrayNode(element1))
+ .withMember("arr2", Node.arrayNode(element2))
+ .withMember("arr3", Node.arrayNode(element3)));
+ Node node = Node.arrayNode(obj, obj);
+
+ List result = new NodeQuery()
+ .anyElement()
+ .member("foo")
+ .anyMember()
+ .anyElement()
+ .execute(node);
+ assertThat(result, containsInAnyOrder(
+ element1,
+ element2,
+ element3,
+ element1,
+ element2,
+ element3));
+ }
+}
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/UnreferencedShapesTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/UnreferencedShapesTest.java
index d456b87ee42..382baf327b9 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/UnreferencedShapesTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/neighbor/UnreferencedShapesTest.java
@@ -84,7 +84,7 @@ private Model createPrivateShapeModel(ShapeId id) {
@Test
public void checksShapeReferencesThroughIdRef() {
Model m = Model.assembler()
- .addImport(getClass().getResource("through-idref.smithy"))
+ .addImport(getClass().getResource("idref-neighbors.smithy"))
.assemble()
.unwrap();
@@ -93,16 +93,17 @@ public void checksShapeReferencesThroughIdRef() {
}
@Test
- public void doesNotCheckShapeReferencesThroughIdRefOnUnconnectedTraits() {
+ public void doesNotCheckShapeReferencesThroughIdRefOnUnconnectedShapes() {
Model m = Model.assembler()
- .addImport(getClass().getResource("through-idref-unconnected.smithy"))
+ .addImport(getClass().getResource("idref-neighbors-unconnected.smithy"))
.assemble()
.unwrap();
Set ids = new UnreferencedShapes().compute(m).stream().map(Shape::getId).collect(Collectors.toSet());
assertThat(ids, containsInAnyOrder(
ShapeId.from("com.foo#WithTrait"),
- ShapeId.from("com.foo#Referenced")
+ ShapeId.from("com.foo#Referenced"),
+ ShapeId.from("com.foo#Unconnected")
));
}
}
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-in-trait-def.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-in-trait-def.smithy
new file mode 100644
index 00000000000..59c2303047f
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-in-trait-def.smithy
@@ -0,0 +1,34 @@
+$version: "2.0"
+
+namespace com.foo
+
+service FooService {
+ version: "2024-01-22"
+ operations: [GetFoo]
+}
+
+operation GetFoo {
+ input := {
+ withRefStructTrait: WithRefStructTrait
+ }
+}
+
+@trait
+@idRef(failWhenMissing: true)
+string ref
+
+@trait
+structure refStruct {
+ @ref(ReferencedInTraitDef)
+ other: String
+
+ @idRef(failWhenMissing: true)
+ ref: String
+}
+
+string ReferencedInTraitDef
+
+@refStruct(other: "foo", ref: OtherReferenced)
+structure WithRefStructTrait {}
+
+string OtherReferenced
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-multiple-levels.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-multiple-levels.smithy
new file mode 100644
index 00000000000..211cf736d59
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-multiple-levels.smithy
@@ -0,0 +1,32 @@
+$version: "2.0"
+
+namespace com.foo
+
+service FooService {
+ version: "2024-01-22"
+ operations: [GetFoo]
+}
+
+operation GetFoo {
+ input := {
+ withIdRef: WithIdRef
+ }
+}
+
+@trait
+@idRef(failWhenMissing: true)
+string ref
+
+@ref(Referenced)
+structure WithIdRef {}
+
+structure Referenced {
+ connectedThroughReferenced: ConnectedThroughReferenced
+}
+
+// Only connected through `Referenced`, which itself is only
+// connected via idRef.
+@ref(AnotherReferenced)
+structure ConnectedThroughReferenced {}
+
+string AnotherReferenced
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-unconnected.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-unconnected.smithy
new file mode 100644
index 00000000000..790028fe45b
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors-unconnected.smithy
@@ -0,0 +1,32 @@
+$version: "2.0"
+
+namespace com.foo
+
+service FooService {
+ version: "2024-01-22"
+ operations: [GetFoo]
+}
+
+operation GetFoo {
+ input := {
+ withReferencedByUnconnected: WithReferencedByUnconnected
+ }
+}
+
+@trait
+@idRef(failWhenMissing: true)
+string ref
+
+@ref(Referenced)
+structure WithTrait {}
+
+structure Referenced {}
+
+@ref(ReferencedByUnconnected)
+structure WithReferencedByUnconnected {}
+
+string ReferencedByUnconnected
+
+structure Unconnected {
+ ref: ReferencedByUnconnected
+}
\ No newline at end of file
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/through-idref.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors.smithy
similarity index 83%
rename from smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/through-idref.smithy
rename to smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors.smithy
index f73bd85e7bb..f5b9e119f52 100644
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/through-idref.smithy
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/idref-neighbors.smithy
@@ -20,6 +20,9 @@ operation GetFoo {
nine: Nine
ten: Ten
eleven: Eleven
+ twelve: Twelve
+ thirteen: Thirteen
+ fourteen: Fourteen
}
}
@@ -182,3 +185,36 @@ map withIdRefOnMapKey {
structure Eleven {}
structure Ref11 {}
+
+// --
+@trait
+@idRef(failWhenMissing: true)
+string ref
+
+@ref(Ref12)
+structure Twelve {}
+
+structure Ref12 {
+ connectedToRef13: ConnectedToRef13
+}
+
+structure ConnectedToRef13 {
+ ref13: Ref13
+}
+
+@ref(Ref13)
+structure Thirteen {}
+
+structure Ref13 {
+ connectedToRef14: ConnectedToRef14
+}
+
+structure ConnectedToRef14 {
+ ref14: Ref14
+}
+
+@ref(Ref14)
+structure Fourteen {}
+
+string Ref14
+
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/through-idref-unconnected.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/through-idref-unconnected.smithy
deleted file mode 100644
index 77346b7f393..00000000000
--- a/smithy-model/src/test/resources/software/amazon/smithy/model/neighbor/through-idref-unconnected.smithy
+++ /dev/null
@@ -1,12 +0,0 @@
-$version: "2.0"
-
-namespace com.foo
-
-@trait
-@idRef(failWhenMissing: true, selector: "*")
-string unconnected
-
-@unconnected(Referenced)
-structure WithTrait {}
-
-structure Referenced {}