Skip to content

Commit 8108216

Browse files
Add additional shape type selectors (#3070)
This adds three new shape type selectors: * `aggregateType` selects lists, unions, structures, and maps. * `serviceType` selects services, operations, and resources. * `dataType` selects aggregate types and simple types. This also adds a new `toSet` override that takes a shape category and returns a cached set of shapes in that category.
1 parent 8216ae7 commit 8108216

9 files changed

Lines changed: 284 additions & 5 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "feature",
3+
"description": "Added three new shape type selectors\n\n* `aggregateType` selects lists, structures, unions, and maps.\n* `serviceType` selects services, resources, and operations.\n* `dataType` selects aggregate types and simple types.",
4+
"pull_requests": [
5+
"[#3070](https://github.com/smithy-lang/smithy/pull/3070)"
6+
]
7+
}

docs/source-2.0/spec/selectors.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ Shapes can be matched by type using the following tokens:
7474
``double``, ``bigDecimal``, and ``bigInteger`` shapes
7575
* - ``simpleType``
7676
- Matches all :ref:`simple types <simple-types>`
77+
* - ``aggregateType``
78+
- Matches all :ref:`aggregate types <aggregate-types>` (``list``,
79+
``map``, ``structure``, and ``union``)
80+
* - ``dataType``
81+
- Matches all :ref:`simple types <simple-types>` and
82+
:ref:`aggregate types <aggregate-types>`. This is an alias of
83+
``:is(simpleType, aggregateType)``.
84+
* - ``serviceType``
85+
- Matches all :ref:`service types <service-types>` (``service``,
86+
``operation``, and ``resource``)
7787
* - ``collection``
7888
- Deprecated: An alias of ``list``. Also matches ``set`` shapes in Smithy IDL 1.0.
7989
* - ``blob``

