diff --git a/docs/source/1.0/spec/core/constraint-traits.rst b/docs/source/1.0/spec/core/constraint-traits.rst index fc7b10a59ba..f2fd1bdeda5 100644 --- a/docs/source/1.0/spec/core/constraint-traits.rst +++ b/docs/source/1.0/spec/core/constraint-traits.rst @@ -597,6 +597,11 @@ in a response. ``uniqueItems`` trait --------------------- +.. warning: + + This trait has been deprecated. It may be removed in future versions. Set + shapes should be used instead. + Summary Indicates that the items in a :ref:`list` MUST be unique. Trait selector diff --git a/docs/source/1.0/spec/core/model.rst b/docs/source/1.0/spec/core/model.rst index c980b4a7a6c..bc89989231f 100644 --- a/docs/source/1.0/spec/core/model.rst +++ b/docs/source/1.0/spec/core/model.rst @@ -458,7 +458,7 @@ reference other shapes using :ref:`members `. * - :ref:`list` - Ordered collection of homogeneous values * - :ref:`set` - - Unordered collection of unique homogeneous values + - Collection of unique homogeneous values * - :ref:`map` - Map data structure that maps string keys to homogeneous values * - :ref:`structure` @@ -565,7 +565,7 @@ example is ``smithy.example#MyList$member``. Set === -The :dfn:`set` type represents an unordered collection of unique homogeneous +The :dfn:`set` type represents a collection of unique homogeneous values. A set shape requires a single member named ``member``. Sets are defined in the IDL using a :ref:`set_statement `. The following example defines a set of strings: @@ -618,6 +618,13 @@ SHOULD represent sets as a custom set data structure that can interpret value hash codes and equality, or alternatively, store the values of a set data structure in a list and rely on validation to ensure uniqueness. +.. rubric:: Set member ordering + +Sets MUST be insertion ordered. Not all programming languages that support +sets support ordered sets, requiring them may be overly burdensome for users, +or conflict with language idioms. Such languages SHOULD store the values +of sets in a list and rely on validation to ensure uniqueness. + .. _map: diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/ServiceIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/ServiceIndex.java index 9907c290f6b..3589669e03d 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/ServiceIndex.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/ServiceIndex.java @@ -205,7 +205,7 @@ private Map getAuthTraitValues(Shape service, Shape subject) { AuthTrait authTrait = subject.expectTrait(AuthTrait.class); Map result = new LinkedHashMap<>(); - for (ShapeId value : authTrait.getValues()) { + for (ShapeId value : authTrait.getValueSet()) { service.findTrait(value).ifPresent(trait -> result.put(value, trait)); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java b/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java index f3dd0585c66..df5e8a56753 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/node/DefaultNodeDeserializers.java @@ -37,6 +37,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -174,9 +175,11 @@ private ReflectiveSupplier> createSupplier(Class targetTyp || targetType.equals(ArrayList.class) || targetType.equals(Iterable.class)) { return ArrayList::new; - } else if (targetType.equals(Set.class) || targetType.equals(HashSet.class)) { + } else if (targetType.equals(Set.class) + || targetType.equals(HashSet.class) + || targetType.equals(LinkedHashSet.class)) { // Special casing for Set or HashSet. - return HashSet::new; + return LinkedHashSet::new; } else if (Collection.class.isAssignableFrom(targetType)) { return createSupplierFromReflection(targetType); } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/AuthTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/AuthTrait.java index fe6a63c562b..c0dbd790631 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/AuthTrait.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/AuthTrait.java @@ -15,8 +15,9 @@ package software.amazon.smithy.model.traits; -import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import software.amazon.smithy.model.FromSourceLocation; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.node.ArrayNode; @@ -24,6 +25,7 @@ import software.amazon.smithy.model.node.StringNode; import software.amazon.smithy.model.shapes.ShapeId; import software.amazon.smithy.utils.ListUtils; +import software.amazon.smithy.utils.SetUtils; /** * Specifies the auth schemes supported by default for operations @@ -33,26 +35,42 @@ public final class AuthTrait extends AbstractTrait { public static final ShapeId ID = ShapeId.from("smithy.api#auth"); - private final List values; + private final Set values; + @Deprecated public AuthTrait(List values, FromSourceLocation sourceLocation) { super(ID, sourceLocation); - this.values = ListUtils.copyOf(values); + this.values = SetUtils.orderedCopyOf(values); } + @Deprecated public AuthTrait(List values) { this(values, SourceLocation.NONE); } + public AuthTrait(Set values, FromSourceLocation sourceLocation) { + super(ID, sourceLocation); + this.values = SetUtils.orderedCopyOf(values); + } + + public AuthTrait(Set values) { + this(values, SourceLocation.NONE); + } + /** * Gets the auth scheme trait values. * * @return Returns the supported auth schemes. */ - public List getValues() { + public Set getValueSet() { return values; } + @Deprecated + public List getValues() { + return ListUtils.copyOf(values); + } + public static final class Provider extends AbstractTrait.Provider { public Provider() { super(ID); @@ -60,7 +78,7 @@ public Provider() { @Override public Trait createTrait(ShapeId target, Node value) { - List values = new ArrayList<>(); + Set values = new LinkedHashSet<>(); for (StringNode node : value.expectArrayNode().getElementsAs(StringNode.class)) { values.add(node.expectShapeId()); } @@ -70,7 +88,7 @@ public Trait createTrait(ShapeId target, Node value) { @Override protected Node createNode() { - return getValues().stream().map(ShapeId::toString).map(Node::from) + return getValueSet().stream().map(ShapeId::toString).map(Node::from) .collect(ArrayNode.collect(getSourceLocation())); } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/CorsTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/CorsTrait.java index 72f7e2e4054..01a29f119d0 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/CorsTrait.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/CorsTrait.java @@ -15,7 +15,7 @@ package software.amazon.smithy.model.traits; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -121,12 +121,12 @@ public Builder maxAge(int maxAge) { } public Builder additionalAllowedHeaders(Set additionalAllowedHeaders) { - this.additionalAllowedHeaders = new HashSet<>(Objects.requireNonNull(additionalAllowedHeaders)); + this.additionalAllowedHeaders = new LinkedHashSet<>(Objects.requireNonNull(additionalAllowedHeaders)); return this; } public Builder additionalExposedHeaders(Set additionalExposedHeaders) { - this.additionalExposedHeaders = new HashSet<>(Objects.requireNonNull(additionalExposedHeaders)); + this.additionalExposedHeaders = new LinkedHashSet<>(Objects.requireNonNull(additionalExposedHeaders)); return this; } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/traits/UniqueItemsTrait.java b/smithy-model/src/main/java/software/amazon/smithy/model/traits/UniqueItemsTrait.java index 68be5d9560b..51c6fa5435b 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/traits/UniqueItemsTrait.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/traits/UniqueItemsTrait.java @@ -22,6 +22,7 @@ /** * Indicates that the members of a list must be unique. */ +@Deprecated public final class UniqueItemsTrait extends AnnotationTrait { public static final ShapeId ID = ShapeId.from("smithy.api#uniqueItems"); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/AuthTraitValidator.java b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/AuthTraitValidator.java index 547baf160de..0f5f52bb861 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/AuthTraitValidator.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/validation/validators/AuthTraitValidator.java @@ -65,7 +65,7 @@ private void validateShape( ) { if (shape.getTrait(AuthTrait.class).isPresent()) { AuthTrait authTrait = shape.getTrait(AuthTrait.class).get(); - Set appliedAuthTraitValue = new TreeSet<>(authTrait.getValues()); + Set appliedAuthTraitValue = new TreeSet<>(authTrait.getValueSet()); appliedAuthTraitValue.removeAll(serviceAuth); if (!appliedAuthTraitValue.isEmpty()) { diff --git a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy index f4b83d4f2e3..3d595520280 100644 --- a/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy +++ b/smithy-model/src/main/resources/software/amazon/smithy/model/loader/prelude.smithy @@ -116,8 +116,7 @@ map externalDocumentation { /// Defines the list of authentication schemes supported by a service or operation. @trait(selector: ":is(service, operation)") -@uniqueItems -list auth { +set auth { member: AuthTraitReference } @@ -492,6 +491,7 @@ structure sparse {} /// Indicates that the items in a list MUST be unique. @trait(selector: "list") +@deprecated(message: "The uniqueItems trait has been deprecated in favor of using sets.", since: "2.0") structure uniqueItems {} /// Indicates that the shape is unstable and could change in the future. diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java index 3a8847e7992..552a3f8b471 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/node/NodeMapperTest.java @@ -784,7 +784,7 @@ public void deserializesSets() { NodeMapper mapper = new NodeMapper(); Set result = mapper.deserializeCollection(value, Set.class, String.class); - assertThat(result, instanceOf(HashSet.class)); + assertThat(result, instanceOf(LinkedHashSet.class)); assertThat(result, contains("a", "b")); } @@ -794,7 +794,7 @@ public void deserializesHashSets() { NodeMapper mapper = new NodeMapper(); Set result = mapper.deserializeCollection(value, HashSet.class, String.class); - assertThat(result, instanceOf(HashSet.class)); + assertThat(result, instanceOf(LinkedHashSet.class)); assertThat(result, contains("a", "b")); } diff --git a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java index 4c1f2208d80..36055458770 100644 --- a/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java +++ b/smithy-openapi/src/main/java/software/amazon/smithy/openapi/fromsmithy/OpenApiConverter.java @@ -516,7 +516,7 @@ private void addOperationSecurity( // If the operation explicitly removes authentication, ensure that "security" is set to an empty // list as opposed to simply being unset as unset will result in the operation inheriting global // configuration. - if (shape.getTrait(AuthTrait.class).map(trait -> trait.getValues().isEmpty()).orElse(false)) { + if (shape.getTrait(AuthTrait.class).map(trait -> trait.getValueSet().isEmpty()).orElse(false)) { builder.security(Collections.emptyList()); return; }