From 2082f48296e9f9bfb0d0e7a902a7b22b2fa14d88 Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Mon, 18 Aug 2025 18:19:53 +0200 Subject: [PATCH 1/2] Return contained operations/resources in order This updates the results of TopDownIndex to be in modeled order. Previously the resuls were being sorted. This problem extended to the Walker, which was returning shapes in reverse order. --- ...d5c00f6fa03f97cef7cdfbe86d1940ae23ff6.json | 7 +++ ...f3996ee7fadab4b8e65c80832be56fa76584c.json | 7 +++ .../core/directed/CodegenDirectorTest.java | 2 +- .../smithy/model/knowledge/TopDownIndex.java | 6 +- .../amazon/smithy/model/neighbor/Walker.java | 10 +++- .../model/knowledge/TopDownIndexTest.java | 58 +++++++++++++++++++ .../model/knowledge/top-down-order.smithy | 43 ++++++++++++++ .../validators/RuleSetParameterValidator.java | 8 ++- 8 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 .changes/next-release/feature-3d5d5c00f6fa03f97cef7cdfbe86d1940ae23ff6.json create mode 100644 .changes/next-release/feature-bb6f3996ee7fadab4b8e65c80832be56fa76584c.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/top-down-order.smithy diff --git a/.changes/next-release/feature-3d5d5c00f6fa03f97cef7cdfbe86d1940ae23ff6.json b/.changes/next-release/feature-3d5d5c00f6fa03f97cef7cdfbe86d1940ae23ff6.json new file mode 100644 index 00000000000..317243e3813 --- /dev/null +++ b/.changes/next-release/feature-3d5d5c00f6fa03f97cef7cdfbe86d1940ae23ff6.json @@ -0,0 +1,7 @@ +{ + "type": "feature", + "description": "Updated `Walker` to walk relationships in their defined order, rather than the reverse of their defined order.", + "pull_requests": [ + "[#2746](https://github.com/smithy-lang/smithy/pull/2746)" + ] +} diff --git a/.changes/next-release/feature-bb6f3996ee7fadab4b8e65c80832be56fa76584c.json b/.changes/next-release/feature-bb6f3996ee7fadab4b8e65c80832be56fa76584c.json new file mode 100644 index 00000000000..cf4f41e7059 --- /dev/null +++ b/.changes/next-release/feature-bb6f3996ee7fadab4b8e65c80832be56fa76584c.json @@ -0,0 +1,7 @@ +{ + "type": "feature", + "description": "Updated `TopDownIndex` to return results in modeled order.", + "pull_requests": [ + "[#2746](https://github.com/smithy-lang/smithy/pull/2746)" + ] +} diff --git a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java index d324ce40178..dbeb2b038ae 100644 --- a/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java +++ b/smithy-codegen-core/src/test/java/software/amazon/smithy/codegen/core/directed/CodegenDirectorTest.java @@ -337,7 +337,7 @@ public void testShapesGenerationWithoutOrder() { runner.run(); assertThat(testDirected.generatedShapes, - contains( + containsInAnyOrder( ShapeId.from("smithy.example#FooOperation"), ShapeId.from("smithy.example#FooOperationOutput"), ShapeId.from("smithy.example#A"), diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/TopDownIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/TopDownIndex.java index 4dccc9c5f80..a6e44df3dca 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/TopDownIndex.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/TopDownIndex.java @@ -7,9 +7,9 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; -import java.util.TreeSet; import java.util.function.Predicate; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.neighbor.NeighborProvider; @@ -56,8 +56,8 @@ public static TopDownIndex of(Model model) { } private void findContained(ShapeId container, Collection shapes) { - Set containedResources = new TreeSet<>(); - Set containedOperations = new TreeSet<>(); + Set containedResources = new LinkedHashSet<>(); + Set containedOperations = new LinkedHashSet<>(); for (Shape shape : shapes) { if (!shape.getId().equals(container)) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/Walker.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/Walker.java index a2f6eae182a..7bf75b66c08 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/Walker.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/Walker.java @@ -154,7 +154,15 @@ public boolean hasNext() { while (!stack.isEmpty()) { // Every relationship is returned, even if the same shape is pointed // to multiple times from a single shape. - Relationship relationship = stack.pop(); + + // Use removeLast to retrieve relationships in their defined order rather + // than the reverse of the defined order. Note that this only preserves + // the order of a particular relationship type, not the order of all + // relationship types. So a resources `operation` relationships will be + // resolved in the order of that list, but the resources's `resource` + // relationships will nevertheless always appear first because that is + // simply the order that the NeighborVisitor checks them in. + Relationship relationship = stack.removeLast(); // Only traverse this relationship if the shape it points to hasn't // already been traversed. diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/TopDownIndexTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/TopDownIndexTest.java index ef1d9eac97d..4b78633379c 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/TopDownIndexTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/TopDownIndexTest.java @@ -9,11 +9,14 @@ import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.empty; +import java.util.List; +import java.util.stream.Collectors; import org.junit.jupiter.api.Test; import software.amazon.smithy.model.Model; 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; public class TopDownIndexTest { @@ -67,4 +70,59 @@ public void findsAllChildren() { assertThat(childIndex.getContainedResources(ShapeId.from("ns.foo#NotThere")), empty()); } + + @Test + public void preservesModeledOrder() { + Model model = Model.assembler() + .addImport(TopDownIndexTest.class.getResource("top-down-order.smithy")) + .assemble() + .unwrap(); + + TopDownIndex index = TopDownIndex.of(model); + List serviceOperations = index + .getContainedOperations(ShapeId.from("com.example#Service")) + .stream() + .map(Shape::toShapeId) + .collect(Collectors.toList()); + assertThat(serviceOperations, + contains( + ShapeId.from("com.example#OperationB"), + ShapeId.from("com.example#OperationA"), + ShapeId.from("com.example#OperationC"), + ShapeId.from("com.example#OperationD"), + ShapeId.from("com.example#OperationO"), + ShapeId.from("com.example#OperationG"))); + + List resourceOperations = index + .getContainedOperations(ShapeId.from("com.example#ResourceA")) + .stream() + .map(Shape::toShapeId) + .collect(Collectors.toList()); + assertThat(resourceOperations, + contains( + ShapeId.from("com.example#OperationD"), + ShapeId.from("com.example#OperationO"), + ShapeId.from("com.example#OperationG"))); + + List serviceResources = index + .getContainedResources(ShapeId.from("com.example#Service")) + .stream() + .map(Shape::toShapeId) + .collect(Collectors.toList()); + assertThat(serviceResources, + contains( + ShapeId.from("com.example#ResourceA"), + ShapeId.from("com.example#ResourceC"), + ShapeId.from("com.example#ResourceB"))); + + List resourceResources = index + .getContainedResources(ShapeId.from("com.example#ResourceA")) + .stream() + .map(Shape::toShapeId) + .collect(Collectors.toList()); + assertThat(resourceResources, + contains( + ShapeId.from("com.example#ResourceC"), + ShapeId.from("com.example#ResourceB"))); + } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/top-down-order.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/top-down-order.smithy new file mode 100644 index 00000000000..6b8cb295ded --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/top-down-order.smithy @@ -0,0 +1,43 @@ +$version: "2.0" + +namespace com.example + + +service Service { + operations: [ + OperationB + OperationA + OperationC + ] + resources: [ + ResourceA + ] +} + +operation OperationB { } + +operation OperationC {} + +operation OperationA {} + +resource ResourceA { + operations: [ + OperationD + OperationO + OperationG + ] + resources: [ + ResourceC + ResourceB + ] +} + +resource ResourceB {} + +resource ResourceC {} + +operation OperationD {} + +operation OperationG {} + +operation OperationO {} diff --git a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java index 709b84db30d..fd1cf5b5a5f 100644 --- a/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java +++ b/smithy-rules-engine/src/main/java/software/amazon/smithy/rulesengine/validators/RuleSetParameterValidator.java @@ -5,6 +5,8 @@ package software.amazon.smithy.rulesengine.validators; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -71,7 +73,9 @@ private void validate( ) { // Pull all the parameters used in this service related to endpoints, validating that // they are of matching types across the traits that can define them. - Set operations = topDownIndex.getContainedOperations(service); + List operations = new ArrayList<>(topDownIndex.getContainedOperations(service)); + Collections.reverse(operations); + Map modelParams = validateAndExtractParameters(errors, model, service, operations); // Make sure parameters align across Params <-> RuleSet transitions. validateParametersMatching(errors, service, sourceLocation, parameters, modelParams); @@ -85,7 +89,7 @@ private Map validateAndExtractParameters( List errors, Model model, ServiceShape service, - Set containedOperations + Collection containedOperations ) { Map endpointParams = new HashMap<>(); From fcf164efdbaf0588c3ef732ce6058e8d76fdc579 Mon Sep 17 00:00:00 2001 From: Jordon Phillips Date: Mon, 27 Oct 2025 11:48:33 +0100 Subject: [PATCH 2/2] Fix typo in walker comments Co-authored-by: Kevin Stich --- .../main/java/software/amazon/smithy/model/neighbor/Walker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/Walker.java b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/Walker.java index 7bf75b66c08..e7318569743 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/Walker.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/neighbor/Walker.java @@ -158,7 +158,7 @@ public boolean hasNext() { // Use removeLast to retrieve relationships in their defined order rather // than the reverse of the defined order. Note that this only preserves // the order of a particular relationship type, not the order of all - // relationship types. So a resources `operation` relationships will be + // relationship types. So a resource's `operation` relationships will be // resolved in the order of that list, but the resources's `resource` // relationships will nevertheless always appear first because that is // simply the order that the NeighborVisitor checks them in.