smithy-model/src/main/java/software/amazon/smithy/model/Model.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
import software.amazon.smithy.model.shapes.SetShape;
4545
import software.amazon.smithy.model.shapes.Shape;
4646
import software.amazon.smithy.model.shapes.ShapeId;
47+
import software.amazon.smithy.model.shapes.ShapeType;
4748
import software.amazon.smithy.model.shapes.ShortShape;
49+
import software.amazon.smithy.model.shapes.SimpleShape;
4850
import software.amazon.smithy.model.shapes.StringShape;
4951
import software.amazon.smithy.model.shapes.StructureShape;
5052
import software.amazon.smithy.model.shapes.TimestampShape;
@@ -76,6 +78,9 @@ public final class Model implements ToSmithyBuilder<Model> {
7678
/** A cache of shapes of a specific type. */
7779
private final Map<Class<? extends Shape>, Set<? extends Shape>> cachedTypes = new ConcurrentHashMap<>();
7880

81+
/** A cache of shapes of a specific category. */
82+
private final Map<ShapeType.Category, Set<? extends Shape>> cachedCategories = new ConcurrentHashMap<>();
83+
7984
/** Cache of computed {@link KnowledgeIndex} instances. */
8085
private final Map<String, KnowledgeIndex> blackboard = new ConcurrentSkipListMap<>();
8186

@@ -753,6 +758,31 @@ public <T extends Shape> Stream<T> shapes(Class<T> shapeType) {
753758
return toSet(shapeType).stream();
754759
}
755760

761+
/**
762+
* Gets an immutable Set of shapes of a specific category.
763+
*
764+
* @param shapeCategory The category of shape to get a set of.
765+
* @return Returns an unmodifiable set of shapes.
766+
*/
767+
public Set<? extends Shape> toSet(ShapeType.Category shapeCategory) {
768+
switch (shapeCategory) {
769+
case SIMPLE:
770+
return toSet(SimpleShape.class);
771+
case MEMBER:
772+
return toSet(MemberShape.class);
773+
default:
774+
return cachedCategories.computeIfAbsent(shapeCategory, c -> {
775+
Set<Shape> result = new HashSet<>();
776+
for (Shape shape : shapeMap.values()) {
777+
if (shape.getType().getCategory() == shapeCategory) {
778+
result.add(shape);
779+
}
780+
}
781+
return Collections.unmodifiableSet(result);
782+
});
783+
}
784+
}
785+
756786
/**
757787
* Gets an immutable Set of shapes of a specific type.
758788
*

smithy-model/src/main/java/software/amazon/smithy/model/selector/SelectorParser.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package software.amazon.smithy.model.selector;
66

77
import java.util.ArrayList;
8+
import java.util.Arrays;
89
import java.util.Collections;
910
import java.util.HashSet;
1011
import java.util.List;
@@ -15,7 +16,6 @@
1516
import software.amazon.smithy.model.shapes.CollectionShape;
1617
import software.amazon.smithy.model.shapes.NumberShape;
1718
import software.amazon.smithy.model.shapes.ShapeType;
18-
import software.amazon.smithy.model.shapes.SimpleShape;
1919
import software.amazon.smithy.utils.SetUtils;
2020
import software.amazon.smithy.utils.SimpleParser;
2121

@@ -133,9 +133,17 @@ private InternalSelector createSelector() {
133133
case "number":
134134
return new ShapeTypeCategorySelector(NumberShape.class);
135135
case "simpleType":
136-
return new ShapeTypeCategorySelector(SimpleShape.class);
136+
return new ShapeTypeCategoryEnumSelector(ShapeType.Category.SIMPLE);
137137
case "collection":
138138
return new ShapeTypeCategorySelector(CollectionShape.class);
139+
case "aggregateType":
140+
return new ShapeTypeCategoryEnumSelector(ShapeType.Category.AGGREGATE);
141+
case "serviceType":
142+
return new ShapeTypeCategoryEnumSelector(ShapeType.Category.SERVICE);
143+
case "dataType":
144+
return IsSelector.of(Arrays.asList(
145+
new ShapeTypeCategoryEnumSelector(ShapeType.Category.SIMPLE),
146+
new ShapeTypeCategoryEnumSelector(ShapeType.Category.AGGREGATE)));
139147
default:
140148
ShapeType shape = ShapeType.fromString(identifier)
141149
.orElseThrow(() -> syntax("Unknown shape type: " + identifier));
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.model.selector;
6+
7+
import java.util.Collection;
8+
import software.amazon.smithy.model.Model;
9+
import software.amazon.smithy.model.shapes.Shape;
10+
import software.amazon.smithy.model.shapes.ShapeType;
11+
12+
/**
13+
* Matches shapes whose {@link ShapeType.Category} equals a given category.
14+
*
15+
* <p>This complements {@link ShapeTypeCategorySelector}, which keys off of a shared
16+
* Java base class. A class-based check is not possible for the AGGREGATE and SERVICE
17+
* categories because their shape types do not share a common Java superclass.
18+
*/
19+
final class ShapeTypeCategoryEnumSelector implements InternalSelector {
20+
private final ShapeType.Category category;
21+
22+
ShapeTypeCategoryEnumSelector(ShapeType.Category category) {
23+
this.category = category;
24+
}
25+
26+
@Override
27+
public Response push(Context ctx, Shape shape, Receiver next) {
28+
if (shape.getType().getCategory() == category) {
29+
return next.apply(ctx, shape);
30+
}
31+
return Response.CONTINUE;
32+
}
33+
34+
@Override
35+
public Collection<? extends Shape> getStartingShapes(Model model) {
36+
return model.toSet(category);
37+
}
38+
39+
@Override
40+
public ContainsShape containsShapeOptimization(Context context, Shape shape) {
41+
return shape.getType().getCategory() == category ? ContainsShape.YES : ContainsShape.NO;
42+
}
43+
}

smithy-model/src/main/java/software/amazon/smithy/model/shapes/ShapeType.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,34 @@ public boolean isShapeType(ShapeType other) {
5858
RESOURCE("resource", ResourceShape.class, Category.SERVICE),
5959
OPERATION("operation", OperationShape.class, Category.SERVICE);
6060

61+
/**
62+
* Top-level grouping of {@link ShapeType} values as defined in the Smithy specification.
63+
*
64+
* <p>Categories are useful when a piece of code needs to distinguish between broad kinds
65+
* of shapes without enumerating each individual type.
66+
*/
6167
public enum Category {
62-
SIMPLE, AGGREGATE, SERVICE, MEMBER
68+
/**
69+
* Simple types that model basic data values.
70+
*/
71+
SIMPLE,
72+
73+
/**
74+
* Aggregate types that compose other shapes (lists, maps, structures, and unions).
75+
*
76+
* <p>The deprecated {@code set} type also falls into this category.
77+
*/
78+
AGGREGATE,
79+
80+
/**
81+
* Service types that define a service-level interface (services, resources, and operations).
82+
*/
83+
SERVICE,
84+
85+
/**
86+
* The special {@code member} type, which references another shape from inside an aggregate shape.
87+
*/
88+
MEMBER
6389
}
6490

6591
private final String stringValue;
@@ -87,8 +113,9 @@ public Class<? extends Shape> getShapeClass() {
87113
}
88114

89115
/**
90-
* Returns the category of the shape type, as defined in the Smithy
91-
* specification (one of SIMPLE, AGGREGATE, or SERVICE).
116+
* Returns the category of the shape type, as defined in the Smithy specification
117+
* (one of {@link Category#SIMPLE}, {@link Category#AGGREGATE}, {@link Category#SERVICE},
118+
* or {@link Category#MEMBER}).
92119
*
93120
* @return Returns the category of the type.
94121
*/

smithy-model/src/test/java/software/amazon/smithy/model/ModelTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,29 @@ public void toSetWithTypeRespectsSubclassing() {
233233
assertThat(model.toSet(StringShape.class), containsInAnyOrder(a, b));
234234
}
235235

236+
@Test
237+
public void toSetWithCategoryReturnsShapesOfCategory() {
238+
StringShape string = StringShape.builder().id("ns.foo#Str").build();
239+
IntegerShape integer = IntegerShape.builder().id("ns.foo#Int").build();
240+
ListShape list = ListShape.builder()
241+
.id("ns.foo#List")
242+
.member(string.getId())
243+
.build();
244+
StructureShape structure = StructureShape.builder().id("ns.foo#Struct").build();
245+
OperationShape operation = OperationShape.builder().id("ns.foo#Op").build();
246+
ServiceShape service = ServiceShape.builder()
247+
.id("ns.foo#Service")
248+
.version("1")
249+
.addOperation(operation)
250+
.build();
251+
Model model = Model.builder().addShapes(string, integer, list, structure, operation, service).build();
252+
253+
assertThat(model.toSet(ShapeType.Category.SIMPLE), containsInAnyOrder(string, integer));
254+
assertThat(model.toSet(ShapeType.Category.AGGREGATE), containsInAnyOrder(list, structure));
255+
assertThat(model.toSet(ShapeType.Category.SERVICE), containsInAnyOrder(service, operation));
256+
assertThat(model.toSet(ShapeType.Category.MEMBER), containsInAnyOrder(list.getMember()));
257+
}
258+
236259
@Test
237260
public void addsMembersAutomatically() {
238261
StringShape string = StringShape.builder().id("ns.foo#a").build();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package software.amazon.smithy.model.selector;
6+
7+
import static org.hamcrest.MatcherAssert.assertThat;
8+
import static org.hamcrest.Matchers.containsInAnyOrder;
9+
import static org.hamcrest.Matchers.in;
10+
import static org.hamcrest.Matchers.not;
11+
12+
import java.util.Set;
13+
import org.junit.jupiter.api.BeforeAll;
14+
import org.junit.jupiter.api.Test;
15+
import software.amazon.smithy.model.Model;
16+
17+
public class ShapeTypeCategoryEnumSelectorTest {
18+
19+
private static Model model;
20+
21+
@BeforeAll
22+
public static void before() {
23+
model = Model.assembler()
24+
.addImport(SelectorTest.class.getResource("shape-type-test.smithy"))
25+
.assemble()
26+
.unwrap();
27+
}
28+
29+
@Test
30+
public void simpleTypeMatchesSimpleShapes() {
31+
Set<String> ids = exampleIds("simpleType");
32+
33+
assertThat(ids,
34+
containsInAnyOrder("smithy.example#String",
35+
"smithy.example#Integer",
36+
"smithy.example#Enum",
37+
"smithy.example#IntEnum"));
38+
assertThat("smithy.example#List", not(in(ids)));
39+
assertThat("smithy.example#Service", not(in(ids)));
40+
}
41+
42+
@Test
43+
public void aggregateTypeMatchesAggregateShapes() {
44+
Set<String> ids = exampleIds("aggregateType");
45+
46+
assertThat(ids,
47+
containsInAnyOrder(
48+
"smithy.example#List",
49+
"smithy.example#Map",
50+
"smithy.example#Structure",
51+
"smithy.example#Union",
52+
"smithy.example#OperationInput",
53+
"smithy.example#OperationOutput"));
54+
assertThat("smithy.example#String", not(in(ids)));
55+
assertThat("smithy.example#Service", not(in(ids)));
56+
}
57+
58+
@Test
59+
public void serviceTypeMatchesServiceShapes() {
60+
Set<String> ids = exampleIds("serviceType");
61+
62+
assertThat(ids,
63+
containsInAnyOrder(
64+
"smithy.example#Service",
65+
"smithy.example#Operation",
66+
"smithy.example#Resource"));
67+
}
68+
69+
@Test
70+
public void dataTypeMatchesAggregateAndSimpleShapes() {
71+
Set<String> ids = exampleIds("dataType");
72+
73+
assertThat(ids,
74+
containsInAnyOrder(
75+
"smithy.example#String",
76+
"smithy.example#Integer",
77+
"smithy.example#Enum",
78+
"smithy.example#IntEnum",
79+
"smithy.example#List",
80+
"smithy.example#Map",
81+
"smithy.example#Structure",
82+
"smithy.example#OperationInput",
83+
"smithy.example#OperationOutput",
84+
"smithy.example#Union"));
85+
86+
// Service shapes are excluded.
87+
assertThat("smithy.example#Service", not(in(ids)));
88+
assertThat("smithy.example#Operation", not(in(ids)));
89+
assertThat("smithy.example#Resource", not(in(ids)));
90+
91+
// Member shapes are excluded
92+
assertThat("smithy.example#List$member", not(in(ids)));
93+
}
94+
95+
private static Set<String> exampleIds(String typeSelector) {
96+
return SelectorTest.ids(model, typeSelector + " [id|namespace = smithy.example]");
97+
}
98+
}

smithy-model/src/test/resources/software/amazon/smithy/model/selector/shape-type-test.smithy

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,36 @@ intEnum IntEnum {
1414
}
1515

1616
integer Integer
17+
18+
list List {
19+
member: String
20+
}
21+
22+
map Map {
23+
key: String
24+
value: String
25+
}
26+
27+
structure Structure {
28+
member: String
29+
}
30+
31+
union Union {
32+
a: String
33+
b: Integer
34+
}
35+
36+
service Service {
37+
version: "1"
38+
operations: [Operation]
39+
resources: [Resource]
40+
}
41+
42+
operation Operation {
43+
input := {}
44+
output := {}
45+
}
46+
47+
resource Resource {
48+
identifiers: { id: String }
49+
}

0 commit comments

Comments
 (0)