From bdcfe21de6c6ff499f7ae579923e61744402ec3c Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Wed, 15 Dec 2021 17:21:40 +0100 Subject: [PATCH 01/10] Allow mixins on all shape types --- designs/mixins.md | 116 ++++++-- .../smithy/model/loader/AstModelLoader.java | 19 +- .../smithy/model/loader/IdlModelParser.java | 66 ++++- .../smithy/model/shapes/CollectionShape.java | 29 +- .../smithy/model/shapes/EntityShape.java | 75 ++++- .../amazon/smithy/model/shapes/MapShape.java | 40 ++- .../smithy/model/shapes/MemberShape.java | 4 +- .../smithy/model/shapes/ModelSerializer.java | 79 +++--- .../model/shapes/NamedMembersShape.java | 5 - .../smithy/model/shapes/OperationShape.java | 52 +++- .../smithy/model/shapes/ResourceShape.java | 15 + .../smithy/model/shapes/ServiceShape.java | 124 ++++++++- .../amazon/smithy/model/shapes/Shape.java | 44 ++- .../shapes/SmithyIdlModelSerializer.java | 40 +-- .../transform/FlattenAndRemoveMixins.java | 5 +- .../amazon/smithy/model/loader/prelude.smithy | 2 +- .../ValidSmithyModelLoaderRunnerTest.java | 1 + .../model/shapes/ModelSerializerTest.java | 27 ++ .../amazon/smithy/model/shapes/ShapeTest.java | 8 - .../model/transform/ModelTransformerTest.java | 5 +- .../loader/invalid/list-empty-members.smithy | 2 +- .../loader/invalid/map-empty-members.smithy | 2 +- .../mixins/invalid-mixins-on-resource.smithy | 5 - .../mixins/invalid-mixins-on-string.smithy | 5 - .../loader/invalid/mixins/mix-types.smithy | 10 + .../mixins/operation-mixin-with-io.smithy | 10 + .../resource-mixin-with-properties.smithy | 12 + .../loader/invalid/set-empty-members.smithy | 2 +- .../mixins/loads-mixins.flattened.smithy | 45 +++ .../loader/valid/mixins/loads-mixins.json | 240 ++++++++++++++++ .../loader/valid/mixins/loads-mixins.smithy | 102 +++++++ .../loader/valid/mixins/mixin-names.json | 40 +++ .../loader/valid/mixins/mixin-names.smithy | 21 ++ .../valid/mixins/operations.flattened.smithy | 14 + .../model/loader/valid/mixins/operations.json | 36 +++ .../loader/valid/mixins/operations.smithy | 19 ++ .../valid/mixins/resources.flattened.smithy | 6 + .../model/loader/valid/mixins/resources.json | 18 ++ .../loader/valid/mixins/resources.smithy | 9 + .../valid/mixins/services.flattened.smithy | 25 ++ .../model/loader/valid/mixins/services.json | 57 ++++ .../model/loader/valid/mixins/services.smithy | 28 ++ .../ast-serialization/cases/data-mixins.json | 258 ++++++++++++++++++ .../cases/operation-mixins.json | 50 ++++ .../cases/resource-mixins.json | 18 ++ .../cases/service-mixins-with-overrides.json | 104 +++++++ .../cases/service-mixins.json | 57 ++++ .../cases/data-mixins.smithy | 106 +++++++ .../cases/operation-mixins.smithy | 27 ++ .../cases/resource-mixins.smithy | 11 + .../cases/service-mixins-with-merging.smithy | 72 +++++ .../cases/service-mixins.smithy | 39 +++ .../amazon/smithy/utils/SimpleParser.java | 6 + 53 files changed, 2069 insertions(+), 143 deletions(-) delete mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/invalid-mixins-on-resource.smithy delete mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/invalid-mixins-on-string.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mix-types.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/operation-mixin-with-io.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/resource-mixin-with-properties.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.flattened.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.flattened.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.flattened.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/data-mixins.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/operation-mixins.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/resource-mixins.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins-with-overrides.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/data-mixins.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/operation-mixins.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/resource-mixins.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins-with-merging.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins.smithy diff --git a/designs/mixins.md b/designs/mixins.md index 77b34777393..96cf8c66625 100644 --- a/designs/mixins.md +++ b/designs/mixins.md @@ -1,7 +1,7 @@ # Smithy Mixins -This proposal defines _mixins_, a modeling mechanism that allows for structure -and union definition reuse. +This proposal defines _mixins_, a modeling mechanism that allows for shape +definition reuse. > Note: mixins will be considered for Smithy IDL 1.1, which treats commas as > optional whitespace. @@ -78,8 +78,8 @@ over time. ## Proposal This proposal introduces *mixins* to reduce the amount of repetition in -structures and unions, reduce copy/paste errors, and provide the ability to -define reusable partial structures and unions. +shapes, reduce copy/paste errors, and provide the ability to define reusable +partial shapes. ### Goals @@ -98,8 +98,7 @@ define reusable partial structures and unions. ### Overview -A mixin is a structure or union marked with the `@mixin` trait -(`smithy.api#mixin`): +A mixin is a shape marked with the `@mixin` trait(`smithy.api#mixin`): ``` $version: "1.1" @@ -114,11 +113,11 @@ structure CityResourceInput { } ``` -Adding a mixin to a structure or union shape causes the members and traits of -a shape to be copied into the shape. Mixins can be added to a shape using +Adding a mixin to a shape causes the members and traits of the other +shape to be copied into the shape. Mixins can be added to a shape using `with` followed by any number of shape IDs. Each shape ID MUST target a -shape marked with the `@mixin` trait. Structure shapes can only use structure -mixins, and union shapes can only use union mixins. +shape marked with the `@mixin` trait. Shapes can only use mixins that +are of the same shape type. ``` structure GetCityInput with CityResourceInput { @@ -173,7 +172,7 @@ structure C { } ``` -Unions can be mixins and use mixins too. +Other shape types can be mixins and use mixins too. ``` @mixin @@ -529,15 +528,22 @@ structure Invalid with ### Mixins in the IDL -To support mixins, `structure_statement` and `union_statement` ABNF rules +To support mixins, shape ABNF rules will be updated to contain an optional `mixins` production -that comes after the shape name and before `{`. Each shape ID referenced in +that comes after the shape name and before any `{`. Each shape ID referenced in the `mixins` production MUST target a shape of the same type as the shape being defined and MUST be marked with the `@mixin` trait. ``` +simple_shape_statement = simple_type_name ws identifier [ws mixins] +list_statement = "list" ws identifier ws [mixins ws] shape_members +set_statement = "set" ws identifier ws [mixins ws] shape_members +map_statement = "map" ws identifier ws [mixins ws] shape_members structure_statement = "structure" ws identifier ws [mixins ws] structure_members union_statement = "union" ws identifier ws [mixins ws] union_members +service_statement = "service" ws identifier ws [mixins ws] node_object +operation_statement = "operation" ws identifier ws [mixins ws] node_object +resource_statement = "resource" ws identifier ws [mixins ws] node_object mixins = "with" 1*(ws shape_id) ``` @@ -547,7 +553,7 @@ mixins = "with" 1*(ws shape_id) Mixins are defined in the JSON AST using the `mixins` property of structure and union shapes. The `mixins` property is a list of objects. To match every other shape target used in the AST, the object supports a single member named -`target` which defines the absolute shape ID of a structure or union marked +`target` which defines the absolute shape ID of a shape marked with the `smithy.api#mixin` trait. ```json @@ -586,7 +592,7 @@ with the `smithy.api#mixin` trait. ### Mixins in selectors A new relationship is introduced to selectors called `mixin` that traverses from -structure and union shapes to every mixin applied to them. The members of each +shapes to every mixin applied to them. The members of each applied mixin are connected to the structure or union through a normal `member` relationship. @@ -692,12 +698,90 @@ The members are ordered as follows: - `sizeFilter` +### Mixins on shapes with non-member properties + +Some shapes don't have members, but do have other properties. Adding a mixin +to such a shape causes the properties of the other shape to be merged into +the shape. Scalar properties defined in the local shape are kept, and +non-scalar properties are merged. When merging map properties, the values for +local keys are kept. The ordering of merged lists / sets follows the +same ordering as members. + +For example, in the following model: + +``` +operation OperationA {} + +@mixin +service A { + version: "A" + operations: [OperationA] +} + +operation OperationB {} + +@mixin +service B with A { + version: "B" + rename: { + "smithy.example#OperationA": "OperA" + "smithy.example#OperationB": "OperB" + } + operations: [OperationB] +} + +operation OperationC {} + +service C with B { + version: "C" + rename: { + "smithy.example#OperationA": "OpA" + "smithy.example#OperationC": "OpC" + } + operations: [OperationC] +} +``` + +The `version` property of the local shape is kept and the `rename` and +`operations` properties are merged. This is equivalent to the following: + +``` +operation OperationA {} + +operation OperationB {} + +operation OperationC {} + +service C { + version: "C" + rename: { + "smithy.example#OperationA": "OpA" + "smithy.example#OperationB": "OperB" + "smithy.example#OperationC": "OpC" + } + operations: [OperationA, OperationB, OperationC] +} +``` + +### Resource mixins + +Resource shapes with the `@mixin` trait MAY NOT define any properties. This is +because every property of a resource shape is intrinsically tied to its set of +identifiers. Changing these identifiers would invalidate every other property +of a given resource. + +### Operation mixins + +Operation shapes with the `@mixin` trait MAY NOT define an `input` or `output` +shape other than `smithy.api#Unit`. This is because allowing input and output +shapes to be shared goes against the goal of the `@input` and `@output` traits. + ### `@mixin` trait The `@mixin` trait is a structured trait defined in the Smithy prelude as: ``` -@trait(selector: ":is(structure, union)") +@trait(selector: ":not(member)") structure mixin { localTraits: LocalMixinTraitList } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/AstModelLoader.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/AstModelLoader.java index e23b8156203..b5481390f3a 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/AstModelLoader.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/AstModelLoader.java @@ -223,6 +223,10 @@ private void loadMember(FullyResolvedModelFile modelFile, ShapeId id, ObjectNode modelFile.onShape(builder); } + private void loadOptionalMember(FullyResolvedModelFile modelFile, ShapeId id, ObjectNode node, String member) { + node.getObjectMember(member).ifPresent(targetNode -> loadMember(modelFile, id, targetNode)); + } + private void loadCollection( ShapeId id, ObjectNode node, @@ -231,16 +235,18 @@ private void loadCollection( ) { LoaderUtils.checkForAdditionalProperties(node, id, COLLECTION_PROPERTY_NAMES, modelFile.events()); applyShapeTraits(id, node, modelFile); - loadMember(modelFile, id.withMember("member"), node.expectObjectMember("member")); + loadOptionalMember(modelFile, id.withMember("member"), node, "member"); modelFile.onShape(builder.id(id).source(node.getSourceLocation())); + addMixins(id, node, modelFile); } private void loadMap(ShapeId id, ObjectNode node, FullyResolvedModelFile modelFile) { LoaderUtils.checkForAdditionalProperties(node, id, MAP_PROPERTY_NAMES, modelFile.events()); - loadMember(modelFile, id.withMember("key"), node.expectObjectMember("key")); - loadMember(modelFile, id.withMember("value"), node.expectObjectMember("value")); + loadOptionalMember(modelFile, id.withMember("key"), node, "key"); + loadOptionalMember(modelFile, id.withMember("value"), node, "value"); applyShapeTraits(id, node, modelFile); modelFile.onShape(MapShape.builder().id(id).source(node.getSourceLocation())); + addMixins(id, node, modelFile); } private void loadOperation(ShapeId id, ObjectNode node, FullyResolvedModelFile modelFile) { @@ -253,6 +259,7 @@ private void loadOperation(ShapeId id, ObjectNode node, FullyResolvedModelFile m loadOptionalTarget(modelFile, id, node, "input").ifPresent(builder::input); loadOptionalTarget(modelFile, id, node, "output").ifPresent(builder::output); modelFile.onShape(builder); + addMixins(id, node, modelFile); } private void loadResource(ShapeId id, ObjectNode node, FullyResolvedModelFile modelFile) { @@ -279,6 +286,7 @@ private void loadResource(ShapeId id, ObjectNode node, FullyResolvedModelFile mo }); modelFile.onShape(builder); + addMixins(id, node, modelFile); } private void loadService(ShapeId id, ObjectNode node, FullyResolvedModelFile modelFile) { @@ -291,6 +299,7 @@ private void loadService(ShapeId id, ObjectNode node, FullyResolvedModelFile mod loadServiceRenameIntoBuilder(builder, node); builder.addErrors(loadOptionalTargetList(modelFile, id, node, ERRORS)); modelFile.onShape(builder); + addMixins(id, node, modelFile); } static void loadServiceRenameIntoBuilder(ServiceShape.Builder builder, ObjectNode node) { @@ -308,6 +317,7 @@ private void loadSimpleShape( LoaderUtils.checkForAdditionalProperties(node, id, SIMPLE_PROPERTY_NAMES, modelFile.events()); applyShapeTraits(id, node, modelFile); modelFile.onShape(builder.id(id).source(node.getSourceLocation())); + addMixins(id, node, modelFile); } private void loadStructure(ShapeId id, ObjectNode node, FullyResolvedModelFile modelFile) { @@ -328,7 +338,10 @@ private void finishLoadingStructOrUnionMembers(ShapeId id, ObjectNode node, Full for (Map.Entry entry : memberObject.getStringMap().entrySet()) { loadMember(modelFile, id.withMember(entry.getKey()), entry.getValue().expectObjectNode()); } + addMixins(id, node, modelFile); + } + private void addMixins(ShapeId id, ObjectNode node, FullyResolvedModelFile modelFile) { ArrayNode mixins = node.getArrayMember(MIXINS).orElse(Node.arrayNode()); for (ObjectNode mixin : mixins.getElementsAs(ObjectNode.class)) { modelFile.addPendingMixin(id, loadReferenceBody(modelFile, id, mixin)); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java index a6630fd91f0..cc99b890398 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java @@ -66,7 +66,6 @@ import software.amazon.smithy.model.traits.TraitFactory; import software.amazon.smithy.model.validation.Severity; import software.amazon.smithy.model.validation.ValidationEvent; -import software.amazon.smithy.model.validation.ValidationUtils; import software.amazon.smithy.model.validation.Validator; import software.amazon.smithy.utils.ListUtils; import software.amazon.smithy.utils.SetUtils; @@ -443,7 +442,6 @@ private void parseShape(List traits) { } addTraits(id, traits); - clearPendingDocs(); ws(); } @@ -454,6 +452,10 @@ private ShapeId parseShapeName() { private void parseSimpleShape(ShapeId id, SourceLocation location, AbstractShapeBuilder builder) { modelFile.onShape(builder.source(location).id(id)); + parseMixins(id); + // Pending docs get cleared by parseMixins. It can't be done here because the + // mixin parser needs to consume whitespace that may include doc comments for + // the next shape in the model. } // See parseMap for information on why members are parsed before the @@ -461,15 +463,14 @@ private void parseSimpleShape(ShapeId id, SourceLocation location, AbstractShape private void parseCollection(ShapeId id, SourceLocation location, CollectionShape.Builder builder) { ws(); builder.id(id).source(location); + parseMixins(id); parseMembers(id, SetUtils.of("member")); modelFile.onShape(builder.id(id)); + clearPendingDocs(); } private void parseMembers(ShapeId id, Set requiredMembers) { Set definedMembers = new HashSet<>(); - Set remaining = requiredMembers.isEmpty() - ? requiredMembers - : new HashSet<>(requiredMembers); ws(); expect('{'); @@ -480,7 +481,7 @@ private void parseMembers(ShapeId id, Set requiredMembers) { break; } - parseMember(id, requiredMembers, definedMembers, remaining); + parseMember(id, requiredMembers, definedMembers); // Clears out any previously captured documentation // comments that may have been found when parsing the member. @@ -493,15 +494,14 @@ private void parseMembers(ShapeId id, Set requiredMembers) { expect('}'); } - if (!remaining.isEmpty()) { - throw syntax(id, "Missing required members of shape `" + id + "`: [" - + ValidationUtils.tickedList(remaining) + ']'); - } - expect('}'); } - private void parseMember(ShapeId parent, Set required, Set defined, Set remaining) { + private void parseMember( + ShapeId parent, + Set allowed, + Set defined + ) { // Parse optional member traits. List memberTraits = parseDocsAndTraits(); SourceLocation memberLocation = currentLocation(); @@ -513,10 +513,9 @@ private void parseMember(ShapeId parent, Set required, Set defin } defined.add(memberName); - remaining.remove(memberName); // Only enforce "allowedMembers" if it isn't empty. - if (!required.isEmpty() && !required.contains(memberName)) { + if (!allowed.isEmpty() && !allowed.contains(memberName)) { throw syntax(parent, "Unexpected member of " + parent + ": '" + memberName + '\''); } @@ -545,8 +544,10 @@ private void parseMapStatement(ShapeId id, SourceLocation location) { // on a builder. This does not suffer the same error messages as // structures/unions because list/set/map have a fixed and required // set of members that must be provided. + parseMixins(id); parseMembers(id, SetUtils.of("key", "value")); modelFile.onShape(MapShape.builder().id(id).source(location)); + clearPendingDocs(); } private void parseStructuredShape( @@ -566,9 +567,13 @@ private void parseStructuredShape( // Parse optional "with" statements to add mixins, but only if it's supported by the version. parseMixins(id); parseMembers(id, Collections.emptySet()); + clearPendingDocs(); } private void parseMixins(ShapeId id) { + // This is fine for now, but if we ever add any other keywords that start with + // 'w' then we'll need to peek farther. + ws(); if (peek() != 'w') { return; } @@ -585,13 +590,39 @@ private void parseMixins(ShapeId id) { ws(); do { + clearPendingDocs(); String target = ParserUtils.parseShapeId(this); modelFile.addForwardReference(target, resolved -> modelFile.addPendingMixin(id, resolved)); ws(); - } while (peek() != '{'); + } while (!peekEndWith()); + } + + private boolean peekEndWith() { + if (peek() == '{' || peek() == '@' || eof()) { + return true; + } + + // We could make this more efficient with a prefix trie + for (String shapeType : SHAPE_TYPES) { + if (peekString(shapeType)) { + return true; + } + } + + return false; + } + + private boolean peekString(String possibility) { + for (int i = 0; i < possibility.length(); i++) { + if (peek(i) != possibility.charAt(i)) { + return false; + } + } + return !ParserUtils.isValidIdentifierCharacter(peek(possibility.length())); } private void parseOperationStatement(ShapeId id, SourceLocation location) { + parseMixins(id); OperationShape.Builder builder = OperationShape.builder().id(id).source(location); parseProperties(id, propertyName -> { switch (propertyName) { @@ -611,6 +642,7 @@ private void parseOperationStatement(ShapeId id, SourceLocation location) { } }); modelFile.onShape(builder); + clearPendingDocs(); } private void parseProperties(ShapeId id, Consumer valueParser) { @@ -691,6 +723,7 @@ private void parseIdList(Consumer consumer) { private void parseServiceStatement(ShapeId id, SourceLocation location) { ws(); + parseMixins(id); ServiceShape.Builder builder = new ServiceShape.Builder().id(id).source(location); ObjectNode shapeNode = IdlNodeParser.parseObjectNode(this, id.toString()); LoaderUtils.checkForAdditionalProperties(shapeNode, id, SERVICE_PROPERTY_NAMES, modelFile.events()); @@ -700,6 +733,7 @@ private void parseServiceStatement(ShapeId id, SourceLocation location) { optionalIdList(shapeNode, RESOURCES_KEY, builder::addResource); optionalIdList(shapeNode, ERRORS_KEYS, builder::addError); AstModelLoader.loadServiceRenameIntoBuilder(builder, shapeNode); + clearPendingDocs(); } private void optionalId(ObjectNode node, String name, Consumer consumer) { @@ -719,6 +753,7 @@ private void optionalIdList(ObjectNode node, String name, Consumer cons private void parseResourceStatement(ShapeId id, SourceLocation location) { ws(); + parseMixins(id); ResourceShape.Builder builder = ResourceShape.builder().id(id).source(location); modelFile.onShape(builder); ObjectNode shapeNode = IdlNodeParser.parseObjectNode(this, id.toString()); @@ -742,6 +777,7 @@ private void parseResourceStatement(ShapeId id, SourceLocation location) { modelFile.addForwardReference(target.getValue(), targetId -> builder.addIdentifier(name, targetId)); } }); + clearPendingDocs(); } // "//" *(not_newline) diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/CollectionShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/CollectionShape.java index 9f2d1c8befb..3635f7ef7b1 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/CollectionShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/CollectionShape.java @@ -19,7 +19,8 @@ import java.util.Collections; import java.util.Objects; import java.util.function.Consumer; -import software.amazon.smithy.utils.SmithyBuilder; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.traits.Trait; /** * Abstract class representing Set and List shapes. @@ -28,9 +29,9 @@ public abstract class CollectionShape extends Shape { private final MemberShape member; - CollectionShape(Builder builder) { + CollectionShape(Builder builder) { super(builder, false); - member = SmithyBuilder.requiredState("member", builder.member); + member = builder.member != null ? builder.member : getRequiredMixinMember(builder, "member"); ShapeId expected = getId().withMember("member"); if (!member.getId().equals(expected)) { throw new IllegalArgumentException(String.format( @@ -124,5 +125,27 @@ public B member(ShapeId target, Consumer memberUpdater) { public final B addMember(MemberShape member) { return member(member); } + + @Override + public B flattenMixins() { + for (Shape mixin : getMixins().values()) { + SourceLocation location = getSourceLocation(); + Collection localTraits = Collections.emptyList(); + MemberShape mixinMember = ((CollectionShape) mixin).getMember(); + MemberShape existing = member; + if (existing != null) { + localTraits = existing.getIntroducedTraits().values(); + location = existing.getSourceLocation(); + } + member = MemberShape.builder() + .id(getId().withMember(mixinMember.getMemberName())) + .target(mixinMember.getTarget()) + .addTraits(mixinMember.getAllTraits().values()) + .addTraits(localTraits) + .source(location) + .build(); + } + return super.flattenMixins(); + } } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java index 0b04993c2f7..4d2173e840c 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java @@ -16,7 +16,9 @@ package software.amazon.smithy.model.shapes; import java.util.Collection; +import java.util.Collections; import java.util.Set; +import java.util.TreeSet; import software.amazon.smithy.utils.BuilderRef; /** @@ -25,12 +27,38 @@ public abstract class EntityShape extends Shape { private final Set resources; + private final Set introducedResources; private final Set operations; + private final Set introducedOperations; EntityShape(Builder builder) { super(builder, false); - resources = builder.resources.copy(); - operations = builder.operations.copy(); + + if (getMixins().isEmpty()) { + resources = builder.resources.copy(); + introducedResources = resources; + operations = builder.operations.copy(); + introducedOperations = operations; + } else { + Set computedResources = new TreeSet<>(); + Set computedOperations = new TreeSet<>(); + + for (Shape shape : builder.getMixins().values()) { + // validateMixins should have already assured that this is an EntityShape. + EntityShape mixin = (EntityShape) shape; + computedResources.addAll(mixin.getResources()); + computedOperations.addAll(mixin.getOperations()); + } + + introducedResources = builder.resources.copy(); + introducedOperations = builder.operations.copy(); + + computedResources.addAll(introducedResources); + computedOperations.addAll(introducedOperations); + + resources = Collections.unmodifiableSet(computedResources); + operations = Collections.unmodifiableSet(computedOperations); + } } /** @@ -40,6 +68,16 @@ public final Set getResources() { return resources; } + /** + * Gets all the directly-bound resources introduced by this shape and + * not inherited from mixins. + * + * @return Gets the introduced resources directly-bound to the shape. + */ + public final Set getIntroducedResources() { + return introducedResources; + } + /** * Gets operations bound only through the "operations" property. * @@ -55,6 +93,20 @@ public final Set getOperations() { return operations; } + /** + * Gets operations bound through the "operations" property that + * were not inherited from mixins. + * + *

This will not include operations bound to resources using + * a lifecycle operation binding. This will also not include + * operations bound to this entity through sub-resources. + * + * @return Gets the introduced operations. + */ + public final Set getIntroducedOperations() { + return introducedOperations; + } + /** * Get all operations directly bound to this shape. * @@ -147,5 +199,24 @@ public B clearResources() { resources.clear(); return (B) this; } + + @Override + public B flattenMixins() { + Set flatResources = new TreeSet<>(); + Set flatOperations = new TreeSet<>(); + + for (Shape shape : getMixins().values()) { + EntityShape mixin = (EntityShape) shape; + flatResources.addAll(mixin.getResources()); + flatOperations.addAll(mixin.getOperations()); + } + + flatResources.addAll(resources.peek()); + flatOperations.addAll(operations.peek()); + resources(flatResources); + operations(flatOperations); + + return super.flattenMixins(); + } } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/MapShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/MapShape.java index 109972573ef..cfae051b535 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/MapShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/MapShape.java @@ -16,11 +16,13 @@ package software.amazon.smithy.model.shapes; import java.util.Collection; +import java.util.Collections; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; +import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.traits.Trait; import software.amazon.smithy.utils.ListUtils; -import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.ToSmithyBuilder; /** @@ -33,8 +35,8 @@ public final class MapShape extends Shape implements ToSmithyBuilder { private MapShape(Builder builder) { super(builder, false); - key = SmithyBuilder.requiredState("key", builder.key); - value = SmithyBuilder.requiredState("value", builder.value); + key = builder.key != null ? builder.key : getRequiredMixinMember(builder, "key"); + value = builder.value != null ? builder.value : getRequiredMixinMember(builder, "value"); validateMemberShapeIds(); } @@ -208,5 +210,37 @@ public Builder value(ShapeId target, Consumer memberUpdater return value(builder.build()); } + + @Override + public Builder flattenMixins() { + for (Shape mixin : getMixins().values()) { + for (MemberShape member : mixin.members()) { + SourceLocation location = getSourceLocation(); + Collection localTraits = Collections.emptyList(); + + MemberShape existing; + if (member.getMemberName().equals("key")) { + existing = key; + } else { + existing = value; + } + + if (existing != null) { + localTraits = existing.getIntroducedTraits().values(); + location = existing.getSourceLocation(); + } + + addMember(MemberShape.builder() + .id(getId().withMember(member.getMemberName())) + .target(member.getTarget()) + .addTraits(member.getAllTraits().values()) + .addTraits(localTraits) + .source(location) + .build()); + } + } + + return super.flattenMixins(); + } } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/MemberShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/MemberShape.java index 8034d7876b6..23d16764751 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/MemberShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/MemberShape.java @@ -15,7 +15,7 @@ package software.amazon.smithy.model.shapes; -import java.util.Collection; +import java.util.Map; import java.util.Optional; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceException; @@ -39,7 +39,7 @@ private MemberShape(Builder builder) { } @Override - protected void validateMixins(Collection mixins) { + protected void validateMixins(Map mixins, Map introducedTraits) { // This can only happen by manipulating the semantic model in code. if (mixins.size() > 1) { throw new SourceException("Members must not have more than one mixin: " + getId(), this); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java index a2435e82ddf..0c31c645932 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java @@ -206,12 +206,22 @@ private final class ShapeSerializer extends ShapeVisitor.Default { private final Set mixinMemberTraits = new TreeSet<>(); private ObjectNode.Builder createTypedBuilder(Shape shape) { - return Node.objectNodeBuilder() + ObjectNode.Builder builder = Node.objectNodeBuilder() .withMember("type", Node.from(shape.getType().toString())); + + if (!shape.getMixins().isEmpty()) { + List mixins = new ArrayList<>(shape.getMixins().size()); + for (ShapeId mixin : shape.getMixins()) { + mixins.add(serializeReference(mixin)); + } + builder.withMember("mixins", Node.fromNodes(mixins)); + } + + return builder; } private ObjectNode.Builder serializeAllTraits(Shape shape, ObjectNode.Builder builder) { - return serializeTraits(builder, shape.getAllTraits().values()); + return serializeTraits(builder, shape.getIntroducedTraits().values()); } @Override @@ -221,24 +231,34 @@ protected ObjectNode getDefault(Shape shape) { @Override public Node listShape(ListShape shape) { - return serializeAllTraits(shape, createTypedBuilder(shape) - .withMember("member", shape.getMember().accept(this))) - .build(); + return collectionShape(shape); } @Override public Node setShape(SetShape shape) { - return serializeAllTraits(shape, createTypedBuilder(shape) - .withMember("member", shape.getMember().accept(this))) - .build(); + return collectionShape(shape); + } + + public Node collectionShape(CollectionShape shape) { + ObjectNode.Builder result = createTypedBuilder(shape); + mixinMember(result, shape.getMember(), "member"); + return serializeAllTraits(shape, result).build(); + } + + private void mixinMember(ObjectNode.Builder builder, MemberShape member, String key) { + if (member.getMixins().isEmpty()) { + builder.withMember(key, member.accept(this)); + } else if (!member.getIntroducedTraits().isEmpty()) { + mixinMemberTraits.add(member); + } } @Override public Node mapShape(MapShape shape) { - return serializeAllTraits(shape, createTypedBuilder(shape) - .withMember("key", shape.getKey().accept(this)) - .withMember("value", shape.getValue().accept(this))) - .build(); + ObjectNode.Builder result = createTypedBuilder(shape); + mixinMember(result, shape.getKey(), "key"); + mixinMember(result, shape.getValue(), "value"); + return serializeAllTraits(shape, result).build(); } @Override @@ -246,7 +266,7 @@ public Node operationShape(OperationShape shape) { return serializeAllTraits(shape, createTypedBuilder(shape) .withMember("input", serializeReference(shape.getInputShape())) .withMember("output", serializeReference(shape.getOutputShape())) - .withOptionalMember("errors", createOptionalIdList(shape.getErrors()))) + .withOptionalMember("errors", createOptionalIdList(shape.getIntroducedErrors()))) .build(); } @@ -268,9 +288,9 @@ public Node resourceShape(ResourceShape shape) { .withOptionalMember("update", shape.getUpdate().map(this::serializeReference)) .withOptionalMember("delete", shape.getDelete().map(this::serializeReference)) .withOptionalMember("list", shape.getList().map(this::serializeReference)) - .withOptionalMember("operations", createOptionalIdList(shape.getOperations())) + .withOptionalMember("operations", createOptionalIdList(shape.getIntroducedOperations())) .withOptionalMember("collectionOperations", createOptionalIdList(shape.getCollectionOperations())) - .withOptionalMember("resources", createOptionalIdList(shape.getResources()))) + .withOptionalMember("resources", createOptionalIdList(shape.getIntroducedResources()))) .build(); } @@ -278,17 +298,17 @@ public Node resourceShape(ResourceShape shape) { public Node serviceShape(ServiceShape shape) { ObjectNode.Builder serviceBuilder = createTypedBuilder(shape); - if (!StringUtils.isBlank(shape.getVersion())) { - serviceBuilder.withMember("version", Node.from(shape.getVersion())); + if (!StringUtils.isBlank(shape.getIntroducedVersion())) { + serviceBuilder.withMember("version", Node.from(shape.getIntroducedVersion())); } - serviceBuilder.withOptionalMember("operations", createOptionalIdList(shape.getOperations())); - serviceBuilder.withOptionalMember("resources", createOptionalIdList(shape.getResources())); - serviceBuilder.withOptionalMember("errors", createOptionalIdList(shape.getErrors())); + serviceBuilder.withOptionalMember("operations", createOptionalIdList(shape.getIntroducedOperations())); + serviceBuilder.withOptionalMember("resources", createOptionalIdList(shape.getIntroducedResources())); + serviceBuilder.withOptionalMember("errors", createOptionalIdList(shape.getIntroducedErrors())); - if (!shape.getRename().isEmpty()) { + if (!shape.getIntroducedRename().isEmpty()) { ObjectNode.Builder renameBuilder = Node.objectNodeBuilder(); - for (Map.Entry entry : shape.getRename().entrySet()) { + for (Map.Entry entry : shape.getIntroducedRename().entrySet()) { renameBuilder.withMember(entry.getKey().toString(), entry.getValue()); } serviceBuilder.withMember("rename", renameBuilder.build()); @@ -323,22 +343,9 @@ public Node unionShape(UnionShape shape) { private ObjectNode createStructureAndUnion(Shape shape, Map members) { ObjectNode.Builder result = createTypedBuilder(shape); - if (!shape.getMixins().isEmpty()) { - List mixins = new ArrayList<>(shape.getMixins().size()); - for (ShapeId mixin : shape.getMixins()) { - mixins.add(serializeReference(mixin)); - } - result.withMember("mixins", Node.fromNodes(mixins)); - } - ObjectNode.Builder membersBuilder = ObjectNode.objectNodeBuilder(); for (MemberShape member : members.values()) { - if (member.getMixins().isEmpty()) { - membersBuilder.withMember(member.getMemberName(), member.accept(this)); - } else if (!member.getIntroducedTraits().isEmpty()) { - // There are traits that need to be added to inherited members. - mixinMemberTraits.add(member); - } + mixinMember(membersBuilder, member, member.getMemberName()); } result.withMember("members", membersBuilder.build()); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/NamedMembersShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/NamedMembersShape.java index 3bd2f0df6cb..ee0748ef657 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/NamedMembersShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/NamedMembersShape.java @@ -86,11 +86,6 @@ public abstract class NamedMembersShape extends Shape { validateMemberShapeIds(); } - @Override - protected void validateMixins(Collection mixins) { - // do nothing. Mixins are allowed on structures and unions. - } - /** * Gets the members of the shape, including mixin members. * diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java index 489c73b8b43..d2966c2085b 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java @@ -17,12 +17,14 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.Set; import software.amazon.smithy.model.knowledge.OperationIndex; +import software.amazon.smithy.model.traits.MixinTrait; import software.amazon.smithy.model.traits.UnitTypeTrait; import software.amazon.smithy.utils.BuilderRef; import software.amazon.smithy.utils.ToSmithyBuilder; @@ -34,12 +36,37 @@ public final class OperationShape extends Shape implements ToSmithyBuilder errors; + private final List introducedErrors; private OperationShape(Builder builder) { super(builder, false); - errors = builder.errors.copy(); + input = Objects.requireNonNull(builder.input); output = Objects.requireNonNull(builder.output); + + if (getMixins().isEmpty()) { + errors = builder.errors.copy(); + introducedErrors = errors; + } else { + // Compute mixin properties of the operation. Input / output are + // forbidden in operation mixins, so we don't bother with them + // here. + Set computedErrors = new LinkedHashSet<>(); + for (Shape shape : builder.getMixins().values()) { + shape.asOperationShape().ifPresent(mixin -> computedErrors.addAll(mixin.getErrors())); + } + introducedErrors = builder.errors.copy(); + computedErrors.addAll(introducedErrors); + errors = Collections.unmodifiableList(new ArrayList<>(computedErrors)); + } + + if (hasTrait(MixinTrait.ID) && (!input.equals(UnitTypeTrait.UNIT) || !output.equals(UnitTypeTrait.UNIT))) { + throw new IllegalStateException(String.format( + "Operation shapes with the mixin trait MUST target `%s` for their input and output. Operation " + + "mixin shape `%s` defines one or both of these properties.", + UnitTypeTrait.UNIT, getId() + )); + } } public static Builder builder() { @@ -51,7 +78,7 @@ public Builder toBuilder() { return updateBuilder(builder()) .input(input) .output(output) - .errors(errors); + .errors(getIntroducedErrors()); } @Override @@ -138,6 +165,16 @@ public List getErrors() { return errors; } + /** + * Gets the errors introduced by the shape and not inherited + * from mixins. + * + * @return Returns the introduced errors. + */ + public List getIntroducedErrors() { + return introducedErrors; + } + /** *

Gets a list of the error shape IDs the operation can encounter, * including any common errors of a service. @@ -270,5 +307,16 @@ public Builder clearErrors() { public OperationShape build() { return new OperationShape(this); } + + @Override + public Builder flattenMixins() { + Set computedErrors = new LinkedHashSet<>(); + for (Shape shape : getMixins().values()) { + shape.asOperationShape().ifPresent(mixin -> computedErrors.addAll(mixin.getErrors())); + } + computedErrors.addAll(errors.peek()); + errors(computedErrors); + return super.flattenMixins(); + } } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ResourceShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ResourceShape.java index 5946b667987..51ca9f6fbf8 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ResourceShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ResourceShape.java @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import software.amazon.smithy.model.traits.MixinTrait; import software.amazon.smithy.utils.BuilderRef; import software.amazon.smithy.utils.ToSmithyBuilder; @@ -59,6 +60,20 @@ private ResourceShape(Builder builder) { getUpdate().ifPresent(allOperations::add); getDelete().ifPresent(allOperations::add); getList().ifPresent(allOperations::add); + + if (hasTrait(MixinTrait.ID) && (!getIdentifiers().isEmpty() + || getPut().isPresent() + || getCreate().isPresent() + || getRead().isPresent() + || getUpdate().isPresent() + || getDelete().isPresent() + || getList().isPresent() + || !getOperations().isEmpty() + || !getResources().isEmpty())) { + throw new IllegalStateException(String.format( + "Resource shapes with the mixin trait may not define any properties. Resource mixin shape `%s` " + + "defines one or more properties.", getId())); + } } public static Builder builder() { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java index 489e2bd7091..50234bca8d3 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java @@ -15,11 +15,16 @@ package software.amazon.smithy.model.shapes; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import software.amazon.smithy.utils.BuilderRef; import software.amazon.smithy.utils.ToSmithyBuilder; @@ -29,14 +34,52 @@ public final class ServiceShape extends EntityShape implements ToSmithyBuilder { private final String version; + private final String introducedVersion; private final Map rename; + private final Map introducedRename; private final List errors; + private final List introducedErrors; private ServiceShape(Builder builder) { super(builder); - version = builder.version; - rename = builder.rename.copy(); - errors = builder.errors.copy(); + + if (getMixins().isEmpty()) { + version = builder.version; + introducedVersion = version; + rename = builder.rename.copy(); + introducedRename = rename; + errors = builder.errors.copy(); + introducedErrors = errors; + } else { + String computedVersion = ""; + Map computedRename = new HashMap<>(); + Set computedErrors = new LinkedHashSet<>(); + + for (Shape shape : builder.getMixins().values()) { + if (shape.isServiceShape()) { + ServiceShape mixin = shape.asServiceShape().get(); + if (!mixin.version.isEmpty()) { + computedVersion = mixin.version; + } + computedRename.putAll(mixin.getRename()); + computedErrors.addAll(mixin.getErrors()); + } + } + + introducedVersion = builder.version; + introducedRename = builder.rename.copy(); + introducedErrors = builder.errors.copy(); + + if (!introducedVersion.isEmpty()) { + computedVersion = introducedVersion; + } + computedRename.putAll(introducedRename); + computedErrors.addAll(introducedErrors); + + version = computedVersion; + rename = Collections.unmodifiableMap(computedRename); + errors = Collections.unmodifiableList(new ArrayList<>(computedErrors)); + } } public static Builder builder() { @@ -46,11 +89,11 @@ public static Builder builder() { @Override public Builder toBuilder() { return updateBuilder(builder()) - .version(version) - .errors(errors) - .rename(rename) - .operations(getOperations()) - .resources(getResources()); + .version(introducedVersion) + .errors(introducedErrors) + .rename(introducedRename) + .operations(getIntroducedOperations()) + .resources(getIntroducedResources()); } @Override @@ -85,6 +128,17 @@ public String getVersion() { return version; } + /** + * Gets the version of the service introduced by the shape and not + * inherited from mixins. An empty string is returned if the version + * is undefined. + * + * @return The introduced version of the service. + */ + public String getIntroducedVersion() { + return introducedVersion; + } + /** * @return The rename map of the service. */ @@ -92,6 +146,16 @@ public Map getRename() { return rename; } + /** + * Gets the rename map introduced by the shape and not inherited + * from mixins. + * + * @return The introduced rename map of the service. + */ + public Map getIntroducedRename() { + return introducedRename; + } + /** *

Gets a list of the common errors that can be encountered by * every operation in the service.

@@ -106,6 +170,21 @@ public List getErrors() { return errors; } + /** + * Gets the list of common errors introduced by the shape and not + * inherited from mixins. These errors can be encountered by every + * operation in the service. + * + * Each returned {@link ShapeId} must resolve to a + * {@link StructureShape} that is targeted by an error trait; however, + * this is only guaranteed after a model is validated. + * + * @return Returns the introduced service errors. + */ + public List getIntroducedErrors() { + return introducedErrors; + } + /** * Gets the contextual name of a shape within the closure. * @@ -237,5 +316,34 @@ public Builder clearErrors() { errors.clear(); return this; } + + @Override + public Builder flattenMixins() { + String flatVersion = version; + Map flatRename = new HashMap<>(); + Set flatErrors = new LinkedHashSet<>(); + + for (Shape shape : getMixins().values()) { + if (shape.isServiceShape()) { + ServiceShape mixin = shape.asServiceShape().get(); + if (!mixin.version.isEmpty()) { + flatVersion = mixin.version; + } + flatRename.putAll(mixin.getRename()); + flatErrors.addAll(mixin.getErrors()); + } + } + + if (!version.isEmpty()) { + flatVersion = version; + } + flatRename.putAll(rename.peek()); + flatErrors.addAll(errors.peek()); + + version = flatVersion; + rename(flatRename); + errors(flatErrors); + return super.flattenMixins(); + } } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java index cffff3156ad..a59c7c892c3 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.TreeSet; import software.amazon.smithy.model.FromSourceLocation; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceException; @@ -75,7 +76,7 @@ public abstract class Shape implements FromSourceLocation, Tagged, ToShapeId, Co // Simple case when there are no mixins. traits = introducedTraits; } else { - validateMixins(mixins.keySet()); + validateMixins(mixins, introducedTraits); // Compute mixin traits. Map computedTraits = new HashMap<>(); for (Shape shape : mixins.values()) { @@ -88,10 +89,43 @@ public abstract class Shape implements FromSourceLocation, Tagged, ToShapeId, Co } } - protected void validateMixins(Collection mixins) { - // This method is overridden in subclasses that allow mixins (structure, union, member). - // This situation can only happen by manipulating the semantic model in code. - throw new SourceException("Mixins are not allowed on " + getId() + ", a " + getType(), this); + protected void validateMixins(Map mixins, Map introducedTraits) { + Set invalid = new TreeSet<>(); + for (Shape mixin : mixins.values()) { + if (mixin.getType() != getType()) { + invalid.add(mixin.getId().toString()); + } + } + if (!invalid.isEmpty()) { + String invalidList = String.join("`, `", invalid); + throw new IllegalStateException(String.format( + "Mixins may only be mixed into shapes of the same type. The following mixins were applied to the " + + "%s shape `%s` which are not %1$s shapes: [`%s`]", getType(), getId(), invalidList)); + } + } + + protected MemberShape getRequiredMixinMember( + AbstractShapeBuilder builder, + String name + ) { + MemberShape mixedMember = null; + for (Shape shape : builder.getMixins().values()) { + for (MemberShape member : shape.members()) { + if (member.getMemberName().equals(name)) { + mixedMember = MemberShape.builder() + .id(getId().withMember(name)) + .target(member.getTarget()) + .source(getSourceLocation()) + .addMixin(member) + .build(); + break; + } + } + } + if (mixedMember == null) { + throw new IllegalStateException(String.format("Missing required member of shape `%s`: %s", getId(), name)); + } + return mixedMember; } /** diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java index 80c63b42f68..a3a1b586ff3 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java @@ -389,7 +389,9 @@ private static final class ShapeSerializer extends ShapeVisitor.Default { @Override protected Void getDefault(Shape shape) { serializeTraits(shape); - codeWriter.write("$L $L", shape.getType(), shape.getId().getName()).write(""); + codeWriter.writeInline("$L $L ", shape.getType(), shape.getId().getName()); + writeMixins(shape, false); + codeWriter.write("").write(""); return null; } @@ -404,7 +406,7 @@ private void shapeWithMembers(Shape shape, List members) { } } - serializeTraits(shape.getIntroducedTraits()); + serializeTraits(shape); codeWriter.writeInline("$L $L ", shape.getType(), shape.getId().getName()); writeMixins(shape, !nonMixinMembers.isEmpty()); @@ -459,7 +461,7 @@ private void applyIntroducedTraits(Collection members) { } private void serializeTraits(Shape shape) { - serializeTraits(shape.getAllTraits()); + serializeTraits(shape.getIntroducedTraits()); } private void serializeTraits(Map traits, TraitFeature... traitFeatures) { @@ -544,18 +546,20 @@ public Void unionShape(UnionShape shape) { @Override public Void serviceShape(ServiceShape shape) { serializeTraits(shape); - codeWriter.openBlock("service $L {", shape.getId().getName()); + codeWriter.writeInline("service $L ", shape.getId().getName()); + writeMixins(shape, false); + codeWriter.openBlock("{"); - if (!StringUtils.isBlank(shape.getVersion())) { - codeWriter.write("version: $S", shape.getVersion()); + if (!StringUtils.isBlank(shape.getIntroducedVersion())) { + codeWriter.write("version: $S", shape.getIntroducedVersion()); } - codeWriter.writeOptionalIdList("operations", shape.getOperations()); - codeWriter.writeOptionalIdList("resources", shape.getResources()); - codeWriter.writeOptionalIdList("errors", shape.getErrors()); - if (!shape.getRename().isEmpty()) { + codeWriter.writeOptionalIdList("operations", shape.getIntroducedOperations()); + codeWriter.writeOptionalIdList("resources", shape.getIntroducedResources()); + codeWriter.writeOptionalIdList("errors", shape.getIntroducedErrors()); + if (!shape.getIntroducedRename().isEmpty()) { codeWriter.openBlock("rename: {", "}", () -> { - for (Map.Entry entry : shape.getRename().entrySet()) { + for (Map.Entry entry : shape.getIntroducedRename().entrySet()) { codeWriter.write("$S: $S", entry.getKey(), entry.getValue()); } }); @@ -567,7 +571,9 @@ public Void serviceShape(ServiceShape shape) { @Override public Void resourceShape(ResourceShape shape) { serializeTraits(shape); - codeWriter.openBlock("resource $L {", shape.getId().getName()); + codeWriter.writeInline("resource $L ", shape.getId().getName()); + writeMixins(shape, false); + codeWriter.openBlock("{"); if (!shape.getIdentifiers().isEmpty()) { codeWriter.openBlock("identifiers: {"); shape.getIdentifiers().entrySet().stream() @@ -583,9 +589,9 @@ public Void resourceShape(ResourceShape shape) { shape.getUpdate().ifPresent(shapeId -> codeWriter.write("update: $I", shapeId)); shape.getDelete().ifPresent(shapeId -> codeWriter.write("delete: $I", shapeId)); shape.getList().ifPresent(shapeId -> codeWriter.write("list: $I", shapeId)); - codeWriter.writeOptionalIdList("operations", shape.getOperations()); + codeWriter.writeOptionalIdList("operations", shape.getIntroducedOperations()); codeWriter.writeOptionalIdList("collectionOperations", shape.getCollectionOperations()); - codeWriter.writeOptionalIdList("resources", shape.getResources()); + codeWriter.writeOptionalIdList("resources", shape.getIntroducedResources()); codeWriter.closeBlock("}"); codeWriter.write(""); @@ -595,11 +601,13 @@ public Void resourceShape(ResourceShape shape) { @Override public Void operationShape(OperationShape shape) { serializeTraits(shape); - codeWriter.openBlock("operation $L {", shape.getId().getName()); + codeWriter.writeInline("operation $L ", shape.getId().getName()); + writeMixins(shape, false); + codeWriter.openBlock("{"); List mixinMembers = new ArrayList<>(); mixinMembers.addAll(writeInlineableProperty("input", shape.getInputShape(), InputTrait.ID)); mixinMembers.addAll(writeInlineableProperty("output", shape.getOutputShape(), OutputTrait.ID)); - codeWriter.writeOptionalIdList("errors", shape.getErrors()); + codeWriter.writeOptionalIdList("errors", shape.getIntroducedErrors()); codeWriter.closeBlock("}"); codeWriter.write(""); applyIntroducedTraits(mixinMembers); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/FlattenAndRemoveMixins.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FlattenAndRemoveMixins.java index fc3787c9c36..a1e1702f7a9 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/transform/FlattenAndRemoveMixins.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/FlattenAndRemoveMixins.java @@ -17,11 +17,8 @@ import java.util.ArrayList; import java.util.List; -import java.util.stream.Stream; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.shapes.Shape; -import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.shapes.UnionShape; import software.amazon.smithy.model.traits.MixinTrait; /** @@ -32,7 +29,7 @@ Model transform(ModelTransformer transformer, Model model) { List updatedShapes = new ArrayList<>(); List toRemove = new ArrayList<>(); - Stream.concat(model.shapes(StructureShape.class), model.shapes(UnionShape.class)).forEach(shape -> { + model.shapes().forEach(shape -> { if (shape.hasTrait(MixinTrait.class)) { toRemove.add(shape); } else if (!shape.getMixins().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 ca19c522421..3523787324c 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 @@ -762,7 +762,7 @@ structure output {} structure unitType {} /// Makes a structure or union a mixin. -@trait(selector: ":is(structure, union)") +@trait(selector: ":not(member)") structure mixin { localTraits: LocalMixinTraitList } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/loader/ValidSmithyModelLoaderRunnerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/loader/ValidSmithyModelLoaderRunnerTest.java index d825731396f..a511458efdc 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/loader/ValidSmithyModelLoaderRunnerTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/loader/ValidSmithyModelLoaderRunnerTest.java @@ -28,6 +28,7 @@ import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.ModelSerializer; +import software.amazon.smithy.model.shapes.Shape; /** * Loads all of the ".smithy" files in idl/valid. diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ModelSerializerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ModelSerializerTest.java index ccd6cff5b1d..ef4d78c59d2 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ModelSerializerTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ModelSerializerTest.java @@ -24,8 +24,18 @@ import static org.hamcrest.Matchers.startsWith; import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DynamicTest; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestFactory; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceLocation; import software.amazon.smithy.model.node.Node; @@ -34,8 +44,25 @@ import software.amazon.smithy.model.traits.DocumentationTrait; import software.amazon.smithy.model.traits.SensitiveTrait; import software.amazon.smithy.model.traits.synthetic.OriginalShapeIdTrait; +import software.amazon.smithy.utils.IoUtils; public class ModelSerializerTest { + @TestFactory + public Stream generateTests() throws IOException, URISyntaxException { + return Files.list(Paths.get( + SmithyIdlModelSerializer.class.getResource("ast-serialization/cases").toURI())) + .map(path -> DynamicTest.dynamicTest(path.getFileName().toString(), () -> testRoundTrip(path))); + } + + public void testRoundTrip(Path path) { + Model model = Model.assembler().addImport(path).assemble().unwrap(); + ModelSerializer serializer = ModelSerializer.builder().build(); + ObjectNode actual = serializer.serialize(model); + ObjectNode expected = Node.parse(IoUtils.readUtf8File(path)).expectObjectNode(); + + Node.assertEquals(actual, expected); + } + @Test public void serializesModels() { Model model = Model.assembler() diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ShapeTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ShapeTest.java index 026382ba001..b259b7f2881 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ShapeTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ShapeTest.java @@ -252,12 +252,4 @@ public void validatesMemberShapeIds() { .build(); }); } - - @Test - public void membersCannotBeAddedToSomeShapes() { - Assertions.assertThrows(SourceException.class, () -> { - StringShape mixin = StringShape.builder().id("ns.foo#string").build(); - StringShape.builder().id("ns.foo#bar").addMixin(mixin).build(); - }); - } } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java index 075f7196af8..31e71a2fc57 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java @@ -169,7 +169,10 @@ public static String[] flattenShapesData() { "loads-mixins", "mixins-with-members", "mixins-with-members-and-traits", - "mixins-with-mixin-local-traits" + "mixins-with-mixin-local-traits", + "operations", + "resources", + "services" }; } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/list-empty-members.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/list-empty-members.smithy index 99e9ec77621..c7d7fafdba2 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/list-empty-members.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/list-empty-members.smithy @@ -1,4 +1,4 @@ -// Parse error at line 4, column 14 near `}\n`: Missing required members of shape `com.foo#MyList`: [`member`] +// Missing required member of shape `com.foo#MyList`: member namespace com.foo list MyList {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/map-empty-members.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/map-empty-members.smithy index e5d312b558d..f0033ef7cfd 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/map-empty-members.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/map-empty-members.smithy @@ -1,4 +1,4 @@ -// Parse error at line 4, column 12 near `}\n`: Missing required members of shape `com.foo#MyMap`: [`key`, `value`] | Model +// Missing required member of shape `com.foo#MyMap`: key namespace com.foo map MyMap {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/invalid-mixins-on-resource.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/invalid-mixins-on-resource.smithy deleted file mode 100644 index 8640db189f6..00000000000 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/invalid-mixins-on-resource.smithy +++ /dev/null @@ -1,5 +0,0 @@ -// Parse error at line 5, column 15 near `with`: Expected: '{', but found 'w' -$version: "2" -namespace com.foo - -resource Test with X {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/invalid-mixins-on-string.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/invalid-mixins-on-string.smithy deleted file mode 100644 index 4ebce8558b4..00000000000 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/invalid-mixins-on-string.smithy +++ /dev/null @@ -1,5 +0,0 @@ -// Parse error at line 5, column 21 near ` X`: Unexpected shape type: with -$version: "2" -namespace com.foo - -string MyString with X diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mix-types.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mix-types.smithy new file mode 100644 index 00000000000..823ceaf93f3 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mix-types.smithy @@ -0,0 +1,10 @@ +// Mixins may only be mixed into shapes of the same type. The following mixins were applied to the structure shape `com.foo#Foo` which are not structure shapes: [`com.foo#Bar`] +$version: "2" +namespace com.foo + +@mixin +union Bar { + first: String +} + +structure Foo with Bar {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/operation-mixin-with-io.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/operation-mixin-with-io.smithy new file mode 100644 index 00000000000..99c5b3f5536 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/operation-mixin-with-io.smithy @@ -0,0 +1,10 @@ +// Operation shapes with the mixin trait MUST target `smithy.api#Unit` for their input and output. Operation mixin shape `smithy.example#MixinOperation` defines one or both of these properties. +$version: "2.0" + +namespace smithy.example + +@mixin +operation MixinOperation { + input := {} + output := {} +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/resource-mixin-with-properties.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/resource-mixin-with-properties.smithy new file mode 100644 index 00000000000..ba98ff8934c --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/resource-mixin-with-properties.smithy @@ -0,0 +1,12 @@ +// Resource shapes with the mixin trait may not define any properties. Resource mixin shape `smithy.example#MixinResource` defines one or more properties. +$version: "2.0" + +namespace smithy.example + +@mixin +@internal +resource MixinResource { + identifiers: { + "mixinId": String + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/set-empty-members.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/set-empty-members.smithy index a1799904d7b..5dc7866ff09 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/set-empty-members.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/set-empty-members.smithy @@ -1,4 +1,4 @@ -// Parse error at line 4, column 12 near `}\n`: Missing required members of shape `com.foo#MySet`: [`member`] +// Missing required member of shape `com.foo#MySet`: member namespace com.foo set MySet {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.flattened.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.flattened.smithy index cf5a2cfb61d..f2dc5aedb89 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.flattened.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.flattened.smithy @@ -5,3 +5,48 @@ namespace smithy.example structure B {} structure F {} + +blob MixedBlob + +boolean MixedBoolean + +string MixedString + +byte MixedByte + +short MixedShort + +integer MixedInteger + +long MixedLong + +float MixedFloat + +double MixedDouble + +bigInteger MixedBigInt + +bigDecimal MixedBigDecimal + +timestamp MixedTimestamp + +document MixedDocument + +list MixedList { + member: String +} + +set MixedSet { + member: String +} + +map MixedMap { + key: String + value: String +} + +service MixedService {} + +resource MixedResource {} + +operation MixedOperation {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.json b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.json index 022e5393ff3..6ca2a6e83a2 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.json +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.json @@ -43,6 +43,246 @@ {"target": "smithy.example#A"}, {"target": "smithy.example#E"} ] + }, + "smithy.example#MixinBlob": { + "type": "blob", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedBlob": { + "type": "blob", + "mixins": [{ + "target": "smithy.example#MixinBlob" + }] + }, + "smithy.example#MixinBoolean": { + "type": "boolean", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedBoolean": { + "type": "boolean", + "mixins": [{ + "target": "smithy.example#MixinBoolean" + }] + }, + "smithy.example#MixinString": { + "type": "string", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedString": { + "type": "string", + "mixins": [{ + "target": "smithy.example#MixinString" + }] + }, + "smithy.example#MixinByte": { + "type": "byte", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedByte": { + "type": "byte", + "mixins": [{ + "target": "smithy.example#MixinByte" + }] + }, + "smithy.example#MixinShort": { + "type": "short", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedShort": { + "type": "short", + "mixins": [{ + "target": "smithy.example#MixinShort" + }] + }, + "smithy.example#MixinInteger": { + "type": "integer", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedInteger": { + "type": "integer", + "mixins": [{ + "target": "smithy.example#MixinInteger" + }] + }, + "smithy.example#MixinLong": { + "type": "long", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedLong": { + "type": "long", + "mixins": [{ + "target": "smithy.example#MixinLong" + }] + }, + "smithy.example#MixinFloat": { + "type": "float", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedFloat": { + "type": "float", + "mixins": [{ + "target": "smithy.example#MixinFloat" + }] + }, + "smithy.example#MixinDouble": { + "type": "double", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedDouble": { + "type": "double", + "mixins": [{ + "target": "smithy.example#MixinDouble" + }] + }, + "smithy.example#MixinBigInt": { + "type": "bigInteger", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedBigInt": { + "type": "bigInteger", + "mixins": [{ + "target": "smithy.example#MixinBigInt" + }] + }, + "smithy.example#MixinBigDecimal": { + "type": "bigDecimal", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedBigDecimal": { + "type": "bigDecimal", + "mixins": [{ + "target": "smithy.example#MixinBigDecimal" + }] + }, + "smithy.example#MixinTimestamp": { + "type": "timestamp", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedTimestamp": { + "type": "timestamp", + "mixins": [{ + "target": "smithy.example#MixinTimestamp" + }] + }, + "smithy.example#MixinDocument": { + "type": "document", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedDocument": { + "type": "document", + "mixins": [{ + "target": "smithy.example#MixinDocument" + }] + }, + "smithy.example#MixinList": { + "type": "list", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedList": { + "type": "list", + "mixins": [{ + "target": "smithy.example#MixinList" + }] + }, + "smithy.example#MixinSet": { + "type": "set", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedSet": { + "type": "set", + "mixins": [{ + "target": "smithy.example#MixinSet" + }] + }, + "smithy.example#MixinMap": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedMap": { + "type": "map", + "mixins": [{ + "target": "smithy.example#MixinMap" + }] + }, + "smithy.example#MixinService": { + "type": "service", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedService": { + "type": "service", + "mixins": [{ + "target": "smithy.example#MixinService" + }] + }, + "smithy.example#MixinResource": { + "type": "resource", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedResource": { + "type": "resource", + "mixins": [{ + "target": "smithy.example#MixinResource" + }] + }, + "smithy.example#MixinOperation": { + "type": "operation", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedOperation": { + "type": "operation", + "mixins": [{ + "target": "smithy.example#MixinOperation" + }] } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.smithy index 8b1ceb08bda..f2784592b91 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.smithy @@ -17,3 +17,105 @@ structure D with C {} structure E with D {} structure F with A, E {} + +@mixin +blob MixinBlob + +blob MixedBlob with MixinBlob + +@mixin +boolean MixinBoolean + +boolean MixedBoolean with MixinBoolean + +@mixin +string MixinString + +string MixedString with MixinString + +@mixin +byte MixinByte + +byte MixedByte with MixinByte + +@mixin +short MixinShort + +short MixedShort with MixinShort + +@mixin +integer MixinInteger + +integer MixedInteger with MixinInteger + +@mixin +long MixinLong + +long MixedLong with MixinLong + +@mixin +float MixinFloat + +float MixedFloat with MixinFloat + +@mixin +double MixinDouble + +double MixedDouble with MixinDouble + +@mixin +bigInteger MixinBigInt + +bigInteger MixedBigInt with MixinBigInt + +@mixin +bigDecimal MixinBigDecimal + +bigDecimal MixedBigDecimal with MixinBigDecimal + +@mixin +timestamp MixinTimestamp + +timestamp MixedTimestamp with MixinTimestamp + +@mixin +document MixinDocument + +document MixedDocument with MixinDocument + +@mixin +list MixinList { + member: String +} + +list MixedList with MixinList {} + +@mixin +set MixinSet { + member: String +} + +set MixedSet with MixinSet {} + +@mixin +map MixinMap { + key: String + value: String +} + +map MixedMap with MixinMap {} + +@mixin +service MixinService {} + +service MixedService with MixinService {} + +@mixin +resource MixinResource {} + +resource MixedResource with MixinResource {} + +@mixin +operation MixinOperation {} + +operation MixedOperation with MixinOperation {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.json b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.json new file mode 100644 index 00000000000..f4e525ac386 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.json @@ -0,0 +1,40 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#integerPrefixed": { + "type": "integer", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#NotPrefixed": { + "type": "integer", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#iiiiiii": { + "type": "integer", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedInt": { + "type": "integer", + "mixins": [ + { + "target": "smithy.example#NotPrefixed" + }, + { + "target": "smithy.example#integerPrefixed" + }, + { + "target": "smithy.example#iiiiiii" + } + ] + }, + "smithy.example#SafeInt": { + "type": "integer" + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.smithy new file mode 100644 index 00000000000..26cc2476420 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.smithy @@ -0,0 +1,21 @@ +$version: "2.0" + +namespace smithy.example + +@mixin +integer integerPrefixed + +@mixin +integer NotPrefixed + +// This is related to a bug that was present where only +// the first character of the identifier following a mixin +// was ever looked at. This ensures that happens to start +// with an i and be the same length as "integer" doesn't +// trip up the parser. +@mixin +integer iiiiiii + +integer MixedInt with NotPrefixed integerPrefixed iiiiiii + +integer SafeInt diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.flattened.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.flattened.smithy new file mode 100644 index 00000000000..0620dc803c2 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.flattened.smithy @@ -0,0 +1,14 @@ +$version: "2.0" + +namespace smithy.example + +@error("client") +structure ConcreteError {} + +@error("server") +structure MixinError {} + +@internal +operation ConcreteOperation { + errors: [MixinError, ConcreteError] +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.json b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.json new file mode 100644 index 00000000000..50987331a75 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.json @@ -0,0 +1,36 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#ConcreteError": { + "type": "structure", + "traits": { + "smithy.api#error": "client" + } + }, + "smithy.example#MixinError": { + "type": "structure", + "traits": { + "smithy.api#error": "server" + } + }, + "smithy.example#InternalMixin": { + "type": "operation", + "errors": [{ + "target": "smithy.example#MixinError" + }], + "traits": { + "smithy.api#mixin": {}, + "smithy.api#internal": {} + } + }, + "smithy.example#ConcreteOperation": { + "type": "operation", + "errors": [{ + "target": "smithy.example#ConcreteError" + }], + "mixins": [{ + "target": "smithy.example#InternalMixin" + }] + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.smithy new file mode 100644 index 00000000000..713ab580aab --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.smithy @@ -0,0 +1,19 @@ +$version: "2.0" + +namespace smithy.example + +@error("client") +structure ConcreteError {} + +@error("server") +structure MixinError {} + +@mixin +@internal +operation InternalMixin { + errors: [MixinError] +} + +operation ConcreteOperation with InternalMixin { + errors: [ConcreteError] +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.flattened.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.flattened.smithy new file mode 100644 index 00000000000..794665ab05a --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.flattened.smithy @@ -0,0 +1,6 @@ +$version: "2.0" + +namespace smithy.example + +@internal +resource MixedResource {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.json b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.json new file mode 100644 index 00000000000..0040b90ba78 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.json @@ -0,0 +1,18 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#MixinResource": { + "type": "resource", + "traits": { + "smithy.api#mixin": {}, + "smithy.api#internal": {} + } + }, + "smithy.example#MixedResource": { + "type": "resource", + "mixins": [{ + "target": "smithy.example#MixinResource" + }] + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.smithy new file mode 100644 index 00000000000..0c3c9c86892 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.smithy @@ -0,0 +1,9 @@ +$version: "2.0" + +namespace smithy.example + +@mixin +@internal +resource MixinResource {} + +resource MixedResource with MixinResource {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.flattened.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.flattened.smithy new file mode 100644 index 00000000000..47c6ee239fe --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.flattened.smithy @@ -0,0 +1,25 @@ +$version: "2.0" + +namespace smithy.example + +string ShapeToRename + +@error("client") +structure MixinError { + message: ShapeToRename +} + +operation MixinOperation {} + +resource MixinResource {} + +@internal +service MixedService { + version: "2021-12-31" + errors: [MixinError] + rename: { + "smithy.example#ShapeToRename": "RenamedShape" + } + operations: [MixinOperation] + resources: [MixinResource] +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.json b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.json new file mode 100644 index 00000000000..adcb63eccba --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.json @@ -0,0 +1,57 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#ShapeToRename": { + "type": "string" + }, + "smithy.example#MixinError": { + "type": "structure", + "members": { + "message": { + "target": "smithy.example#ShapeToRename" + } + }, + "traits": { + "smithy.api#error": "client" + } + }, + "smithy.example#MixinOperation": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + } + }, + "smithy.example#MixinResource": { + "type": "resource" + }, + "smithy.example#MixinService": { + "type": "service", + "version": "2021-12-31", + "errors": [{ + "target": "smithy.example#MixinError" + }], + "rename": { + "smithy.example#ShapeToRename": "RenamedShape" + }, + "operations": [{ + "target": "smithy.example#MixinOperation" + }], + "resources": [{ + "target": "smithy.example#MixinResource" + }], + "traits": { + "smithy.api#mixin": {}, + "smithy.api#internal": {} + } + }, + "smithy.example#MixedService": { + "type": "service", + "mixins": [{ + "target": "smithy.example#MixinService" + }] + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.smithy new file mode 100644 index 00000000000..5f5e9c2bd75 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.smithy @@ -0,0 +1,28 @@ +$version: "2.0" + +namespace smithy.example + +string ShapeToRename + +@error("client") +structure MixinError { + message: ShapeToRename +} + +operation MixinOperation {} + +resource MixinResource {} + +@internal +@mixin +service MixinService { + version: "2021-12-31" + errors: [MixinError] + rename: { + "smithy.example#ShapeToRename": "RenamedShape" + } + operations: [MixinOperation] + resources: [MixinResource] +} + +service MixedService with MixinService {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/data-mixins.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/data-mixins.json new file mode 100644 index 00000000000..3051d34eef0 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/data-mixins.json @@ -0,0 +1,258 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#A": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#B": { + "type": "structure", + "members": {}, + "mixins": [ + {"target": "smithy.example#A"} + ] + }, + "smithy.example#C": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#D": { + "type": "structure", + "members": {}, + "mixins": [ + {"target": "smithy.example#C"} + ], + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#E": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#mixin": {} + }, + "mixins": [ + {"target": "smithy.example#D"} + ] + }, + "smithy.example#F": { + "type": "structure", + "members": {}, + "mixins": [ + {"target": "smithy.example#A"}, + {"target": "smithy.example#E"} + ] + }, + "smithy.example#MixinBlob": { + "type": "blob", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedBlob": { + "type": "blob", + "mixins": [{ + "target": "smithy.example#MixinBlob" + }] + }, + "smithy.example#MixinBoolean": { + "type": "boolean", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedBoolean": { + "type": "boolean", + "mixins": [{ + "target": "smithy.example#MixinBoolean" + }] + }, + "smithy.example#MixinString": { + "type": "string", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedString": { + "type": "string", + "mixins": [{ + "target": "smithy.example#MixinString" + }] + }, + "smithy.example#MixinByte": { + "type": "byte", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedByte": { + "type": "byte", + "mixins": [{ + "target": "smithy.example#MixinByte" + }] + }, + "smithy.example#MixinShort": { + "type": "short", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedShort": { + "type": "short", + "mixins": [{ + "target": "smithy.example#MixinShort" + }] + }, + "smithy.example#MixinInteger": { + "type": "integer", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedInteger": { + "type": "integer", + "mixins": [{ + "target": "smithy.example#MixinInteger" + }] + }, + "smithy.example#MixinLong": { + "type": "long", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedLong": { + "type": "long", + "mixins": [{ + "target": "smithy.example#MixinLong" + }] + }, + "smithy.example#MixinFloat": { + "type": "float", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedFloat": { + "type": "float", + "mixins": [{ + "target": "smithy.example#MixinFloat" + }] + }, + "smithy.example#MixinDouble": { + "type": "double", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedDouble": { + "type": "double", + "mixins": [{ + "target": "smithy.example#MixinDouble" + }] + }, + "smithy.example#MixinBigInt": { + "type": "bigInteger", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedBigInt": { + "type": "bigInteger", + "mixins": [{ + "target": "smithy.example#MixinBigInt" + }] + }, + "smithy.example#MixinBigDecimal": { + "type": "bigDecimal", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedBigDecimal": { + "type": "bigDecimal", + "mixins": [{ + "target": "smithy.example#MixinBigDecimal" + }] + }, + "smithy.example#MixinTimestamp": { + "type": "timestamp", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedTimestamp": { + "type": "timestamp", + "mixins": [{ + "target": "smithy.example#MixinTimestamp" + }] + }, + "smithy.example#MixinDocument": { + "type": "document", + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedDocument": { + "type": "document", + "mixins": [{ + "target": "smithy.example#MixinDocument" + }] + }, + "smithy.example#MixinList": { + "type": "list", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedList": { + "type": "list", + "mixins": [{ + "target": "smithy.example#MixinList" + }] + }, + "smithy.example#MixinSet": { + "type": "set", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedSet": { + "type": "set", + "mixins": [{ + "target": "smithy.example#MixinSet" + }] + }, + "smithy.example#MixinMap": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedMap": { + "type": "map", + "mixins": [{ + "target": "smithy.example#MixinMap" + }] + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/operation-mixins.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/operation-mixins.json new file mode 100644 index 00000000000..af5f24087fd --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/operation-mixins.json @@ -0,0 +1,50 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#MixinError": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#error": "server" + } + }, + "smithy.example#MixinOperation": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + }, + "errors": [{ + "target": "smithy.example#MixinError" + }], + "traits": { + "smithy.api#mixin": {}, + "smithy.api#internal": {} + } + }, + "smithy.example#ConcreteError": { + "type": "structure", + "members": {}, + "traits": { + "smithy.api#error": "client" + } + }, + "smithy.example#MixedOperation": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + }, + "errors": [{ + "target": "smithy.example#ConcreteError" + }], + "mixins": [{ + "target": "smithy.example#MixinOperation" + }] + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/resource-mixins.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/resource-mixins.json new file mode 100644 index 00000000000..0040b90ba78 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/resource-mixins.json @@ -0,0 +1,18 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#MixinResource": { + "type": "resource", + "traits": { + "smithy.api#mixin": {}, + "smithy.api#internal": {} + } + }, + "smithy.example#MixedResource": { + "type": "resource", + "mixins": [{ + "target": "smithy.example#MixinResource" + }] + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins-with-overrides.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins-with-overrides.json new file mode 100644 index 00000000000..d1d8adcf9b2 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins-with-overrides.json @@ -0,0 +1,104 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#MixedService": { + "type": "service", + "version": "2022-01-01", + "operations": [{ + "target": "smithy.example#MixedOperation" + }], + "resources": [{ + "target": "smithy.example#MixedResource" + }], + "errors": [{ + "target": "smithy.example#MixedError" + }], + "rename": { + "smithy.example#OverriddenRename": "Override", + "smithy.example#MixedRename": "LocalRename" + }, + "mixins": [{ + "target": "smithy.example#MixinService" + }] + }, + "smithy.example#MixinService": { + "type": "service", + "version": "2021-12-31", + "operations": [{ + "target": "smithy.example#MixinOperation" + }], + "resources": [{ + "target": "smithy.example#MixinResource" + }], + "errors": [{ + "target": "smithy.example#MixinError" + }], + "rename": { + "smithy.example#MixinRename": "ThisWillBeInherited", + "smithy.example#OverriddenRename": "ThisWillBeOverridden" + }, + "traits": { + "smithy.api#mixin": {}, + "smithy.api#internal": {} + } + }, + "smithy.example#MixedResource": { + "type": "resource" + }, + "smithy.example#MixinResource": { + "type": "resource" + }, + "smithy.example#MixedOperation": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + } + }, + "smithy.example#MixinOperation": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + } + }, + "smithy.example#MixedError": { + "type": "structure", + "members": { + "message": { + "target": "smithy.example#MixedRename" + } + }, + "traits": { + "smithy.api#error": "server" + } + }, + "smithy.example#MixinError": { + "type": "structure", + "members": { + "message": { + "target": "smithy.example#MixinRename" + }, + "state": { + "target": "smithy.example#OverriddenRename" + } + }, + "traits": { + "smithy.api#error": "client" + } + }, + "smithy.example#MixedRename": { + "type": "string" + }, + "smithy.example#MixinRename": { + "type": "string" + }, + "smithy.example#OverriddenRename": { + "type": "string" + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins.json new file mode 100644 index 00000000000..adcb63eccba --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins.json @@ -0,0 +1,57 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#ShapeToRename": { + "type": "string" + }, + "smithy.example#MixinError": { + "type": "structure", + "members": { + "message": { + "target": "smithy.example#ShapeToRename" + } + }, + "traits": { + "smithy.api#error": "client" + } + }, + "smithy.example#MixinOperation": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + } + }, + "smithy.example#MixinResource": { + "type": "resource" + }, + "smithy.example#MixinService": { + "type": "service", + "version": "2021-12-31", + "errors": [{ + "target": "smithy.example#MixinError" + }], + "rename": { + "smithy.example#ShapeToRename": "RenamedShape" + }, + "operations": [{ + "target": "smithy.example#MixinOperation" + }], + "resources": [{ + "target": "smithy.example#MixinResource" + }], + "traits": { + "smithy.api#mixin": {}, + "smithy.api#internal": {} + } + }, + "smithy.example#MixedService": { + "type": "service", + "mixins": [{ + "target": "smithy.example#MixinService" + }] + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/data-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/data-mixins.smithy new file mode 100644 index 00000000000..19ae396cee8 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/data-mixins.smithy @@ -0,0 +1,106 @@ +$version: "2.0" + +namespace smithy.example + +list MixedList with MixinList {} + +@internal +@mixin +list MixinList { + member: String +} + +set MixedSet with MixinSet {} + +@internal +@mixin +set MixinSet { + member: String +} + +map MixedMap with MixinMap {} + +@internal +@mixin +map MixinMap { + key: String + value: String +} + +bigDecimal MixedBigDecimal with MixinBigDecimal + +bigInteger MixedBigInt with MixinBigInt + +blob MixedBlob with MixinBlob + +boolean MixedBoolean with MixinBoolean + +byte MixedByte with MixinByte + +document MixedDocument with MixinDocument + +double MixedDouble with MixinDouble + +float MixedFloat with MixinFloat + +integer MixedInteger with MixinInteger + +long MixedLong with MixinLong + +short MixedShort with MixinShort + +string MixedString with MixinString + +timestamp MixedTimestamp with MixinTimestamp + +@internal +@mixin +bigDecimal MixinBigDecimal + +@internal +@mixin +bigInteger MixinBigInt + +@internal +@mixin +blob MixinBlob + +@internal +@mixin +boolean MixinBoolean + +@internal +@mixin +byte MixinByte + +@internal +@mixin +document MixinDocument + +@internal +@mixin +double MixinDouble + +@internal +@mixin +float MixinFloat + +@internal +@mixin +integer MixinInteger + +@internal +@mixin +long MixinLong + +@internal +@mixin +short MixinShort + +@internal +@mixin +string MixinString + +@internal +@mixin +timestamp MixinTimestamp diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/operation-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/operation-mixins.smithy new file mode 100644 index 00000000000..24a041e3f45 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/operation-mixins.smithy @@ -0,0 +1,27 @@ +$version: "2.0" + +namespace smithy.example + +operation ConcreteOperation with InternalMixin { + input: Unit + output: Unit + errors: [ + ConcreteError + ] +} + +@internal +@mixin +operation InternalMixin { + input: Unit + output: Unit + errors: [ + MixinError + ] +} + +@error("client") +structure ConcreteError {} + +@error("server") +structure MixinError {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/resource-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/resource-mixins.smithy new file mode 100644 index 00000000000..a8c89fbe8c4 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/resource-mixins.smithy @@ -0,0 +1,11 @@ +$version: "2.0" + +namespace smithy.example + +resource MixedResource with MixinResource { +} + +@internal +@mixin +resource MixinResource { +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins-with-merging.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins-with-merging.smithy new file mode 100644 index 00000000000..16ac8196e5c --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins-with-merging.smithy @@ -0,0 +1,72 @@ +$version: "2.0" + +namespace smithy.example + +service MixedService with MixinService { + version: "2022-01-01" + operations: [ + MixedOperation + ] + resources: [ + MixedResource + ] + errors: [ + MixedError + ] + rename: { + "smithy.example#OverriddenRename": "Override" + "smithy.example#MixedRename": "LocalRename" + } +} + +@internal +@mixin +service MixinService { + version: "2021-12-31" + operations: [ + MixinOperation + ] + resources: [ + MixinResource + ] + errors: [ + MixinError + ] + rename: { + "smithy.example#MixinRename": "ThisWillBeInherited" + "smithy.example#OverriddenRename": "ThisWillBeOverridden" + } +} + +resource MixedResource { +} + +resource MixinResource { +} + +operation MixedOperation { + input: Unit + output: Unit +} + +operation MixinOperation { + input: Unit + output: Unit +} + +@error("server") +structure MixedError { + message: MixedRename +} + +@error("client") +structure MixinError { + message: MixinRename + state: OverriddenRename +} + +string MixedRename + +string MixinRename + +string OverriddenRename diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins.smithy new file mode 100644 index 00000000000..ce3b75f3c7b --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins.smithy @@ -0,0 +1,39 @@ +$version: "2.0" + +namespace smithy.example + +service MixedService with MixinService { +} + +@internal +@mixin +service MixinService { + version: "2021-12-31" + operations: [ + MixinOperation + ] + resources: [ + MixinResource + ] + errors: [ + MixinError + ] + rename: { + "smithy.example#ShapeToRename": "RenamedShape" + } +} + +resource MixinResource { +} + +operation MixinOperation { + input: Unit + output: Unit +} + +@error("client") +structure MixinError { + message: ShapeToRename +} + +string ShapeToRename diff --git a/smithy-utils/src/main/java/software/amazon/smithy/utils/SimpleParser.java b/smithy-utils/src/main/java/software/amazon/smithy/utils/SimpleParser.java index 789efca1072..fcdefd02f52 100644 --- a/smithy-utils/src/main/java/software/amazon/smithy/utils/SimpleParser.java +++ b/smithy-utils/src/main/java/software/amazon/smithy/utils/SimpleParser.java @@ -103,6 +103,12 @@ public final int column() { return column; } + public final void rewind(int position, int line, int column) { + this.column = column; + this.line = line; + this.position = position; + } + /** * Checks if the parser has reached the end of the expression. * From 11cb6758e8866f3410b4d4b3d1219fc5968a3b64 Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Mon, 27 Dec 2021 17:22:27 +0100 Subject: [PATCH 02/10] Compute introduced properties after the fact --- .../smithy/model/knowledge/PreMixinIndex.java | 222 ++++++++++++++++++ .../smithy/model/shapes/EntityShape.java | 35 +-- .../smithy/model/shapes/ModelSerializer.java | 31 ++- .../smithy/model/shapes/OperationShape.java | 17 +- .../smithy/model/shapes/ServiceShape.java | 64 +---- .../shapes/SmithyIdlModelSerializer.java | 26 +- .../model/knowledge/PreMixinIndexTest.java | 42 ++++ .../model/knowledge/premixin-unrolled.smithy | 89 +++++++ .../smithy/model/knowledge/premixin.smithy | 89 +++++++ 9 files changed, 491 insertions(+), 124 deletions(-) create mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PreMixinIndex.java create mode 100644 smithy-model/src/test/java/software/amazon/smithy/model/knowledge/PreMixinIndexTest.java create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin-unrolled.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PreMixinIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PreMixinIndex.java new file mode 100644 index 00000000000..d6d64ae7b6a --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PreMixinIndex.java @@ -0,0 +1,222 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package software.amazon.smithy.model.knowledge; + +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.ListShape; +import software.amazon.smithy.model.shapes.MapShape; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ServiceShape; +import software.amazon.smithy.model.shapes.SetShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.ShapeVisitor; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.shapes.UnionShape; + +/** + * Index of shapes stripped down to only their introduced properties. + * + *

This is primarily useful in serialization to determine what + * properties to actually serialize vs what is computed as part of + * mixins. + */ +public final class PreMixinIndex implements KnowledgeIndex { + + private final Map preMixinShapes = new HashMap<>(); + + public PreMixinIndex(Model model) { + MixinUnroller unroller = new MixinUnroller(model); + model.shapes() + .filter(shape -> !shape.isMemberShape()) + .forEach(shape -> { + if (!shape.getMixins().isEmpty()) { + preMixinShapes.put(shape.getId(), shape.accept(unroller)); + } + }); + } + + public static PreMixinIndex of(Model model) { + return model.getKnowledge(PreMixinIndex.class, PreMixinIndex::new); + } + + /** + * Gets a version of the shape that has mixin properties stripped out. + * + *

NOTE: mixin members with introduced traits WILL be present in + * their entirety. The only way to determine if those members originally + * were defined in a mixin is to have an original copy of the shape to + * compare against. Any members of the shape that themselves have mixins + * are inherited. + * + * @param shape The shape to strip mixin data from. + * @return A version of the shape without mixin data. + */ + public Shape getPreMixinShape(Shape shape) { + return preMixinShapes.getOrDefault(shape.getId(), shape); + } + + private static final class MixinUnroller extends ShapeVisitor.Default { + + private final Model model; + + private MixinUnroller(Model model) { + this.model = model; + } + + @Override + protected Shape getDefault(Shape shape) { + return Shape.shapeToBuilder(shape) + .clearMixins() + .traits(shape.getIntroducedTraits().values()) + .build(); + } + + @Override + public Shape listShape(ListShape shape) { + return shape.toBuilder() + .clearMixins() + .traits(shape.getIntroducedTraits().values()) + .member((MemberShape) shape.getMember().accept(this)) + .build(); + } + + @Override + public Shape setShape(SetShape shape) { + return shape.toBuilder() + .clearMixins() + .traits(shape.getIntroducedTraits().values()) + .member((MemberShape) shape.getMember().accept(this)) + .build(); + } + + @Override + public Shape mapShape(MapShape shape) { + return shape.toBuilder() + .clearMixins() + .traits(shape.getIntroducedTraits().values()) + .key((MemberShape) shape.getKey().accept(this)) + .value((MemberShape) shape.getValue().accept(this)) + .build(); + } + + @Override + public Shape structureShape(StructureShape shape) { + StructureShape.Builder builder = shape.toBuilder() + .clearMixins() + .traits(shape.getIntroducedTraits().values()); + unrollMembers(shape, builder::addMember); + return builder.build(); + } + + @Override + public Shape unionShape(UnionShape shape) { + UnionShape.Builder builder = shape.toBuilder() + .clearMixins() + .traits(shape.getIntroducedTraits().values()); + unrollMembers(shape, builder::addMember); + return builder.build(); + } + + private void unrollMembers(Shape shape, Consumer consumer) { + for (MemberShape member : shape.members()) { + if (member.getMixins().isEmpty()) { + consumer.accept(member); + } else { + consumer.accept((MemberShape) member.accept(this)); + } + } + } + + @Override + public Shape operationShape(OperationShape shape) { + OperationShape.Builder builder = shape.toBuilder() + .clearMixins() + .traits(shape.getIntroducedTraits().values()) + .clearErrors(); + + Set previousErrors = new HashSet<>(); + for (ShapeId mixinId : shape.getMixins()) { + previousErrors.addAll(model.expectShape(mixinId, OperationShape.class).getErrors()); + } + + addIntroduced(shape.getErrors(), previousErrors, builder::addError); + + return builder.build(); + } + + @Override + public Shape serviceShape(ServiceShape shape) { + ServiceShape.Builder builder = shape.toBuilder() + .clearMixins() + .clearOperations() + .clearResources() + .clearErrors() + .clearRename() + .traits(shape.getIntroducedTraits().values()); + + String previousVersion = ""; + Set previousOperations = new HashSet<>(); + Set previousResources = new HashSet<>(); + Set previousErrors = new HashSet<>(); + Map previousRename = new HashMap<>(); + + for (ShapeId mixinId : shape.getMixins()) { + ServiceShape mixin = model.expectShape(mixinId, ServiceShape.class); + previousVersion = mixin.getVersion(); + previousOperations.addAll(mixin.getOperations()); + previousResources.addAll(mixin.getResources()); + previousErrors.addAll(mixin.getErrors()); + previousRename.putAll(mixin.getRename()); + } + + if (shape.getVersion().equals(previousVersion)) { + builder.version(""); + } + + addIntroduced(shape.getOperations(), previousOperations, builder::addOperation); + addIntroduced(shape.getResources(), previousResources, builder::addResource); + addIntroduced(shape.getErrors(), previousErrors, builder::addError); + + for (Map.Entry entry : shape.getRename().entrySet()) { + if (!entry.getValue().equals(previousRename.get(entry.getKey()))) { + builder.putRename(entry.getKey(), entry.getValue()); + } + } + + return builder.build(); + } + + private void addIntroduced( + Collection current, + Collection previous, + Consumer consumer + ) { + for (ShapeId shapeId : current) { + if (!previous.contains(shapeId)) { + consumer.accept(shapeId); + } + } + } + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java index 4d2173e840c..0ad3814fcd9 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java @@ -27,18 +27,14 @@ public abstract class EntityShape extends Shape { private final Set resources; - private final Set introducedResources; private final Set operations; - private final Set introducedOperations; EntityShape(Builder builder) { super(builder, false); if (getMixins().isEmpty()) { resources = builder.resources.copy(); - introducedResources = resources; operations = builder.operations.copy(); - introducedOperations = operations; } else { Set computedResources = new TreeSet<>(); Set computedOperations = new TreeSet<>(); @@ -50,11 +46,8 @@ public abstract class EntityShape extends Shape { computedOperations.addAll(mixin.getOperations()); } - introducedResources = builder.resources.copy(); - introducedOperations = builder.operations.copy(); - - computedResources.addAll(introducedResources); - computedOperations.addAll(introducedOperations); + computedResources.addAll(builder.resources.peek()); + computedOperations.addAll(builder.operations.peek()); resources = Collections.unmodifiableSet(computedResources); operations = Collections.unmodifiableSet(computedOperations); @@ -68,16 +61,6 @@ public final Set getResources() { return resources; } - /** - * Gets all the directly-bound resources introduced by this shape and - * not inherited from mixins. - * - * @return Gets the introduced resources directly-bound to the shape. - */ - public final Set getIntroducedResources() { - return introducedResources; - } - /** * Gets operations bound only through the "operations" property. * @@ -93,20 +76,6 @@ public final Set getOperations() { return operations; } - /** - * Gets operations bound through the "operations" property that - * were not inherited from mixins. - * - *

This will not include operations bound to resources using - * a lifecycle operation binding. This will also not include - * operations bound to this entity through sub-resources. - * - * @return Gets the introduced operations. - */ - public final Set getIntroducedOperations() { - return introducedOperations; - } - /** * Get all operations directly bound to this shape. * diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java index 0c31c645932..b9de110b9c8 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java @@ -29,6 +29,7 @@ import java.util.stream.Stream; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceLocation; +import software.amazon.smithy.model.knowledge.PreMixinIndex; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.Node; @@ -66,7 +67,7 @@ private ModelSerializer(Builder builder) { } public ObjectNode serialize(Model model) { - ShapeSerializer shapeSerializer = new ShapeSerializer(); + ShapeSerializer shapeSerializer = new ShapeSerializer(model); ObjectNode.Builder builder = Node.objectNodeBuilder() .withMember("smithy", Node.from(Model.MODEL_VERSION)) @@ -204,6 +205,11 @@ private ObjectNode.Builder serializeTraits(ObjectNode.Builder builder, Collectio private final class ShapeSerializer extends ShapeVisitor.Default { private final Set mixinMemberTraits = new TreeSet<>(); + private final PreMixinIndex preMixinIndex; + + private ShapeSerializer(Model model) { + preMixinIndex = PreMixinIndex.of(model); + } private ObjectNode.Builder createTypedBuilder(Shape shape) { ObjectNode.Builder builder = Node.objectNodeBuilder() @@ -263,10 +269,11 @@ public Node mapShape(MapShape shape) { @Override public Node operationShape(OperationShape shape) { + OperationShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asOperationShape().get(); return serializeAllTraits(shape, createTypedBuilder(shape) .withMember("input", serializeReference(shape.getInputShape())) .withMember("output", serializeReference(shape.getOutputShape())) - .withOptionalMember("errors", createOptionalIdList(shape.getIntroducedErrors()))) + .withOptionalMember("errors", createOptionalIdList(preMixinShape.getErrors()))) .build(); } @@ -288,9 +295,9 @@ public Node resourceShape(ResourceShape shape) { .withOptionalMember("update", shape.getUpdate().map(this::serializeReference)) .withOptionalMember("delete", shape.getDelete().map(this::serializeReference)) .withOptionalMember("list", shape.getList().map(this::serializeReference)) - .withOptionalMember("operations", createOptionalIdList(shape.getIntroducedOperations())) + .withOptionalMember("operations", createOptionalIdList(shape.getOperations())) .withOptionalMember("collectionOperations", createOptionalIdList(shape.getCollectionOperations())) - .withOptionalMember("resources", createOptionalIdList(shape.getIntroducedResources()))) + .withOptionalMember("resources", createOptionalIdList(shape.getResources()))) .build(); } @@ -298,17 +305,19 @@ public Node resourceShape(ResourceShape shape) { public Node serviceShape(ServiceShape shape) { ObjectNode.Builder serviceBuilder = createTypedBuilder(shape); - if (!StringUtils.isBlank(shape.getIntroducedVersion())) { - serviceBuilder.withMember("version", Node.from(shape.getIntroducedVersion())); + ServiceShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asServiceShape().get(); + + if (!StringUtils.isBlank(preMixinShape.getVersion())) { + serviceBuilder.withMember("version", Node.from(preMixinShape.getVersion())); } - serviceBuilder.withOptionalMember("operations", createOptionalIdList(shape.getIntroducedOperations())); - serviceBuilder.withOptionalMember("resources", createOptionalIdList(shape.getIntroducedResources())); - serviceBuilder.withOptionalMember("errors", createOptionalIdList(shape.getIntroducedErrors())); + serviceBuilder.withOptionalMember("operations", createOptionalIdList(preMixinShape.getOperations())); + serviceBuilder.withOptionalMember("resources", createOptionalIdList(preMixinShape.getResources())); + serviceBuilder.withOptionalMember("errors", createOptionalIdList(preMixinShape.getErrors())); - if (!shape.getIntroducedRename().isEmpty()) { + if (!preMixinShape.getRename().isEmpty()) { ObjectNode.Builder renameBuilder = Node.objectNodeBuilder(); - for (Map.Entry entry : shape.getIntroducedRename().entrySet()) { + for (Map.Entry entry : preMixinShape.getRename().entrySet()) { renameBuilder.withMember(entry.getKey().toString(), entry.getValue()); } serviceBuilder.withMember("rename", renameBuilder.build()); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java index d2966c2085b..98518c22d10 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java @@ -36,7 +36,6 @@ public final class OperationShape extends Shape implements ToSmithyBuilder errors; - private final List introducedErrors; private OperationShape(Builder builder) { super(builder, false); @@ -46,7 +45,6 @@ private OperationShape(Builder builder) { if (getMixins().isEmpty()) { errors = builder.errors.copy(); - introducedErrors = errors; } else { // Compute mixin properties of the operation. Input / output are // forbidden in operation mixins, so we don't bother with them @@ -55,8 +53,7 @@ private OperationShape(Builder builder) { for (Shape shape : builder.getMixins().values()) { shape.asOperationShape().ifPresent(mixin -> computedErrors.addAll(mixin.getErrors())); } - introducedErrors = builder.errors.copy(); - computedErrors.addAll(introducedErrors); + computedErrors.addAll(builder.errors.peek()); errors = Collections.unmodifiableList(new ArrayList<>(computedErrors)); } @@ -78,7 +75,7 @@ public Builder toBuilder() { return updateBuilder(builder()) .input(input) .output(output) - .errors(getIntroducedErrors()); + .errors(errors); } @Override @@ -165,16 +162,6 @@ public List getErrors() { return errors; } - /** - * Gets the errors introduced by the shape and not inherited - * from mixins. - * - * @return Returns the introduced errors. - */ - public List getIntroducedErrors() { - return introducedErrors; - } - /** *

Gets a list of the error shape IDs the operation can encounter, * including any common errors of a service. diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java index 50234bca8d3..897928af4b0 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java @@ -34,22 +34,16 @@ public final class ServiceShape extends EntityShape implements ToSmithyBuilder { private final String version; - private final String introducedVersion; private final Map rename; - private final Map introducedRename; private final List errors; - private final List introducedErrors; private ServiceShape(Builder builder) { super(builder); if (getMixins().isEmpty()) { version = builder.version; - introducedVersion = version; rename = builder.rename.copy(); - introducedRename = rename; errors = builder.errors.copy(); - introducedErrors = errors; } else { String computedVersion = ""; Map computedRename = new HashMap<>(); @@ -66,15 +60,11 @@ private ServiceShape(Builder builder) { } } - introducedVersion = builder.version; - introducedRename = builder.rename.copy(); - introducedErrors = builder.errors.copy(); - - if (!introducedVersion.isEmpty()) { - computedVersion = introducedVersion; + if (!builder.version.isEmpty()) { + computedVersion = builder.version; } - computedRename.putAll(introducedRename); - computedErrors.addAll(introducedErrors); + computedRename.putAll(builder.rename.peek()); + computedErrors.addAll(builder.errors.peek()); version = computedVersion; rename = Collections.unmodifiableMap(computedRename); @@ -89,11 +79,11 @@ public static Builder builder() { @Override public Builder toBuilder() { return updateBuilder(builder()) - .version(introducedVersion) - .errors(introducedErrors) - .rename(introducedRename) - .operations(getIntroducedOperations()) - .resources(getIntroducedResources()); + .version(version) + .errors(errors) + .rename(rename) + .operations(getOperations()) + .resources(getResources()); } @Override @@ -128,17 +118,6 @@ public String getVersion() { return version; } - /** - * Gets the version of the service introduced by the shape and not - * inherited from mixins. An empty string is returned if the version - * is undefined. - * - * @return The introduced version of the service. - */ - public String getIntroducedVersion() { - return introducedVersion; - } - /** * @return The rename map of the service. */ @@ -146,16 +125,6 @@ public Map getRename() { return rename; } - /** - * Gets the rename map introduced by the shape and not inherited - * from mixins. - * - * @return The introduced rename map of the service. - */ - public Map getIntroducedRename() { - return introducedRename; - } - /** *

Gets a list of the common errors that can be encountered by * every operation in the service.

@@ -170,21 +139,6 @@ public List getErrors() { return errors; } - /** - * Gets the list of common errors introduced by the shape and not - * inherited from mixins. These errors can be encountered by every - * operation in the service. - * - * Each returned {@link ShapeId} must resolve to a - * {@link StructureShape} that is targeted by an error trait; however, - * this is only guaranteed after a model is validated. - * - * @return Returns the introduced service errors. - */ - public List getIntroducedErrors() { - return introducedErrors; - } - /** * Gets the contextual name of a shape within the closure. * diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java index a3a1b586ff3..6a355f01c66 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java @@ -33,6 +33,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.PreMixinIndex; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.BooleanNode; @@ -371,6 +372,7 @@ private static final class ShapeSerializer extends ShapeVisitor.Default { private final Predicate traitFilter; private final Model model; private final Set inlineableShapes; + private final PreMixinIndex preMixinIndex; ShapeSerializer( SmithyCodeWriter codeWriter, @@ -384,6 +386,7 @@ private static final class ShapeSerializer extends ShapeVisitor.Default { this.traitFilter = traitFilter; this.model = model; this.inlineableShapes = inlineableShapes; + this.preMixinIndex = PreMixinIndex.of(model); } @Override @@ -550,16 +553,18 @@ public Void serviceShape(ServiceShape shape) { writeMixins(shape, false); codeWriter.openBlock("{"); - if (!StringUtils.isBlank(shape.getIntroducedVersion())) { - codeWriter.write("version: $S", shape.getIntroducedVersion()); + ServiceShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asServiceShape().get(); + + if (!StringUtils.isBlank(preMixinShape.getVersion())) { + codeWriter.write("version: $S", preMixinShape.getVersion()); } - codeWriter.writeOptionalIdList("operations", shape.getIntroducedOperations()); - codeWriter.writeOptionalIdList("resources", shape.getIntroducedResources()); - codeWriter.writeOptionalIdList("errors", shape.getIntroducedErrors()); - if (!shape.getIntroducedRename().isEmpty()) { + codeWriter.writeOptionalIdList("operations", preMixinShape.getOperations()); + codeWriter.writeOptionalIdList("resources", preMixinShape.getResources()); + codeWriter.writeOptionalIdList("errors", preMixinShape.getErrors()); + if (!preMixinShape.getRename().isEmpty()) { codeWriter.openBlock("rename: {", "}", () -> { - for (Map.Entry entry : shape.getIntroducedRename().entrySet()) { + for (Map.Entry entry : preMixinShape.getRename().entrySet()) { codeWriter.write("$S: $S", entry.getKey(), entry.getValue()); } }); @@ -589,9 +594,9 @@ public Void resourceShape(ResourceShape shape) { shape.getUpdate().ifPresent(shapeId -> codeWriter.write("update: $I", shapeId)); shape.getDelete().ifPresent(shapeId -> codeWriter.write("delete: $I", shapeId)); shape.getList().ifPresent(shapeId -> codeWriter.write("list: $I", shapeId)); - codeWriter.writeOptionalIdList("operations", shape.getIntroducedOperations()); + codeWriter.writeOptionalIdList("operations", shape.getOperations()); codeWriter.writeOptionalIdList("collectionOperations", shape.getCollectionOperations()); - codeWriter.writeOptionalIdList("resources", shape.getIntroducedResources()); + codeWriter.writeOptionalIdList("resources", shape.getResources()); codeWriter.closeBlock("}"); codeWriter.write(""); @@ -600,6 +605,7 @@ public Void resourceShape(ResourceShape shape) { @Override public Void operationShape(OperationShape shape) { + OperationShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asOperationShape().get(); serializeTraits(shape); codeWriter.writeInline("operation $L ", shape.getId().getName()); writeMixins(shape, false); @@ -607,7 +613,7 @@ public Void operationShape(OperationShape shape) { List mixinMembers = new ArrayList<>(); mixinMembers.addAll(writeInlineableProperty("input", shape.getInputShape(), InputTrait.ID)); mixinMembers.addAll(writeInlineableProperty("output", shape.getOutputShape(), OutputTrait.ID)); - codeWriter.writeOptionalIdList("errors", shape.getIntroducedErrors()); + codeWriter.writeOptionalIdList("errors", preMixinShape.getErrors()); codeWriter.closeBlock("}"); codeWriter.write(""); applyIntroducedTraits(mixinMembers); diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/PreMixinIndexTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/PreMixinIndexTest.java new file mode 100644 index 00000000000..6cfad787529 --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/PreMixinIndexTest.java @@ -0,0 +1,42 @@ +package software.amazon.smithy.model.knowledge; + +import java.util.HashSet; +import java.util.Set; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.node.Node; +import software.amazon.smithy.model.node.ObjectNode; +import software.amazon.smithy.model.shapes.ModelSerializer; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.transform.ModelTransformer; + +public class PreMixinIndexTest { + + @Test + public void testUnroll() { + Model withMixins = Model.assembler() + .addImport(OperationIndexTest.class.getResource("premixin.smithy")) + .assemble() + .unwrap(); + + PreMixinIndex index = PreMixinIndex.of(withMixins); + Set updatedShapes = new HashSet<>(); + withMixins.shapes().forEach(shape -> updatedShapes.add(index.getPreMixinShape(shape))); + + Model actual = ModelTransformer.create().replaceShapes(withMixins, updatedShapes); + + Model expected = Model.assembler() + .addImport(OperationIndexTest.class.getResource("premixin-unrolled.smithy")) + .assemble() + .unwrap(); + + if (!actual.equals(expected)) { + ModelSerializer serializer = ModelSerializer.builder().build(); + ObjectNode actualNode = serializer.serialize(actual); + ObjectNode expectedNode = serializer.serialize(expected); + + Node.assertEquals(actualNode, expectedNode); + } + } + +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin-unrolled.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin-unrolled.smithy new file mode 100644 index 00000000000..2d241aac016 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin-unrolled.smithy @@ -0,0 +1,89 @@ +$version: "2.0" + +namespace smithy.example + +@private +service MixedService { + version: "2022-01-01" + operations: [ + MixedServiceOperation + ] + resources: [ + MixedResource + ] + errors: [ + MixedError + ] + rename: { + "smithy.example#MixedRename": "LocalRename" + } +} + +@internal +@mixin +service MixinService { + version: "2021-12-31" + operations: [ + MixinServiceOperation + ] + resources: [ + MixinResource + ] + errors: [ + MixinError + ] + rename: { + "smithy.example#MixinRename": "UpstreamRename" + } +} + +resource MixedResource { +} + +resource MixinResource { +} + +@mixin +@internal +operation MixinOperation { + errors: [MixinError] +} + +@private +operation MixedOperation { + errors: [MixedError] +} + +operation MixedServiceOperation { + input: Unit + output: Unit +} + +operation MixinServiceOperation { + input: Unit + output: Unit +} + +@error("server") +structure MixedError { + message: MixedRename +} + +@error("client") +structure MixinError { + message: MixinRename + state: OverriddenRename +} + +string MixedRename + +string MixinRename + +@mixin +@internal +string MixinString + +@private +string MixedString + +string OverriddenRename diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy new file mode 100644 index 00000000000..cc59b3a18eb --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy @@ -0,0 +1,89 @@ +$version: "2.0" + +namespace smithy.example + +@private +service MixedService with MixinService { + version: "2022-01-01" + operations: [ + MixedServiceOperation + ] + resources: [ + MixedResource + ] + errors: [ + MixedError + ] + rename: { + "smithy.example#MixedRename": "LocalRename" + } +} + +@internal +@mixin +service MixinService { + version: "2021-12-31" + operations: [ + MixinServiceOperation + ] + resources: [ + MixinResource + ] + errors: [ + MixinError + ] + rename: { + "smithy.example#MixinRename": "UpstreamRename" + } +} + +resource MixedResource { +} + +resource MixinResource { +} + +@mixin +@internal +operation MixinOperation { + errors: [MixinError] +} + +@private +operation MixedOperation with MixinOperation { + errors: [MixedError] +} + +operation MixedServiceOperation { + input: Unit + output: Unit +} + +operation MixinServiceOperation { + input: Unit + output: Unit +} + +@error("server") +structure MixedError { + message: MixedRename +} + +@error("client") +structure MixinError { + message: MixinRename + state: OverriddenRename +} + +string MixedRename + +string MixinRename + +@mixin +@internal +string MixinString + +@private +string MixedString with MixinString + +string OverriddenRename From 00793b1444fec0334bc99650429ccfee1c81a74e Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Mon, 3 Jan 2022 16:07:50 +0100 Subject: [PATCH 03/10] Allow duplicating mixin members This updates mixins to allow duplicating a mixin member on a shape, so long as the target remains the same. Members applied locally will behave the same way as they would if they were applied with the apply statement. --- designs/mixins.md | 97 +++++++++++++++++-- .../loader/AbstractMutableModelFile.java | 11 ++- .../model/transform/ModelTransformerTest.java | 3 +- .../mixin-illegal-redefined-member.smithy | 10 +- ...dl-mixins-redefine-member.flattened.smithy | 9 ++ .../mixins/idl-mixins-redefine-member.json | 33 +++++++ .../mixins/idl-mixins-redefine-member.smithy | 17 ++++ 7 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.flattened.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.json create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.smithy diff --git a/designs/mixins.md b/designs/mixins.md index 96cf8c66625..cb75b13711c 100644 --- a/designs/mixins.md +++ b/designs/mixins.md @@ -349,13 +349,12 @@ can be referred to outside of `smithy.example`. The members and traits applied to members of a mixin are copied onto the target shape. It is sometimes necessary to provide a more specific trait value for a copied member or to add traits only to a specific copy of a member. Traits can -be applied to these members in the JSON AST using the `apply` type and in the -Smithy IDL using `apply` statements. +be added on to these members as normal. Additionally, Traits can be applied to +these members in the JSON AST using the `apply` type and in the Smithy IDL using +`apply` statements. -> Note: using the `apply` type and `apply` statements on members that are -copied from mixin members _do not_ merge the applied trait value with the -traits applied to the original mixin member. Applying traits to copied mixin -members completely supersedes any traits applied to mixin members. +> Note: just like with traits on the shape itself, the local trait values +supersede trait values from mixins. #### Applying traits in the JSON AST @@ -403,6 +402,45 @@ applied to `MyStruct`. The `mixinMember` member of `MyMixin` has a } ``` +Traits can also be applied to copies of mixin members as if they were local +members. + +```json +{ + "smithy": "1.1", + "shapes": { + "smithy.example#MyMixin": { + "type": "structure", + "members": { + "mixinMember": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Generic docs" + } + } + }, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MyStruct": { + "type": "structure", + "members": { + "mixinMember": { + "target": "smithy.api#String", + "traits": { + "smithy.api#documentation": "Specific docs" + } + } + }, + "mixins": [ + {"target": "smithy.example#MyMixin"} + ] + } + } +} +``` + #### Applying traits in the IDL The previous example can be defined in the Smithy IDL using an @@ -428,6 +466,24 @@ structure MyStruct with MyMixin {} apply MyStruct$mixinMember @documentation("Specific docs") ``` +Or, alternatively: + +```smithy +$version: "1.1" +namespace smithy.example + +@mixin +structure MyMixin { + /// Generic docs + mixinMember: String +} + +structure MyStruct with MyMixin { + /// Specific docs + mixinMember: String +} +``` + ### Mixins are not code generated @@ -487,7 +543,8 @@ structure CycleB with CycleA {} ### Mixin members MUST NOT conflict The list of mixins applied to a structure or union MUST NOT attempt to define -members that use the same member name. The following model is invalid: +members that use the same member name with different targets. The following model +is invalid: ``` @mixin @@ -497,7 +554,7 @@ structure A1 { @mixin structure A2 { - a: String + a: Integer } structure Invalid with @@ -517,7 +574,7 @@ structure A1 { @mixin structure A2 { - A: String + A: Integer } structure Invalid with @@ -525,6 +582,28 @@ structure Invalid with A2 {} ``` +A shape MAY use a member name that has already defined, but if it does it MUST +target the same shape. This can be done to apply additional traits to the +member. + +``` +@mixin +structure A1 { + @private + a: String +} + +@mixin +structure A2 { + @required + a: String +} + +structure Valid with + A1 + A2 {} +``` + ### Mixins in the IDL diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/AbstractMutableModelFile.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/AbstractMutableModelFile.java index a1481aa6cb5..c9c30b9ff84 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/AbstractMutableModelFile.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/AbstractMutableModelFile.java @@ -228,7 +228,7 @@ private PendingShape createPendingShape( for (ShapeId mixin : mixins) { Shape mixinShape = shapeMap.get(mixin); for (MemberShape member : mixinShape.members()) { - if (builderMembers.containsKey(member.getMemberName())) { + if (memberConflicts(builderMembers, member)) { // Members cannot be redefined. MemberShape.Builder conflict = builderMembers.get(member.getMemberName()); events.add(ValidationEvent.builder() @@ -260,6 +260,15 @@ private PendingShape createPendingShape( }); } + private boolean memberConflicts(Map builderMembers, MemberShape mixinMember) { + if (!builderMembers.containsKey(mixinMember.getMemberName())) { + return false; + } + + MemberShape localMember = builderMembers.get(mixinMember.getMemberName()).build(); + return !localMember.getTarget().equals(mixinMember.getTarget()); + } + private > Optional buildShape( B builder, TraitContainer resolvedTraits diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java index 31e71a2fc57..974b1e35d70 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/transform/ModelTransformerTest.java @@ -172,7 +172,8 @@ public static String[] flattenShapesData() { "mixins-with-mixin-local-traits", "operations", "resources", - "services" + "services", + "idl-mixins-redefine-member" }; } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy index eac6aab99ba..089c39c394a 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy @@ -5,17 +5,21 @@ namespace smithy.example @mixin structure Foo { foo: String + bar: String } structure Baz with Foo { - foo: String // cannot redefine mixin members! + foo: Integer // cannot redefine mixin members! + bar: String // This is allowed because the target hasn't changed } @mixin structure Bam with Foo { - foo: String // cannot redefine mixin members! + foo: Integer // cannot redefine mixin members! + bar: String // This is allowed because the target hasn't changed } structure Boo with Bam { - foo: String // cannot redefine mixin members! + foo: Long // cannot redefine mixin members! + bar: String // This is allowed because the target hasn't changed } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.flattened.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.flattened.smithy new file mode 100644 index 00000000000..02b2ea6a820 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.flattened.smithy @@ -0,0 +1,9 @@ +$version: "2.0" + +namespace smithy.example + +structure MixedStructure { + @required + @internal + redefineable: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.json b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.json new file mode 100644 index 00000000000..b846559ccca --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.json @@ -0,0 +1,33 @@ +{ + "smithy": "2.0", + "shapes": { + "smithy.example#MixinStructure": { + "type": "structure", + "members": { + "redefineable": { + "target": "smithy.api#String", + "traits": { + "smithy.api#internal": {} + } + } + }, + "traits": { + "smithy.api#mixin": {} + } + }, + "smithy.example#MixedStructure": { + "type": "structure", + "members": { + "redefineable": { + "target": "smithy.api#String", + "traits": { + "smithy.api#required": {} + } + } + }, + "mixins": [{ + "target": "smithy.example#MixinStructure" + }] + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.smithy new file mode 100644 index 00000000000..a9d896fd98c --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.smithy @@ -0,0 +1,17 @@ +$version: "2.0" + +namespace smithy.example + +@mixin +structure MixinStructure { + @internal + redefineable: String +} + +structure MixedStructure with MixinStructure { + // Since the target hasn't changed, this is an acceptable redefinition. + // Traits are still inherited as normal, they just don't have to use + // apply to introduce new ones. + @required + redefineable: String +} From 44304caa6c8e1dc4488aea14f5c94682b924f4ed Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Thu, 6 Jan 2022 16:45:26 +0100 Subject: [PATCH 04/10] Update mixins to use square brackets --- designs/mixins.md | 62 ++++++++----------- .../jsonschema/model-with-mixins.smithy | 2 +- .../smithy/model/loader/IdlModelParser.java | 32 +++------- .../shapes/SmithyIdlModelSerializer.java | 24 ++++--- .../mixin-illegal-redefined-member.smithy | 6 +- .../mixins/mixin-trait-applied.smithy | 2 +- .../smithy/model/knowledge/premixin.smithy | 6 +- .../invalid/mixins/dangling-with.smithy | 2 +- .../mixins/missing-rest-of-with-word.smithy | 4 +- .../loader/invalid/mixins/mix-types.smithy | 2 +- .../mixins-usage-in-explicit-1-0.smithy | 4 +- .../mixins-usage-in-implicit-1-0.smithy | 4 +- .../invalid/mixins/with-but-no-ids.smithy | 4 +- .../mixins/mixin-conflict-acceptable-1.smithy | 2 +- .../mixins/mixin-conflict-acceptable-2.smithy | 2 +- .../loader/mixins/mixin-conflict-error.smithy | 2 +- .../mixins/mixins-can-override-traits.smithy | 8 +-- .../loader/valid/inline-io/inline-io.smithy | 8 +-- .../mixins/idl-mixins-redefine-member.smithy | 2 +- .../loads-mixins-with-whitespace.smithy | 4 +- .../loader/valid/mixins/loads-mixins.smithy | 46 +++++++------- .../loader/valid/mixins/mixin-names.smithy | 2 +- .../mixins-with-members-and-traits.smithy | 8 +-- .../valid/mixins/mixins-with-members.smithy | 8 +-- .../mixins-with-mixin-local-traits.smithy | 2 +- .../loader/valid/mixins/operations.smithy | 2 +- .../loader/valid/mixins/resources.smithy | 2 +- .../model/loader/valid/mixins/services.smithy | 2 +- .../selector/structure-with-mixins.smithy | 4 +- .../cases/data-mixins.smithy | 32 +++++----- .../idl-serialization/cases/inline-io.smithy | 8 +-- .../idl-serialization/cases/mixins.smithy | 17 ++--- .../cases/operation-mixins.smithy | 2 +- .../cases/resource-mixins.smithy | 2 +- .../cases/service-mixins-with-merging.smithy | 2 +- .../cases/service-mixins.smithy | 2 +- .../transform/mixin-removal/model.smithy | 12 ++-- .../without-a-a2-a3-b-b2-b3.smithy | 2 +- .../mixin-removal/without-a-a2-a3.smithy | 8 +-- .../mixin-removal/without-a-a2.smithy | 8 +-- .../mixin-removal/without-a-b.smithy | 8 +-- .../transform/mixin-removal/without-a.smithy | 10 +-- .../transform/mixin-removal/without-a2.smithy | 8 +-- .../transform/mixin-removal/without-a3.smithy | 10 +-- .../transform/mixin-removal/without-b.smithy | 10 +-- .../transform/mixin-removal/without-b2.smithy | 8 +-- .../transform/mixin-removal/without-b3.smithy | 10 +-- .../transform/mixin-removal/without-c.smithy | 8 +-- .../transform/mixin-removal/without-d.smithy | 10 +-- .../fromsmithy/model-with-mixins.smithy | 2 +- 50 files changed, 205 insertions(+), 232 deletions(-) diff --git a/designs/mixins.md b/designs/mixins.md index cb75b13711c..a3960b8f55d 100644 --- a/designs/mixins.md +++ b/designs/mixins.md @@ -120,7 +120,7 @@ shape marked with the `@mixin` trait. Shapes can only use mixins that are of the same shape type. ``` -structure GetCityInput with CityResourceInput { +structure GetCityInput with [CityResourceInput] { foo: String } ``` @@ -128,10 +128,10 @@ structure GetCityInput with CityResourceInput { Multiple mixins can be applied: ``` -structure GetAnotherCityInput with +structure GetAnotherCityInput with [ CityResourceInput SomeOtherMixin -{ +]{ foo: String } ``` @@ -145,11 +145,11 @@ structure MixinA { } @mixin -structure MixinB with MixinA { +structure MixinB with [MixinA] { b: String } -structure C with MixinB { +structure C with [MixinB] { c: String } ``` @@ -181,7 +181,7 @@ union UserActions { unsubscribe: UnsubscribeAction, } -union AdminActions with UserAction { +union AdminActions with [UserAction] { banUser: BanUserAction, promoteToAdmin: PromoteToAdminAction } @@ -204,7 +204,7 @@ structure UserInfo { userId: String } -structure UserSummary with UserInfo {} +structure UserSummary with [UserInfo] {} ``` Is equivalent to the following flattened structure because it inherits the @@ -230,7 +230,7 @@ structure UserInfo { /// Specific documentation @tags(["replaced-tags"]) -structure UserSummary with UserInfo {} +structure UserSummary with [UserInfo] {} ``` Is equivalent to the following flattened structure because it inherits the @@ -268,13 +268,11 @@ structure StructB {} /// C @threeTrait @mixin -structure StructC with - StructA - StructB {} +structure StructC with [StructA StructB] {} /// D @fourTrait -structure StructD with StructC {} +structure StructD with [StructC] {} ``` Is equivalent to the following flattened structure: @@ -325,7 +323,7 @@ structure PrivateMixin { foo: String } -structure PublicShape with PrivateMixin {} +structure PublicShape with [PrivateMixin] {} ``` `PublicShape` is equivalent to the following flattened structure: @@ -462,7 +460,7 @@ structure MyMixin { mixinMember: String } -structure MyStruct with MyMixin {} +structure MyStruct with [MyMixin] {} apply MyStruct$mixinMember @documentation("Specific docs") ``` @@ -478,7 +476,7 @@ structure MyMixin { mixinMember: String } -structure MyStruct with MyMixin { +structure MyStruct with [MyMixin] { /// Specific docs mixinMember: String } @@ -533,10 +531,10 @@ Mixins MUST NOT introduce circular references. The following model is invalid: ``` @mixin -structure CycleA with CycleB {} +structure CycleA with [CycleB] {} @mixin -structure CycleB with CycleA {} +structure CycleB with [CycleA] {} ``` @@ -557,9 +555,7 @@ structure A2 { a: Integer } -structure Invalid with - A1 - A2 {} +structure Invalid with [A1 A2] {} ``` The following model is also invalid, but not specifically because of mixins. @@ -577,9 +573,7 @@ structure A2 { A: Integer } -structure Invalid with - A1 - A2 {} +structure Invalid with [A1 A2] {} ``` A shape MAY use a member name that has already defined, but if it does it MUST @@ -599,9 +593,7 @@ structure A2 { a: String } -structure Valid with - A1 - A2 {} +structure Valid with [A1 A2] {} ``` @@ -623,7 +615,7 @@ union_statement = "union" ws identifier ws [mixins ws] union_members service_statement = "service" ws identifier ws [mixins ws] node_object operation_statement = "operation" ws identifier ws [mixins ws] node_object resource_statement = "resource" ws identifier ws [mixins ws] node_object -mixins = "with" 1*(ws shape_id) +mixins = "with" ws "[" 1*(ws shape_id) ws "]" ``` @@ -684,7 +676,7 @@ structure PaginatedInput { pageSize: Integer } -structure ListSomethingInput with PaginatedInput { +structure ListSomethingInput with [PaginatedInput] { nameFilter: String } ``` @@ -761,10 +753,10 @@ structure PaginatedInput { pageSize: Integer } -structure ListSomethingInput with +structure ListSomethingInput with [ PaginatedInput FilteredByName -{ +]{ sizeFilter: Integer } ``` @@ -800,7 +792,7 @@ service A { operation OperationB {} @mixin -service B with A { +service B with [A] { version: "B" rename: { "smithy.example#OperationA": "OperA" @@ -811,7 +803,7 @@ service B with A { operation OperationC {} -service C with B { +service C with [B] { version: "C" rename: { "smithy.example#OperationA": "OpA" @@ -981,7 +973,7 @@ mixin UserActions { subscribe: SubscribeAction, } -union AdminActions with UserAction {} +union AdminActions with [UserAction] {} ``` The `required` trait on `UserActions$subscribe` is valid, but it makes it so @@ -1024,7 +1016,7 @@ structure FooMixin { otherMember: String } -structure ApplicationOfFooMixin with FooMixin { +structure ApplicationOfFooMixin with [FooMixin] { // Remove the required trait from this member. @override([omitTraits: [required]]) someMember: String @@ -1048,7 +1040,7 @@ structure FooMixinOptional { } @mixin -structure FooMixinRequired with FooMixinOptional {} +structure FooMixinRequired with [FooMixinOptional] {} apply FooMixinRequired$someMember @required ``` diff --git a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/model-with-mixins.smithy b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/model-with-mixins.smithy index 2b78022d96b..4e109f46b1f 100644 --- a/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/model-with-mixins.smithy +++ b/smithy-jsonschema/src/test/resources/software/amazon/smithy/jsonschema/model-with-mixins.smithy @@ -7,6 +7,6 @@ structure Mixin { foo: String } -structure UsesMixin with Mixin { +structure UsesMixin with [Mixin] { baz: String } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java index cc99b890398..11f1ea4646f 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java @@ -589,36 +589,16 @@ private void parseMixins(ShapeId id) { } ws(); + expect('['); + ws(); + do { clearPendingDocs(); String target = ParserUtils.parseShapeId(this); modelFile.addForwardReference(target, resolved -> modelFile.addPendingMixin(id, resolved)); ws(); - } while (!peekEndWith()); - } - - private boolean peekEndWith() { - if (peek() == '{' || peek() == '@' || eof()) { - return true; - } - - // We could make this more efficient with a prefix trie - for (String shapeType : SHAPE_TYPES) { - if (peekString(shapeType)) { - return true; - } - } - - return false; - } - - private boolean peekString(String possibility) { - for (int i = 0; i < possibility.length(); i++) { - if (peek(i) != possibility.charAt(i)) { - return false; - } - } - return !ParserUtils.isValidIdentifierCharacter(peek(possibility.length())); + } while (peek() != ']'); + expect(']'); } private void parseOperationStatement(ShapeId id, SourceLocation location) { @@ -724,6 +704,7 @@ private void parseIdList(Consumer consumer) { private void parseServiceStatement(ShapeId id, SourceLocation location) { ws(); parseMixins(id); + ws(); ServiceShape.Builder builder = new ServiceShape.Builder().id(id).source(location); ObjectNode shapeNode = IdlNodeParser.parseObjectNode(this, id.toString()); LoaderUtils.checkForAdditionalProperties(shapeNode, id, SERVICE_PROPERTY_NAMES, modelFile.events()); @@ -754,6 +735,7 @@ private void optionalIdList(ObjectNode node, String name, Consumer cons private void parseResourceStatement(ShapeId id, SourceLocation location) { ws(); parseMixins(id); + ws(); ResourceShape.Builder builder = ResourceShape.builder().id(id).source(location); modelFile.onShape(builder); ObjectNode shapeNode = IdlNodeParser.parseObjectNode(this, id.toString()); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java index 6a355f01c66..808a9f37839 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java @@ -393,7 +393,7 @@ private static final class ShapeSerializer extends ShapeVisitor.Default { protected Void getDefault(Shape shape) { serializeTraits(shape); codeWriter.writeInline("$L $L ", shape.getType(), shape.getId().getName()); - writeMixins(shape, false); + writeMixins(shape); codeWriter.write("").write(""); return null; } @@ -412,24 +412,22 @@ private void shapeWithMembers(Shape shape, List members) { serializeTraits(shape); codeWriter.writeInline("$L $L ", shape.getType(), shape.getId().getName()); - writeMixins(shape, !nonMixinMembers.isEmpty()); + writeMixins(shape); writeShapeMembers(nonMixinMembers); codeWriter.write(""); applyIntroducedTraits(mixinMembers); } - private void writeMixins(Shape shape, boolean hasNonMixinMembers) { + private void writeMixins(Shape shape) { if (shape.getMixins().size() == 1) { - codeWriter.writeInline("with $I ", shape.getMixins().iterator().next()); + codeWriter.writeInline("with [$I] ", shape.getMixins().iterator().next()); } else if (shape.getMixins().size() > 1) { - codeWriter.writeInline("with"); + codeWriter.write("with [").indent(); for (ShapeId id : shape.getMixins()) { // Trailing spaces are trimmed. - codeWriter.writeInline("\n $I ", id); - } - if (hasNonMixinMembers) { - codeWriter.write(""); + codeWriter.write("$I", id); } + codeWriter.dedent().writeInline("] "); } } @@ -550,7 +548,7 @@ public Void unionShape(UnionShape shape) { public Void serviceShape(ServiceShape shape) { serializeTraits(shape); codeWriter.writeInline("service $L ", shape.getId().getName()); - writeMixins(shape, false); + writeMixins(shape); codeWriter.openBlock("{"); ServiceShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asServiceShape().get(); @@ -577,7 +575,7 @@ public Void serviceShape(ServiceShape shape) { public Void resourceShape(ResourceShape shape) { serializeTraits(shape); codeWriter.writeInline("resource $L ", shape.getId().getName()); - writeMixins(shape, false); + writeMixins(shape); codeWriter.openBlock("{"); if (!shape.getIdentifiers().isEmpty()) { codeWriter.openBlock("identifiers: {"); @@ -608,7 +606,7 @@ public Void operationShape(OperationShape shape) { OperationShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asOperationShape().get(); serializeTraits(shape); codeWriter.writeInline("operation $L ", shape.getId().getName()); - writeMixins(shape, false); + writeMixins(shape); codeWriter.openBlock("{"); List mixinMembers = new ArrayList<>(); mixinMembers.addAll(writeInlineableProperty("input", shape.getInputShape(), InputTrait.ID)); @@ -650,7 +648,7 @@ private Collection writeInlineableProperty(String key, ShapeId shap } } - writeMixins(structure, !nonMixinMembers.isEmpty()); + writeMixins(structure); writeShapeMembers(nonMixinMembers); if (!hasOnlyDefaultTrait(structure, defaultTrait)) { diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy index 089c39c394a..2a6455bb470 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy @@ -8,18 +8,18 @@ structure Foo { bar: String } -structure Baz with Foo { +structure Baz with [Foo] { foo: Integer // cannot redefine mixin members! bar: String // This is allowed because the target hasn't changed } @mixin -structure Bam with Foo { +structure Bam with [Foo] { foo: Integer // cannot redefine mixin members! bar: String // This is allowed because the target hasn't changed } -structure Boo with Bam { +structure Boo with [Bam] { foo: Long // cannot redefine mixin members! bar: String // This is allowed because the target hasn't changed } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-trait-applied.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-trait-applied.smithy index a7e48f4e018..c234a49322b 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-trait-applied.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-trait-applied.smithy @@ -6,7 +6,7 @@ namespace smithy.example structure mixinTrait {} // This is fine, and it makes this shape a trait! -structure usesMixinTrait with mixinTrait {} +structure usesMixinTrait with [mixinTrait] {} @usesMixinTrait structure FineUseOfTrait {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy index cc59b3a18eb..0e7afe6e25d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy @@ -3,7 +3,7 @@ $version: "2.0" namespace smithy.example @private -service MixedService with MixinService { +service MixedService with [MixinService] { version: "2022-01-01" operations: [ MixedServiceOperation @@ -50,7 +50,7 @@ operation MixinOperation { } @private -operation MixedOperation with MixinOperation { +operation MixedOperation with [MixinOperation] { errors: [MixedError] } @@ -84,6 +84,6 @@ string MixinRename string MixinString @private -string MixedString with MixinString +string MixedString with [MixinString] string OverriddenRename diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/dangling-with.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/dangling-with.smithy index f55462a2ffe..b8319bb95cb 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/dangling-with.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/dangling-with.smithy @@ -1,4 +1,4 @@ -// Parse error at line 6, column 1 near ``: Expected a valid identifier character, but found '' +// Parse error at line 6, column 1 near ``: Expected: '[', but found '' $version: "2" namespace com.foo diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/missing-rest-of-with-word.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/missing-rest-of-with-word.smithy index d7e31186a52..f038cf9b8f6 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/missing-rest-of-with-word.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/missing-rest-of-with-word.smithy @@ -1,5 +1,5 @@ -// Parse error at line 5, column 18 near ` Baz`: Expected: 'h', but found ' ' +// Parse error at line 5, column 18 near ` `: Expected: 'h', but found ' ' $version: "2" namespace com.foo -structure Foo wit Baz {} +structure Foo wit [Baz] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mix-types.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mix-types.smithy index 823ceaf93f3..7c415c1b3cb 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mix-types.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mix-types.smithy @@ -7,4 +7,4 @@ union Bar { first: String } -structure Foo with Bar {} +structure Foo with [Bar] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mixins-usage-in-explicit-1-0.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mixins-usage-in-explicit-1-0.smithy index 5634323e0e4..3de5b363575 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mixins-usage-in-explicit-1-0.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mixins-usage-in-explicit-1-0.smithy @@ -1,5 +1,5 @@ -// Parse error at line 5, column 19 near ` Bar`: Mixins can only be used with Smithy version 2 or later. Attempted to use mixins with version `1.0`. +// Parse error at line 5, column 19 near ` `: Mixins can only be used with Smithy version 2 or later. Attempted to use mixins with version `1.0`. $version: "1.0" namespace smithy.example -structure Foo with Bar {} +structure Foo with [Bar] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mixins-usage-in-implicit-1-0.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mixins-usage-in-implicit-1-0.smithy index 22291e3be0a..df8a67024d4 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mixins-usage-in-implicit-1-0.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/mixins-usage-in-implicit-1-0.smithy @@ -1,4 +1,4 @@ -// Parse error at line 4, column 19 near ` Bar`: Mixins can only be used with Smithy version 2 or later. Attempted to use mixins with version `1.0`. +// Parse error at line 4, column 19 near ` `: Mixins can only be used with Smithy version 2 or later. Attempted to use mixins with version `1.0`. namespace smithy.example -structure Foo with Bar {} +structure Foo with [Bar] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-but-no-ids.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-but-no-ids.smithy index 2a32ac1a8b1..f9fd01569ba 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-but-no-ids.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-but-no-ids.smithy @@ -1,5 +1,5 @@ -// Parse error at line 5, column 20 near `{}`: Expected a valid identifier character, but found '{' +// Parse error at line 5, column 21 near `] `: Expected a valid identifier character, but found ']' $version: "2" namespace com.foo -structure Foo with {} +structure Foo with [] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-acceptable-1.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-acceptable-1.smithy index 09fd6389650..a39bc837ed8 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-acceptable-1.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-acceptable-1.smithy @@ -3,7 +3,7 @@ $version: "2.0" namespace smithy.example @mixin -structure A with B { +structure A with [B] { a: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-acceptable-2.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-acceptable-2.smithy index 37e2185b578..412a4111324 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-acceptable-2.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-acceptable-2.smithy @@ -3,6 +3,6 @@ $version: "2.0" namespace smithy.example @mixin -structure A with B { +structure A with [B] { a: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-error.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-error.smithy index 0307fa5a8d6..a299c4ec41d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-error.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixin-conflict-error.smithy @@ -4,6 +4,6 @@ $version: "2.0" namespace smithy.example @mixin -structure A with B { +structure A with [B] { a: Integer } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixins-can-override-traits.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixins-can-override-traits.smithy index 6c73aac11c4..5edeb5f4a5d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixins-can-override-traits.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/mixins/mixins-can-override-traits.smithy @@ -12,7 +12,7 @@ structure A { /// B @deprecated @mixin -structure B with A { +structure B with [A] { /// B.b b: String } @@ -28,7 +28,7 @@ structure C { /// D @mixin @externalDocumentation(web: "http://example.com") -structure D with C { +structure D with [C] { /// D.d d: String } @@ -40,14 +40,14 @@ apply D$c @documentation("I've changed") /// E @since("X") @mixin -structure E with D { +structure E with [D] { /// E.e e: String } /// F @internal -structure F with B, E { +structure F with [B, E] { /// F.f f: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/inline-io/inline-io.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/inline-io/inline-io.smithy index 2438c748680..858368f5d09 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/inline-io/inline-io.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/inline-io/inline-io.smithy @@ -30,11 +30,11 @@ structure NameBearer { } operation UsesMixins { - input := with NameBearer { + input := with [NameBearer] { id: String } - output := with NameBearer { + output := with [NameBearer] { id: String } } @@ -42,13 +42,13 @@ operation UsesMixins { operation UsesTraitsAndMixins { input := @sensitive - with NameBearer { + with [NameBearer] { id: String } output := @sensitive - with NameBearer { + with [NameBearer] { id: String } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.smithy index a9d896fd98c..96d901f885d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/idl-mixins-redefine-member.smithy @@ -8,7 +8,7 @@ structure MixinStructure { redefineable: String } -structure MixedStructure with MixinStructure { +structure MixedStructure with [MixinStructure] { // Since the target hasn't changed, this is an acceptable redefinition. // Traits are still inherited as normal, they just don't have to use // apply to introduce new ones. diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins-with-whitespace.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins-with-whitespace.smithy index 3a2ac3b7d70..17d4082292d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins-with-whitespace.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins-with-whitespace.smithy @@ -8,8 +8,8 @@ structure A {} @mixin structure B {} -structure C with +structure C with [ A ,,, - , B ,,, , {} + , B ,,, , ] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.smithy index f2784592b91..5055db8be9b 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/loads-mixins.smithy @@ -5,97 +5,97 @@ namespace smithy.example @mixin structure A {} -structure B with A {} +structure B with [A] {} @mixin structure C {} @mixin -structure D with C {} +structure D with [C] {} @mixin -structure E with D {} +structure E with [D] {} -structure F with A, E {} +structure F with [A, E] {} @mixin blob MixinBlob -blob MixedBlob with MixinBlob +blob MixedBlob with [MixinBlob] @mixin boolean MixinBoolean -boolean MixedBoolean with MixinBoolean +boolean MixedBoolean with [MixinBoolean] @mixin string MixinString -string MixedString with MixinString +string MixedString with [MixinString] @mixin byte MixinByte -byte MixedByte with MixinByte +byte MixedByte with [MixinByte] @mixin short MixinShort -short MixedShort with MixinShort +short MixedShort with [MixinShort] @mixin integer MixinInteger -integer MixedInteger with MixinInteger +integer MixedInteger with [MixinInteger] @mixin long MixinLong -long MixedLong with MixinLong +long MixedLong with [MixinLong] @mixin float MixinFloat -float MixedFloat with MixinFloat +float MixedFloat with [MixinFloat] @mixin double MixinDouble -double MixedDouble with MixinDouble +double MixedDouble with [MixinDouble] @mixin bigInteger MixinBigInt -bigInteger MixedBigInt with MixinBigInt +bigInteger MixedBigInt with [MixinBigInt] @mixin bigDecimal MixinBigDecimal -bigDecimal MixedBigDecimal with MixinBigDecimal +bigDecimal MixedBigDecimal with [MixinBigDecimal] @mixin timestamp MixinTimestamp -timestamp MixedTimestamp with MixinTimestamp +timestamp MixedTimestamp with [MixinTimestamp] @mixin document MixinDocument -document MixedDocument with MixinDocument +document MixedDocument with [MixinDocument] @mixin list MixinList { member: String } -list MixedList with MixinList {} +list MixedList with [MixinList] {} @mixin set MixinSet { member: String } -set MixedSet with MixinSet {} +set MixedSet with [MixinSet] {} @mixin map MixinMap { @@ -103,19 +103,19 @@ map MixinMap { value: String } -map MixedMap with MixinMap {} +map MixedMap with [MixinMap] {} @mixin service MixinService {} -service MixedService with MixinService {} +service MixedService with [MixinService] {} @mixin resource MixinResource {} -resource MixedResource with MixinResource {} +resource MixedResource with [MixinResource] {} @mixin operation MixinOperation {} -operation MixedOperation with MixinOperation {} +operation MixedOperation with [MixinOperation] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.smithy index 26cc2476420..69359715e82 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixin-names.smithy @@ -16,6 +16,6 @@ integer NotPrefixed @mixin integer iiiiiii -integer MixedInt with NotPrefixed integerPrefixed iiiiiii +integer MixedInt with [NotPrefixed integerPrefixed iiiiiii] integer SafeInt diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-members-and-traits.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-members-and-traits.smithy index 2185be685ac..7d9e89d9535 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-members-and-traits.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-members-and-traits.smithy @@ -13,7 +13,7 @@ structure A { /// B @deprecated @mixin -structure B with A { +structure B with [A] { /// B.b b: String } @@ -29,7 +29,7 @@ structure C { /// D @mixin @externalDocumentation(web: "http://example.com") -structure D with C { +structure D with [C] { /// D.d d: String } @@ -41,14 +41,14 @@ apply D$c @documentation("I've changed") /// E @since("X") @mixin -structure E with D { +structure E with [D] { /// E.e e: String } /// F @internal -structure F with B, E { +structure F with [B, E] { /// F.f f: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-members.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-members.smithy index 3a084c370d6..d7ddbe8b4ce 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-members.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-members.smithy @@ -8,7 +8,7 @@ structure A { } @mixin -structure B with A { +structure B with [A] { b: String } @@ -18,15 +18,15 @@ structure C { } @mixin -structure D with C { +structure D with [C] { d: String } @mixin -structure E with D { +structure E with [D] { e: String } -structure F with B, E { +structure F with [B, E] { f: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-mixin-local-traits.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-mixin-local-traits.smithy index e4091a58873..70169b1123a 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-mixin-local-traits.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/mixins-with-mixin-local-traits.smithy @@ -11,4 +11,4 @@ structure PrivateMixin { foo: String } -structure PublicShape with PrivateMixin {} +structure PublicShape with [PrivateMixin] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.smithy index 713ab580aab..368e9ea0967 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/operations.smithy @@ -14,6 +14,6 @@ operation InternalMixin { errors: [MixinError] } -operation ConcreteOperation with InternalMixin { +operation ConcreteOperation with [InternalMixin] { errors: [ConcreteError] } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.smithy index 0c3c9c86892..b06a60d054d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/resources.smithy @@ -6,4 +6,4 @@ namespace smithy.example @internal resource MixinResource {} -resource MixedResource with MixinResource {} +resource MixedResource with [MixinResource] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.smithy index 5f5e9c2bd75..7b8085ed568 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/valid/mixins/services.smithy @@ -25,4 +25,4 @@ service MixinService { resources: [MixinResource] } -service MixedService with MixinService {} +service MixedService with [MixinService] {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/selector/structure-with-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/selector/structure-with-mixins.smithy index 4e99ef54e70..650dbfbafeb 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/selector/structure-with-mixins.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/selector/structure-with-mixins.smithy @@ -6,9 +6,9 @@ namespace smithy.example structure Mixin1 {} @mixin -structure Mixin2 with Mixin1 {} +structure Mixin2 with [Mixin1] {} -structure Concrete with Mixin2 {} +structure Concrete with [Mixin2] {} structure NoMixins {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/data-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/data-mixins.smithy index 19ae396cee8..1aa73197fe0 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/data-mixins.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/data-mixins.smithy @@ -2,7 +2,7 @@ $version: "2.0" namespace smithy.example -list MixedList with MixinList {} +list MixedList with [MixinList] {} @internal @mixin @@ -10,7 +10,7 @@ list MixinList { member: String } -set MixedSet with MixinSet {} +set MixedSet with [MixinSet] {} @internal @mixin @@ -18,7 +18,7 @@ set MixinSet { member: String } -map MixedMap with MixinMap {} +map MixedMap with [MixinMap] {} @internal @mixin @@ -27,31 +27,31 @@ map MixinMap { value: String } -bigDecimal MixedBigDecimal with MixinBigDecimal +bigDecimal MixedBigDecimal with [MixinBigDecimal] -bigInteger MixedBigInt with MixinBigInt +bigInteger MixedBigInt with [MixinBigInt] -blob MixedBlob with MixinBlob +blob MixedBlob with [MixinBlob] -boolean MixedBoolean with MixinBoolean +boolean MixedBoolean with [MixinBoolean] -byte MixedByte with MixinByte +byte MixedByte with [MixinByte] -document MixedDocument with MixinDocument +document MixedDocument with [MixinDocument] -double MixedDouble with MixinDouble +double MixedDouble with [MixinDouble] -float MixedFloat with MixinFloat +float MixedFloat with [MixinFloat] -integer MixedInteger with MixinInteger +integer MixedInteger with [MixinInteger] -long MixedLong with MixinLong +long MixedLong with [MixinLong] -short MixedShort with MixinShort +short MixedShort with [MixinShort] -string MixedString with MixinString +string MixedString with [MixinString] -timestamp MixedTimestamp with MixinTimestamp +timestamp MixedTimestamp with [MixinTimestamp] @internal @mixin diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/inline-io.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/inline-io.smithy index 521b35b0738..3b192bcc73a 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/inline-io.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/inline-io.smithy @@ -31,10 +31,10 @@ operation HasDocComments { } operation UsesMixins { - input := with NameBearer { + input := with [NameBearer] { id: String } - output := with NameBearer { + output := with [NameBearer] { id: String } } @@ -55,12 +55,12 @@ operation UsesTraits { operation UsesTraitsAndMixins { input := @sensitive - with NameBearer { + with [NameBearer] { id: String } output := @sensitive - with NameBearer { + with [NameBearer] { id: String } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/mixins.smithy index 68d19cb55c5..1c3585cf4ea 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/mixins.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/mixins.smithy @@ -13,7 +13,7 @@ structure A { /// B @deprecated @mixin -structure B with A { +structure B with [A] { /// B.b b: String } @@ -33,7 +33,7 @@ structure C { web: "http://example.com" ) @mixin -structure D with C { +structure D with [C] { /// D.d d: String } @@ -46,17 +46,17 @@ apply D$c { /// E @mixin @since("X") -structure E with D { +structure E with [D] { /// E.e e: String } /// F @internal -structure F with +structure F with [ B E -{ +] { /// F.f f: String } @@ -68,10 +68,11 @@ apply F$a { apply F$e @sensitive -structure G with +structure G with [ B - E {} + E +] {} -structure H with B {} +structure H with [B] {} structure I {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/operation-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/operation-mixins.smithy index 24a041e3f45..074f742ebfd 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/operation-mixins.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/operation-mixins.smithy @@ -2,7 +2,7 @@ $version: "2.0" namespace smithy.example -operation ConcreteOperation with InternalMixin { +operation ConcreteOperation with [InternalMixin] { input: Unit output: Unit errors: [ diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/resource-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/resource-mixins.smithy index a8c89fbe8c4..325a6f403ba 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/resource-mixins.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/resource-mixins.smithy @@ -2,7 +2,7 @@ $version: "2.0" namespace smithy.example -resource MixedResource with MixinResource { +resource MixedResource with [MixinResource] { } @internal diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins-with-merging.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins-with-merging.smithy index 16ac8196e5c..0cbe5249ea4 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins-with-merging.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins-with-merging.smithy @@ -2,7 +2,7 @@ $version: "2.0" namespace smithy.example -service MixedService with MixinService { +service MixedService with [MixinService] { version: "2022-01-01" operations: [ MixedOperation diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins.smithy index ce3b75f3c7b..f98a1ab14a0 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/cases/service-mixins.smithy @@ -2,7 +2,7 @@ $version: "2.0" namespace smithy.example -service MixedService with MixinService { +service MixedService with [MixinService] { } @internal diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/model.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/model.smithy index 2082229ef3a..aedd4db24ae 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/model.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/model.smithy @@ -9,14 +9,14 @@ structure A { } @mixin -structure A2 with A { +structure A2 with [A] { a2: String } apply A2$a @documentation("A2") @mixin -structure A3 with A2 { +structure A3 with [A2] { a3: String } @@ -28,23 +28,23 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with A3, B3 { +structure C with [A3, B3] { c: String } apply C$a @documentation("C") -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2-a3-b-b2-b3.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2-a3-b-b2-b3.smithy index b7a0b8d3b3a..3abdd383f49 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2-a3-b-b2-b3.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2-a3-b-b2-b3.smithy @@ -7,6 +7,6 @@ structure C { c: String } -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2-a3.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2-a3.smithy index 2a9ca83efce..58f96956cf8 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2-a3.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2-a3.smithy @@ -8,20 +8,20 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with B3 { +structure C with [B3] { c: String } -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2.smithy index dc5a55765fd..34da3740069 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-a2.smithy @@ -13,20 +13,20 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with A3, B3 { +structure C with [A3, B3] { c: String } -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-b.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-b.smithy index e95e468a6ce..53d18568f44 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-b.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a-b.smithy @@ -8,7 +8,7 @@ structure A2 { } @mixin -structure A3 with A2 { +structure A3 with [A2] { a3: String } @@ -18,15 +18,15 @@ structure B2 { } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with A3, B3 { +structure C with [A3, B3] { c: String } -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a.smithy index 327a8929f8d..54eea282916 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a.smithy @@ -8,7 +8,7 @@ structure A2 { } @mixin -structure A3 with A2 { +structure A3 with [A2] { a3: String } @@ -18,20 +18,20 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with A3, B3 { +structure C with [A3, B3] { c: String } -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a2.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a2.smithy index 90607a9ce22..7f0c3be9446 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a2.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a2.smithy @@ -19,20 +19,20 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with A3, B3 { +structure C with [A3, B3] { c: String } -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a3.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a3.smithy index 04f56c5b6cf..16942c7007e 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a3.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-a3.smithy @@ -9,7 +9,7 @@ structure A { } @mixin -structure A2 with A { +structure A2 with [A] { a2: String } @@ -21,20 +21,20 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with B3 { +structure C with [B3] { c: String } -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b.smithy index 8bd7c4c6f80..d80b7402d09 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b.smithy @@ -9,14 +9,14 @@ structure A { } @mixin -structure A2 with A { +structure A2 with [A] { a2: String } apply A2$a @documentation("A2") @mixin -structure A3 with A2 { +structure A3 with [A2] { a3: String } @@ -28,18 +28,18 @@ structure B2 { } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with A3, B3 { +structure C with [A3, B3] { c: String } apply C$a @documentation("C") -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b2.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b2.smithy index 531921ab821..5056fe8440d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b2.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b2.smithy @@ -9,14 +9,14 @@ structure A { } @mixin -structure A2 with A { +structure A2 with [A] { a2: String } apply A2$a @documentation("A2") @mixin -structure A3 with A2 { +structure A3 with [A2] { a3: String } @@ -33,13 +33,13 @@ structure B3 { } @mixin -structure C with A3, B3 { +structure C with [A3, B3] { c: String } apply C$a @documentation("C") -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b3.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b3.smithy index a8ad265cdde..94cbb77633d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b3.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-b3.smithy @@ -9,14 +9,14 @@ structure A { } @mixin -structure A2 with A { +structure A2 with [A] { a2: String } apply A2$a @documentation("A2") @mixin -structure A3 with A2 { +structure A3 with [A2] { a3: String } @@ -28,18 +28,18 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure C with A3 { +structure C with [A3] { c: String } apply C$a @documentation("C") -structure D with C { +structure D with [C] { d: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-c.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-c.smithy index 1a855bb381d..650c4ca6539 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-c.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-c.smithy @@ -12,14 +12,14 @@ structure A { } @mixin -structure A2 with A { +structure A2 with [A] { a2: String } apply A2$a @documentation("A2") @mixin -structure A3 with A2 { +structure A3 with [A2] { a3: String } @@ -31,12 +31,12 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-d.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-d.smithy index 50e5b9b4c6c..a82391c3c3d 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-d.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/transform/mixin-removal/without-d.smithy @@ -11,14 +11,14 @@ structure A { } @mixin -structure A2 with A { +structure A2 with [A] { a2: String } apply A2$a @documentation("A2") @mixin -structure A3 with A2 { +structure A3 with [A2] { a3: String } @@ -30,17 +30,17 @@ structure B { } @mixin -structure B2 with B { +structure B2 with [B] { b2: String } @mixin -structure B3 with B2 { +structure B3 with [B2] { b3: String } @mixin -structure C with A3, B3 { +structure C with [A3, B3] { c: String } diff --git a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/model-with-mixins.smithy b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/model-with-mixins.smithy index d0041360f8c..c9c09f68ba8 100644 --- a/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/model-with-mixins.smithy +++ b/smithy-openapi/src/test/resources/software/amazon/smithy/openapi/fromsmithy/model-with-mixins.smithy @@ -19,6 +19,6 @@ structure Mixin { greeting: String } -structure Output with Mixin { +structure Output with [Mixin] { language: String } From 5401ce359d9968ab4837385c0a81b2bfae8db935 Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Thu, 6 Jan 2022 19:48:40 +0100 Subject: [PATCH 05/10] Revert "Compute introduced properties after the fact" This reverts commit 11cb6758e8866f3410b4d4b3d1219fc5968a3b64. --- .../smithy/model/knowledge/PreMixinIndex.java | 222 ------------------ .../smithy/model/shapes/EntityShape.java | 35 ++- .../smithy/model/shapes/ModelSerializer.java | 31 +-- .../smithy/model/shapes/OperationShape.java | 17 +- .../smithy/model/shapes/ServiceShape.java | 64 ++++- .../shapes/SmithyIdlModelSerializer.java | 26 +- .../model/knowledge/PreMixinIndexTest.java | 42 ---- .../model/knowledge/premixin-unrolled.smithy | 89 ------- .../smithy/model/knowledge/premixin.smithy | 89 ------- 9 files changed, 124 insertions(+), 491 deletions(-) delete mode 100644 smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PreMixinIndex.java delete mode 100644 smithy-model/src/test/java/software/amazon/smithy/model/knowledge/PreMixinIndexTest.java delete mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin-unrolled.smithy delete mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PreMixinIndex.java b/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PreMixinIndex.java deleted file mode 100644 index d6d64ae7b6a..00000000000 --- a/smithy-model/src/main/java/software/amazon/smithy/model/knowledge/PreMixinIndex.java +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package software.amazon.smithy.model.knowledge; - -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.shapes.ListShape; -import software.amazon.smithy.model.shapes.MapShape; -import software.amazon.smithy.model.shapes.MemberShape; -import software.amazon.smithy.model.shapes.OperationShape; -import software.amazon.smithy.model.shapes.ServiceShape; -import software.amazon.smithy.model.shapes.SetShape; -import software.amazon.smithy.model.shapes.Shape; -import software.amazon.smithy.model.shapes.ShapeId; -import software.amazon.smithy.model.shapes.ShapeVisitor; -import software.amazon.smithy.model.shapes.StructureShape; -import software.amazon.smithy.model.shapes.UnionShape; - -/** - * Index of shapes stripped down to only their introduced properties. - * - *

This is primarily useful in serialization to determine what - * properties to actually serialize vs what is computed as part of - * mixins. - */ -public final class PreMixinIndex implements KnowledgeIndex { - - private final Map preMixinShapes = new HashMap<>(); - - public PreMixinIndex(Model model) { - MixinUnroller unroller = new MixinUnroller(model); - model.shapes() - .filter(shape -> !shape.isMemberShape()) - .forEach(shape -> { - if (!shape.getMixins().isEmpty()) { - preMixinShapes.put(shape.getId(), shape.accept(unroller)); - } - }); - } - - public static PreMixinIndex of(Model model) { - return model.getKnowledge(PreMixinIndex.class, PreMixinIndex::new); - } - - /** - * Gets a version of the shape that has mixin properties stripped out. - * - *

NOTE: mixin members with introduced traits WILL be present in - * their entirety. The only way to determine if those members originally - * were defined in a mixin is to have an original copy of the shape to - * compare against. Any members of the shape that themselves have mixins - * are inherited. - * - * @param shape The shape to strip mixin data from. - * @return A version of the shape without mixin data. - */ - public Shape getPreMixinShape(Shape shape) { - return preMixinShapes.getOrDefault(shape.getId(), shape); - } - - private static final class MixinUnroller extends ShapeVisitor.Default { - - private final Model model; - - private MixinUnroller(Model model) { - this.model = model; - } - - @Override - protected Shape getDefault(Shape shape) { - return Shape.shapeToBuilder(shape) - .clearMixins() - .traits(shape.getIntroducedTraits().values()) - .build(); - } - - @Override - public Shape listShape(ListShape shape) { - return shape.toBuilder() - .clearMixins() - .traits(shape.getIntroducedTraits().values()) - .member((MemberShape) shape.getMember().accept(this)) - .build(); - } - - @Override - public Shape setShape(SetShape shape) { - return shape.toBuilder() - .clearMixins() - .traits(shape.getIntroducedTraits().values()) - .member((MemberShape) shape.getMember().accept(this)) - .build(); - } - - @Override - public Shape mapShape(MapShape shape) { - return shape.toBuilder() - .clearMixins() - .traits(shape.getIntroducedTraits().values()) - .key((MemberShape) shape.getKey().accept(this)) - .value((MemberShape) shape.getValue().accept(this)) - .build(); - } - - @Override - public Shape structureShape(StructureShape shape) { - StructureShape.Builder builder = shape.toBuilder() - .clearMixins() - .traits(shape.getIntroducedTraits().values()); - unrollMembers(shape, builder::addMember); - return builder.build(); - } - - @Override - public Shape unionShape(UnionShape shape) { - UnionShape.Builder builder = shape.toBuilder() - .clearMixins() - .traits(shape.getIntroducedTraits().values()); - unrollMembers(shape, builder::addMember); - return builder.build(); - } - - private void unrollMembers(Shape shape, Consumer consumer) { - for (MemberShape member : shape.members()) { - if (member.getMixins().isEmpty()) { - consumer.accept(member); - } else { - consumer.accept((MemberShape) member.accept(this)); - } - } - } - - @Override - public Shape operationShape(OperationShape shape) { - OperationShape.Builder builder = shape.toBuilder() - .clearMixins() - .traits(shape.getIntroducedTraits().values()) - .clearErrors(); - - Set previousErrors = new HashSet<>(); - for (ShapeId mixinId : shape.getMixins()) { - previousErrors.addAll(model.expectShape(mixinId, OperationShape.class).getErrors()); - } - - addIntroduced(shape.getErrors(), previousErrors, builder::addError); - - return builder.build(); - } - - @Override - public Shape serviceShape(ServiceShape shape) { - ServiceShape.Builder builder = shape.toBuilder() - .clearMixins() - .clearOperations() - .clearResources() - .clearErrors() - .clearRename() - .traits(shape.getIntroducedTraits().values()); - - String previousVersion = ""; - Set previousOperations = new HashSet<>(); - Set previousResources = new HashSet<>(); - Set previousErrors = new HashSet<>(); - Map previousRename = new HashMap<>(); - - for (ShapeId mixinId : shape.getMixins()) { - ServiceShape mixin = model.expectShape(mixinId, ServiceShape.class); - previousVersion = mixin.getVersion(); - previousOperations.addAll(mixin.getOperations()); - previousResources.addAll(mixin.getResources()); - previousErrors.addAll(mixin.getErrors()); - previousRename.putAll(mixin.getRename()); - } - - if (shape.getVersion().equals(previousVersion)) { - builder.version(""); - } - - addIntroduced(shape.getOperations(), previousOperations, builder::addOperation); - addIntroduced(shape.getResources(), previousResources, builder::addResource); - addIntroduced(shape.getErrors(), previousErrors, builder::addError); - - for (Map.Entry entry : shape.getRename().entrySet()) { - if (!entry.getValue().equals(previousRename.get(entry.getKey()))) { - builder.putRename(entry.getKey(), entry.getValue()); - } - } - - return builder.build(); - } - - private void addIntroduced( - Collection current, - Collection previous, - Consumer consumer - ) { - for (ShapeId shapeId : current) { - if (!previous.contains(shapeId)) { - consumer.accept(shapeId); - } - } - } - } -} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java index 0ad3814fcd9..4d2173e840c 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java @@ -27,14 +27,18 @@ public abstract class EntityShape extends Shape { private final Set resources; + private final Set introducedResources; private final Set operations; + private final Set introducedOperations; EntityShape(Builder builder) { super(builder, false); if (getMixins().isEmpty()) { resources = builder.resources.copy(); + introducedResources = resources; operations = builder.operations.copy(); + introducedOperations = operations; } else { Set computedResources = new TreeSet<>(); Set computedOperations = new TreeSet<>(); @@ -46,8 +50,11 @@ public abstract class EntityShape extends Shape { computedOperations.addAll(mixin.getOperations()); } - computedResources.addAll(builder.resources.peek()); - computedOperations.addAll(builder.operations.peek()); + introducedResources = builder.resources.copy(); + introducedOperations = builder.operations.copy(); + + computedResources.addAll(introducedResources); + computedOperations.addAll(introducedOperations); resources = Collections.unmodifiableSet(computedResources); operations = Collections.unmodifiableSet(computedOperations); @@ -61,6 +68,16 @@ public final Set getResources() { return resources; } + /** + * Gets all the directly-bound resources introduced by this shape and + * not inherited from mixins. + * + * @return Gets the introduced resources directly-bound to the shape. + */ + public final Set getIntroducedResources() { + return introducedResources; + } + /** * Gets operations bound only through the "operations" property. * @@ -76,6 +93,20 @@ public final Set getOperations() { return operations; } + /** + * Gets operations bound through the "operations" property that + * were not inherited from mixins. + * + *

This will not include operations bound to resources using + * a lifecycle operation binding. This will also not include + * operations bound to this entity through sub-resources. + * + * @return Gets the introduced operations. + */ + public final Set getIntroducedOperations() { + return introducedOperations; + } + /** * Get all operations directly bound to this shape. * diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java index b9de110b9c8..0c31c645932 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ModelSerializer.java @@ -29,7 +29,6 @@ import java.util.stream.Stream; import software.amazon.smithy.model.Model; import software.amazon.smithy.model.SourceLocation; -import software.amazon.smithy.model.knowledge.PreMixinIndex; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.Node; @@ -67,7 +66,7 @@ private ModelSerializer(Builder builder) { } public ObjectNode serialize(Model model) { - ShapeSerializer shapeSerializer = new ShapeSerializer(model); + ShapeSerializer shapeSerializer = new ShapeSerializer(); ObjectNode.Builder builder = Node.objectNodeBuilder() .withMember("smithy", Node.from(Model.MODEL_VERSION)) @@ -205,11 +204,6 @@ private ObjectNode.Builder serializeTraits(ObjectNode.Builder builder, Collectio private final class ShapeSerializer extends ShapeVisitor.Default { private final Set mixinMemberTraits = new TreeSet<>(); - private final PreMixinIndex preMixinIndex; - - private ShapeSerializer(Model model) { - preMixinIndex = PreMixinIndex.of(model); - } private ObjectNode.Builder createTypedBuilder(Shape shape) { ObjectNode.Builder builder = Node.objectNodeBuilder() @@ -269,11 +263,10 @@ public Node mapShape(MapShape shape) { @Override public Node operationShape(OperationShape shape) { - OperationShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asOperationShape().get(); return serializeAllTraits(shape, createTypedBuilder(shape) .withMember("input", serializeReference(shape.getInputShape())) .withMember("output", serializeReference(shape.getOutputShape())) - .withOptionalMember("errors", createOptionalIdList(preMixinShape.getErrors()))) + .withOptionalMember("errors", createOptionalIdList(shape.getIntroducedErrors()))) .build(); } @@ -295,9 +288,9 @@ public Node resourceShape(ResourceShape shape) { .withOptionalMember("update", shape.getUpdate().map(this::serializeReference)) .withOptionalMember("delete", shape.getDelete().map(this::serializeReference)) .withOptionalMember("list", shape.getList().map(this::serializeReference)) - .withOptionalMember("operations", createOptionalIdList(shape.getOperations())) + .withOptionalMember("operations", createOptionalIdList(shape.getIntroducedOperations())) .withOptionalMember("collectionOperations", createOptionalIdList(shape.getCollectionOperations())) - .withOptionalMember("resources", createOptionalIdList(shape.getResources()))) + .withOptionalMember("resources", createOptionalIdList(shape.getIntroducedResources()))) .build(); } @@ -305,19 +298,17 @@ public Node resourceShape(ResourceShape shape) { public Node serviceShape(ServiceShape shape) { ObjectNode.Builder serviceBuilder = createTypedBuilder(shape); - ServiceShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asServiceShape().get(); - - if (!StringUtils.isBlank(preMixinShape.getVersion())) { - serviceBuilder.withMember("version", Node.from(preMixinShape.getVersion())); + if (!StringUtils.isBlank(shape.getIntroducedVersion())) { + serviceBuilder.withMember("version", Node.from(shape.getIntroducedVersion())); } - serviceBuilder.withOptionalMember("operations", createOptionalIdList(preMixinShape.getOperations())); - serviceBuilder.withOptionalMember("resources", createOptionalIdList(preMixinShape.getResources())); - serviceBuilder.withOptionalMember("errors", createOptionalIdList(preMixinShape.getErrors())); + serviceBuilder.withOptionalMember("operations", createOptionalIdList(shape.getIntroducedOperations())); + serviceBuilder.withOptionalMember("resources", createOptionalIdList(shape.getIntroducedResources())); + serviceBuilder.withOptionalMember("errors", createOptionalIdList(shape.getIntroducedErrors())); - if (!preMixinShape.getRename().isEmpty()) { + if (!shape.getIntroducedRename().isEmpty()) { ObjectNode.Builder renameBuilder = Node.objectNodeBuilder(); - for (Map.Entry entry : preMixinShape.getRename().entrySet()) { + for (Map.Entry entry : shape.getIntroducedRename().entrySet()) { renameBuilder.withMember(entry.getKey().toString(), entry.getValue()); } serviceBuilder.withMember("rename", renameBuilder.build()); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java index 98518c22d10..d2966c2085b 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java @@ -36,6 +36,7 @@ public final class OperationShape extends Shape implements ToSmithyBuilder errors; + private final List introducedErrors; private OperationShape(Builder builder) { super(builder, false); @@ -45,6 +46,7 @@ private OperationShape(Builder builder) { if (getMixins().isEmpty()) { errors = builder.errors.copy(); + introducedErrors = errors; } else { // Compute mixin properties of the operation. Input / output are // forbidden in operation mixins, so we don't bother with them @@ -53,7 +55,8 @@ private OperationShape(Builder builder) { for (Shape shape : builder.getMixins().values()) { shape.asOperationShape().ifPresent(mixin -> computedErrors.addAll(mixin.getErrors())); } - computedErrors.addAll(builder.errors.peek()); + introducedErrors = builder.errors.copy(); + computedErrors.addAll(introducedErrors); errors = Collections.unmodifiableList(new ArrayList<>(computedErrors)); } @@ -75,7 +78,7 @@ public Builder toBuilder() { return updateBuilder(builder()) .input(input) .output(output) - .errors(errors); + .errors(getIntroducedErrors()); } @Override @@ -162,6 +165,16 @@ public List getErrors() { return errors; } + /** + * Gets the errors introduced by the shape and not inherited + * from mixins. + * + * @return Returns the introduced errors. + */ + public List getIntroducedErrors() { + return introducedErrors; + } + /** *

Gets a list of the error shape IDs the operation can encounter, * including any common errors of a service. diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java index 897928af4b0..50234bca8d3 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java @@ -34,16 +34,22 @@ public final class ServiceShape extends EntityShape implements ToSmithyBuilder { private final String version; + private final String introducedVersion; private final Map rename; + private final Map introducedRename; private final List errors; + private final List introducedErrors; private ServiceShape(Builder builder) { super(builder); if (getMixins().isEmpty()) { version = builder.version; + introducedVersion = version; rename = builder.rename.copy(); + introducedRename = rename; errors = builder.errors.copy(); + introducedErrors = errors; } else { String computedVersion = ""; Map computedRename = new HashMap<>(); @@ -60,11 +66,15 @@ private ServiceShape(Builder builder) { } } - if (!builder.version.isEmpty()) { - computedVersion = builder.version; + introducedVersion = builder.version; + introducedRename = builder.rename.copy(); + introducedErrors = builder.errors.copy(); + + if (!introducedVersion.isEmpty()) { + computedVersion = introducedVersion; } - computedRename.putAll(builder.rename.peek()); - computedErrors.addAll(builder.errors.peek()); + computedRename.putAll(introducedRename); + computedErrors.addAll(introducedErrors); version = computedVersion; rename = Collections.unmodifiableMap(computedRename); @@ -79,11 +89,11 @@ public static Builder builder() { @Override public Builder toBuilder() { return updateBuilder(builder()) - .version(version) - .errors(errors) - .rename(rename) - .operations(getOperations()) - .resources(getResources()); + .version(introducedVersion) + .errors(introducedErrors) + .rename(introducedRename) + .operations(getIntroducedOperations()) + .resources(getIntroducedResources()); } @Override @@ -118,6 +128,17 @@ public String getVersion() { return version; } + /** + * Gets the version of the service introduced by the shape and not + * inherited from mixins. An empty string is returned if the version + * is undefined. + * + * @return The introduced version of the service. + */ + public String getIntroducedVersion() { + return introducedVersion; + } + /** * @return The rename map of the service. */ @@ -125,6 +146,16 @@ public Map getRename() { return rename; } + /** + * Gets the rename map introduced by the shape and not inherited + * from mixins. + * + * @return The introduced rename map of the service. + */ + public Map getIntroducedRename() { + return introducedRename; + } + /** *

Gets a list of the common errors that can be encountered by * every operation in the service.

@@ -139,6 +170,21 @@ public List getErrors() { return errors; } + /** + * Gets the list of common errors introduced by the shape and not + * inherited from mixins. These errors can be encountered by every + * operation in the service. + * + * Each returned {@link ShapeId} must resolve to a + * {@link StructureShape} that is targeted by an error trait; however, + * this is only guaranteed after a model is validated. + * + * @return Returns the introduced service errors. + */ + public List getIntroducedErrors() { + return introducedErrors; + } + /** * Gets the contextual name of a shape within the closure. * diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java index 808a9f37839..29fed1bd877 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializer.java @@ -33,7 +33,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.knowledge.PreMixinIndex; import software.amazon.smithy.model.loader.Prelude; import software.amazon.smithy.model.node.ArrayNode; import software.amazon.smithy.model.node.BooleanNode; @@ -372,7 +371,6 @@ private static final class ShapeSerializer extends ShapeVisitor.Default { private final Predicate traitFilter; private final Model model; private final Set inlineableShapes; - private final PreMixinIndex preMixinIndex; ShapeSerializer( SmithyCodeWriter codeWriter, @@ -386,7 +384,6 @@ private static final class ShapeSerializer extends ShapeVisitor.Default { this.traitFilter = traitFilter; this.model = model; this.inlineableShapes = inlineableShapes; - this.preMixinIndex = PreMixinIndex.of(model); } @Override @@ -551,18 +548,16 @@ public Void serviceShape(ServiceShape shape) { writeMixins(shape); codeWriter.openBlock("{"); - ServiceShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asServiceShape().get(); - - if (!StringUtils.isBlank(preMixinShape.getVersion())) { - codeWriter.write("version: $S", preMixinShape.getVersion()); + if (!StringUtils.isBlank(shape.getIntroducedVersion())) { + codeWriter.write("version: $S", shape.getIntroducedVersion()); } - codeWriter.writeOptionalIdList("operations", preMixinShape.getOperations()); - codeWriter.writeOptionalIdList("resources", preMixinShape.getResources()); - codeWriter.writeOptionalIdList("errors", preMixinShape.getErrors()); - if (!preMixinShape.getRename().isEmpty()) { + codeWriter.writeOptionalIdList("operations", shape.getIntroducedOperations()); + codeWriter.writeOptionalIdList("resources", shape.getIntroducedResources()); + codeWriter.writeOptionalIdList("errors", shape.getIntroducedErrors()); + if (!shape.getIntroducedRename().isEmpty()) { codeWriter.openBlock("rename: {", "}", () -> { - for (Map.Entry entry : preMixinShape.getRename().entrySet()) { + for (Map.Entry entry : shape.getIntroducedRename().entrySet()) { codeWriter.write("$S: $S", entry.getKey(), entry.getValue()); } }); @@ -592,9 +587,9 @@ public Void resourceShape(ResourceShape shape) { shape.getUpdate().ifPresent(shapeId -> codeWriter.write("update: $I", shapeId)); shape.getDelete().ifPresent(shapeId -> codeWriter.write("delete: $I", shapeId)); shape.getList().ifPresent(shapeId -> codeWriter.write("list: $I", shapeId)); - codeWriter.writeOptionalIdList("operations", shape.getOperations()); + codeWriter.writeOptionalIdList("operations", shape.getIntroducedOperations()); codeWriter.writeOptionalIdList("collectionOperations", shape.getCollectionOperations()); - codeWriter.writeOptionalIdList("resources", shape.getResources()); + codeWriter.writeOptionalIdList("resources", shape.getIntroducedResources()); codeWriter.closeBlock("}"); codeWriter.write(""); @@ -603,7 +598,6 @@ public Void resourceShape(ResourceShape shape) { @Override public Void operationShape(OperationShape shape) { - OperationShape preMixinShape = preMixinIndex.getPreMixinShape(shape).asOperationShape().get(); serializeTraits(shape); codeWriter.writeInline("operation $L ", shape.getId().getName()); writeMixins(shape); @@ -611,7 +605,7 @@ public Void operationShape(OperationShape shape) { List mixinMembers = new ArrayList<>(); mixinMembers.addAll(writeInlineableProperty("input", shape.getInputShape(), InputTrait.ID)); mixinMembers.addAll(writeInlineableProperty("output", shape.getOutputShape(), OutputTrait.ID)); - codeWriter.writeOptionalIdList("errors", preMixinShape.getErrors()); + codeWriter.writeOptionalIdList("errors", shape.getIntroducedErrors()); codeWriter.closeBlock("}"); codeWriter.write(""); applyIntroducedTraits(mixinMembers); diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/PreMixinIndexTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/PreMixinIndexTest.java deleted file mode 100644 index 6cfad787529..00000000000 --- a/smithy-model/src/test/java/software/amazon/smithy/model/knowledge/PreMixinIndexTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package software.amazon.smithy.model.knowledge; - -import java.util.HashSet; -import java.util.Set; -import org.junit.jupiter.api.Test; -import software.amazon.smithy.model.Model; -import software.amazon.smithy.model.node.Node; -import software.amazon.smithy.model.node.ObjectNode; -import software.amazon.smithy.model.shapes.ModelSerializer; -import software.amazon.smithy.model.shapes.Shape; -import software.amazon.smithy.model.transform.ModelTransformer; - -public class PreMixinIndexTest { - - @Test - public void testUnroll() { - Model withMixins = Model.assembler() - .addImport(OperationIndexTest.class.getResource("premixin.smithy")) - .assemble() - .unwrap(); - - PreMixinIndex index = PreMixinIndex.of(withMixins); - Set updatedShapes = new HashSet<>(); - withMixins.shapes().forEach(shape -> updatedShapes.add(index.getPreMixinShape(shape))); - - Model actual = ModelTransformer.create().replaceShapes(withMixins, updatedShapes); - - Model expected = Model.assembler() - .addImport(OperationIndexTest.class.getResource("premixin-unrolled.smithy")) - .assemble() - .unwrap(); - - if (!actual.equals(expected)) { - ModelSerializer serializer = ModelSerializer.builder().build(); - ObjectNode actualNode = serializer.serialize(actual); - ObjectNode expectedNode = serializer.serialize(expected); - - Node.assertEquals(actualNode, expectedNode); - } - } - -} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin-unrolled.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin-unrolled.smithy deleted file mode 100644 index 2d241aac016..00000000000 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin-unrolled.smithy +++ /dev/null @@ -1,89 +0,0 @@ -$version: "2.0" - -namespace smithy.example - -@private -service MixedService { - version: "2022-01-01" - operations: [ - MixedServiceOperation - ] - resources: [ - MixedResource - ] - errors: [ - MixedError - ] - rename: { - "smithy.example#MixedRename": "LocalRename" - } -} - -@internal -@mixin -service MixinService { - version: "2021-12-31" - operations: [ - MixinServiceOperation - ] - resources: [ - MixinResource - ] - errors: [ - MixinError - ] - rename: { - "smithy.example#MixinRename": "UpstreamRename" - } -} - -resource MixedResource { -} - -resource MixinResource { -} - -@mixin -@internal -operation MixinOperation { - errors: [MixinError] -} - -@private -operation MixedOperation { - errors: [MixedError] -} - -operation MixedServiceOperation { - input: Unit - output: Unit -} - -operation MixinServiceOperation { - input: Unit - output: Unit -} - -@error("server") -structure MixedError { - message: MixedRename -} - -@error("client") -structure MixinError { - message: MixinRename - state: OverriddenRename -} - -string MixedRename - -string MixinRename - -@mixin -@internal -string MixinString - -@private -string MixedString - -string OverriddenRename diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy deleted file mode 100644 index 0e7afe6e25d..00000000000 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/knowledge/premixin.smithy +++ /dev/null @@ -1,89 +0,0 @@ -$version: "2.0" - -namespace smithy.example - -@private -service MixedService with [MixinService] { - version: "2022-01-01" - operations: [ - MixedServiceOperation - ] - resources: [ - MixedResource - ] - errors: [ - MixedError - ] - rename: { - "smithy.example#MixedRename": "LocalRename" - } -} - -@internal -@mixin -service MixinService { - version: "2021-12-31" - operations: [ - MixinServiceOperation - ] - resources: [ - MixinResource - ] - errors: [ - MixinError - ] - rename: { - "smithy.example#MixinRename": "UpstreamRename" - } -} - -resource MixedResource { -} - -resource MixinResource { -} - -@mixin -@internal -operation MixinOperation { - errors: [MixinError] -} - -@private -operation MixedOperation with [MixinOperation] { - errors: [MixedError] -} - -operation MixedServiceOperation { - input: Unit - output: Unit -} - -operation MixinServiceOperation { - input: Unit - output: Unit -} - -@error("server") -structure MixedError { - message: MixedRename -} - -@error("client") -structure MixinError { - message: MixinRename - state: OverriddenRename -} - -string MixedRename - -string MixinRename - -@mixin -@internal -string MixinString - -@private -string MixedString with [MixinString] - -string OverriddenRename From d69d7f8578b567b205e8a7cdae04c22dd4023d68 Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Fri, 7 Jan 2022 17:04:37 +0100 Subject: [PATCH 06/10] Update design --- designs/mixins.md | 88 +++++++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 26 deletions(-) diff --git a/designs/mixins.md b/designs/mixins.md index a3960b8f55d..9e1c5b8955f 100644 --- a/designs/mixins.md +++ b/designs/mixins.md @@ -131,7 +131,7 @@ Multiple mixins can be applied: structure GetAnotherCityInput with [ CityResourceInput SomeOtherMixin -]{ +] { foo: String } ``` @@ -268,7 +268,7 @@ structure StructB {} /// C @threeTrait @mixin -structure StructC with [StructA StructB] {} +structure StructC with [StructA, StructB] {} /// D @fourTrait @@ -347,12 +347,11 @@ can be referred to outside of `smithy.example`. The members and traits applied to members of a mixin are copied onto the target shape. It is sometimes necessary to provide a more specific trait value for a copied member or to add traits only to a specific copy of a member. Traits can -be added on to these members as normal. Additionally, Traits can be applied to -these members in the JSON AST using the `apply` type and in the Smithy IDL using -`apply` statements. +be added on to these members like any other member. Additionally, Traits can be +applied to these members in the JSON AST using the `apply` type and in the +Smithy IDL using `apply` statements. -> Note: just like with traits on the shape itself, the local trait values -supersede trait values from mixins. +> Note: Traits applied to shapes supersede any traits inherited from mixins. #### Applying traits in the JSON AST @@ -464,7 +463,7 @@ structure MyStruct with [MyMixin] {} apply MyStruct$mixinMember @documentation("Specific docs") ``` -Or, alternatively: +Alternatively, the member can be redefined if it targets the same shape: ```smithy $version: "1.1" @@ -555,7 +554,7 @@ structure A2 { a: Integer } -structure Invalid with [A1 A2] {} +structure Invalid with [A1, A2] {} ``` The following model is also invalid, but not specifically because of mixins. @@ -573,12 +572,12 @@ structure A2 { A: Integer } -structure Invalid with [A1 A2] {} +structure Invalid with [A1, A2] {} ``` -A shape MAY use a member name that has already defined, but if it does it MUST -target the same shape. This can be done to apply additional traits to the -member. +Members that are mixed into shapes MAY be redefined if and only if each +redefined member targets the same shape. Traits applied to redefined members +supersede any traits inherited from mixins. ``` @mixin @@ -593,7 +592,7 @@ structure A2 { a: String } -structure Valid with [A1 A2] {} +structure Valid with [A1, A2] {} ``` @@ -601,15 +600,15 @@ structure Valid with [A1 A2] {} To support mixins, shape ABNF rules will be updated to contain an optional `mixins` production -that comes after the shape name and before any `{`. Each shape ID referenced in +that comes after the shape name. Each shape ID referenced in the `mixins` production MUST target a shape of the same type as the shape being defined and MUST be marked with the `@mixin` trait. ``` simple_shape_statement = simple_type_name ws identifier [ws mixins] list_statement = "list" ws identifier ws [mixins ws] shape_members -set_statement = "set" ws identifier ws [mixins ws] shape_members -map_statement = "map" ws identifier ws [mixins ws] shape_members +set_statement = "set" ws identifier ws [mixins ws] shape_members +map_statement = "map" ws identifier ws [mixins ws] shape_members structure_statement = "structure" ws identifier ws [mixins ws] structure_members union_statement = "union" ws identifier ws [mixins ws] union_members service_statement = "service" ws identifier ws [mixins ws] node_object @@ -756,7 +755,7 @@ structure PaginatedInput { structure ListSomethingInput with [ PaginatedInput FilteredByName -]{ +] { sizeFilter: Integer } ``` @@ -772,13 +771,19 @@ The members are ordered as follows: ### Mixins on shapes with non-member properties Some shapes don't have members, but do have other properties. Adding a mixin -to such a shape causes the properties of the other shape to be merged into -the shape. Scalar properties defined in the local shape are kept, and -non-scalar properties are merged. When merging map properties, the values for -local keys are kept. The ordering of merged lists / sets follows the -same ordering as members. +to such a shape merges the properties of each mixin into the local shape. Only +certain properties may be defined in the mixin shapes. See the sections below +for which properties are permitted for each shape type. -For example, in the following model: +Scalar properties defined in the local shape are kept, and non-scalar +properties are merged. When merging map properties, the values for local keys +are kept. The ordering of merged lists / sets follows the same ordering as +members. + +#### Service mixins + +Service shapes with the `@mixin` trait may define any property. For example, +in the following model: ``` operation OperationA {} @@ -834,19 +839,50 @@ service C { } ``` -### Resource mixins +#### Resource mixins Resource shapes with the `@mixin` trait MAY NOT define any properties. This is because every property of a resource shape is intrinsically tied to its set of identifiers. Changing these identifiers would invalidate every other property of a given resource. -### Operation mixins +Example: + +``` +@mixin +@internal +resource MixinResource {} + +resource MixedResource with [MixinResource] {} +``` + +#### Operation mixins Operation shapes with the `@mixin` trait MAY NOT define an `input` or `output` shape other than `smithy.api#Unit`. This is because allowing input and output shapes to be shared goes against the goal of the `@input` and `@output` traits. +Operation shapes with the `@mixin` trait MAY define the errors shape. + +Example: + +``` +@mixin +operation MixinOperation { + errors: [MixinError] +} + +operation MixedOperation with [MixinOperation] { + error: [MixedError] +} + +@error("client") +structure MixinError {} + +@error("client") +structure MixedError {} +``` + ### `@mixin` trait The `@mixin` trait is a structured trait defined in the Smithy prelude as: From eec6c44df8321c02d4881b4c12825a91b80d9e2b Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Fri, 7 Jan 2022 17:24:51 +0100 Subject: [PATCH 07/10] Remove duplicated member building --- .../loader/AbstractMutableModelFile.java | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/AbstractMutableModelFile.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/AbstractMutableModelFile.java index c9c30b9ff84..5862ce8dd72 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/AbstractMutableModelFile.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/AbstractMutableModelFile.java @@ -228,30 +228,40 @@ private PendingShape createPendingShape( for (ShapeId mixin : mixins) { Shape mixinShape = shapeMap.get(mixin); for (MemberShape member : mixinShape.members()) { - if (memberConflicts(builderMembers, member)) { - // Members cannot be redefined. - MemberShape.Builder conflict = builderMembers.get(member.getMemberName()); - events.add(ValidationEvent.builder() - .severity(Severity.ERROR) - .id(Validator.MODEL_ERROR) - .shapeId(conflict.getId()) - .sourceLocation(conflict.getSourceLocation()) - .message("Member conflicts with an inherited mixin member: " + member.getId()) - .build()); - } else { - // Build local member copies before adding mixins if traits - // were introduced to inherited mixin members. - ShapeId targetId = builder.getId().withMember(member.getMemberName()); - Map introducedTraits = traitContainer.getTraitsForShape(targetId); - if (!introducedTraits.isEmpty()) { - builder.addMember(MemberShape.builder() - .id(targetId) - .target(member.getTarget()) - .source(member.getSourceLocation()) - .addTraits(introducedTraits.values()) - .addMixin(member) + ShapeId targetId = builder.getId().withMember(member.getMemberName()); + Map introducedTraits = traitContainer.getTraitsForShape(targetId); + + MemberShape introducedMember = null; + if (builderMembers.containsKey(member.getMemberName())) { + introducedMember = builderMembers.get(member.getMemberName()) + .addMixin(member) + .build(); + + if (!introducedMember.getTarget().equals(member.getTarget())) { + // Members cannot be redefined if their targets conflict. + MemberShape.Builder conflict = builderMembers.get(member.getMemberName()); + events.add(ValidationEvent.builder() + .severity(Severity.ERROR) + .id(Validator.MODEL_ERROR) + .shapeId(conflict.getId()) + .sourceLocation(conflict.getSourceLocation()) + .message("Member conflicts with an inherited mixin member: " + member.getId()) .build()); } + } else if (!introducedTraits.isEmpty()) { + // Build local member copies before adding mixins if traits + // were introduced to inherited mixin members. + introducedMember = MemberShape.builder() + .id(targetId) + .target(member.getTarget()) + .source(member.getSourceLocation()) + .addTraits(introducedTraits.values()) + .addMixin(member) + .build(); + } + + if (introducedMember != null) { + builder.addMember(introducedMember); } } builder.addMixin(mixinShape); @@ -260,15 +270,6 @@ private PendingShape createPendingShape( }); } - private boolean memberConflicts(Map builderMembers, MemberShape mixinMember) { - if (!builderMembers.containsKey(mixinMember.getMemberName())) { - return false; - } - - MemberShape localMember = builderMembers.get(mixinMember.getMemberName()).build(); - return !localMember.getTarget().equals(mixinMember.getTarget()); - } - private > Optional buildShape( B builder, TraitContainer resolvedTraits From 676a2431dc1a77cf56aad8cfae21854e966c26bb Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Fri, 7 Jan 2022 17:27:07 +0100 Subject: [PATCH 08/10] Preserve ordering in service mixins --- .../amazon/smithy/model/shapes/EntityShape.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java index 4d2173e840c..136c8bb8ed5 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/EntityShape.java @@ -17,8 +17,8 @@ import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.Set; -import java.util.TreeSet; import software.amazon.smithy.utils.BuilderRef; /** @@ -40,8 +40,8 @@ public abstract class EntityShape extends Shape { operations = builder.operations.copy(); introducedOperations = operations; } else { - Set computedResources = new TreeSet<>(); - Set computedOperations = new TreeSet<>(); + Set computedResources = new LinkedHashSet<>(); + Set computedOperations = new LinkedHashSet<>(); for (Shape shape : builder.getMixins().values()) { // validateMixins should have already assured that this is an EntityShape. @@ -202,8 +202,8 @@ public B clearResources() { @Override public B flattenMixins() { - Set flatResources = new TreeSet<>(); - Set flatOperations = new TreeSet<>(); + Set flatResources = new LinkedHashSet<>(); + Set flatOperations = new LinkedHashSet<>(); for (Shape shape : getMixins().values()) { EntityShape mixin = (EntityShape) shape; From ba9229a131054792fd6faaf521772dae4d046930 Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Fri, 7 Jan 2022 17:50:20 +0100 Subject: [PATCH 09/10] Require with to be on the same line --- designs/mixins.md | 20 +++++++++---------- .../smithy/model/loader/IdlModelParser.java | 12 ++++------- .../smithy/model/shapes/OperationShape.java | 5 +++-- .../smithy/model/shapes/ServiceShape.java | 6 +++--- .../amazon/smithy/model/shapes/Shape.java | 12 ++++++++--- .../ValidSmithyModelLoaderRunnerTest.java | 1 - .../mixin-illegal-redefined-member.smithy | 6 +++--- .../invalid/mixins/with-on-next-line.smithy | 10 ++++++++++ 8 files changed, 42 insertions(+), 30 deletions(-) create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line.smithy diff --git a/designs/mixins.md b/designs/mixins.md index 9e1c5b8955f..39f495f3630 100644 --- a/designs/mixins.md +++ b/designs/mixins.md @@ -605,16 +605,16 @@ the `mixins` production MUST target a shape of the same type as the shape being defined and MUST be marked with the `@mixin` trait. ``` -simple_shape_statement = simple_type_name ws identifier [ws mixins] -list_statement = "list" ws identifier ws [mixins ws] shape_members -set_statement = "set" ws identifier ws [mixins ws] shape_members -map_statement = "map" ws identifier ws [mixins ws] shape_members -structure_statement = "structure" ws identifier ws [mixins ws] structure_members -union_statement = "union" ws identifier ws [mixins ws] union_members -service_statement = "service" ws identifier ws [mixins ws] node_object -operation_statement = "operation" ws identifier ws [mixins ws] node_object -resource_statement = "resource" ws identifier ws [mixins ws] node_object -mixins = "with" ws "[" 1*(ws shape_id) ws "]" +simple_shape_statement = simple_type_name ws identifier [mixins] +list_statement = "list" ws identifier [mixins ws] shape_members +set_statement = "set" ws identifier [mixins ws] shape_members +map_statement = "map" ws identifier [mixins ws] shape_members +structure_statement = "structure" ws identifier [mixins ws] structure_members +union_statement = "union" ws identifier [mixins ws] union_members +service_statement = "service" ws identifier [mixins ws] node_object +operation_statement = "operation" ws identifier [mixins ws] node_object +resource_statement = "resource" ws identifier [mixins ws] node_object +mixins = sp "with" ws "[" 1*(ws shape_id) ws "]" ``` diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java index 11f1ea4646f..94a9db3b20b 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java @@ -453,7 +453,7 @@ private ShapeId parseShapeName() { private void parseSimpleShape(ShapeId id, SourceLocation location, AbstractShapeBuilder builder) { modelFile.onShape(builder.source(location).id(id)); parseMixins(id); - // Pending docs get cleared by parseMixins. It can't be done here because the + // Pending docs get cleared by parseMixins. It isn't done here because the // mixin parser needs to consume whitespace that may include doc comments for // the next shape in the model. } @@ -497,11 +497,7 @@ private void parseMembers(ShapeId id, Set requiredMembers) { expect('}'); } - private void parseMember( - ShapeId parent, - Set allowed, - Set defined - ) { + private void parseMember(ShapeId parent, Set allowed, Set defined) { // Parse optional member traits. List memberTraits = parseDocsAndTraits(); SourceLocation memberLocation = currentLocation(); @@ -573,7 +569,7 @@ private void parseStructuredShape( private void parseMixins(ShapeId id) { // This is fine for now, but if we ever add any other keywords that start with // 'w' then we'll need to peek farther. - ws(); + sp(); if (peek() != 'w') { return; } @@ -593,12 +589,12 @@ private void parseMixins(ShapeId id) { ws(); do { - clearPendingDocs(); String target = ParserUtils.parseShapeId(this); modelFile.addForwardReference(target, resolved -> modelFile.addPendingMixin(id, resolved)); ws(); } while (peek() != ']'); expect(']'); + clearPendingDocs(); } private void parseOperationStatement(ShapeId id, SourceLocation location) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java index d2966c2085b..fb989104f68 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/OperationShape.java @@ -23,6 +23,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import software.amazon.smithy.model.SourceException; import software.amazon.smithy.model.knowledge.OperationIndex; import software.amazon.smithy.model.traits.MixinTrait; import software.amazon.smithy.model.traits.UnitTypeTrait; @@ -61,11 +62,11 @@ private OperationShape(Builder builder) { } if (hasTrait(MixinTrait.ID) && (!input.equals(UnitTypeTrait.UNIT) || !output.equals(UnitTypeTrait.UNIT))) { - throw new IllegalStateException(String.format( + throw new SourceException(String.format( "Operation shapes with the mixin trait MUST target `%s` for their input and output. Operation " + "mixin shape `%s` defines one or both of these properties.", UnitTypeTrait.UNIT, getId() - )); + ), builder.getSourceLocation()); } } diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java index 50234bca8d3..0e65ac93753 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/ServiceShape.java @@ -18,7 +18,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -52,7 +52,7 @@ private ServiceShape(Builder builder) { introducedErrors = errors; } else { String computedVersion = ""; - Map computedRename = new HashMap<>(); + Map computedRename = new LinkedHashMap<>(); Set computedErrors = new LinkedHashSet<>(); for (Shape shape : builder.getMixins().values()) { @@ -320,7 +320,7 @@ public Builder clearErrors() { @Override public Builder flattenMixins() { String flatVersion = version; - Map flatRename = new HashMap<>(); + Map flatRename = new LinkedHashMap<>(); Set flatErrors = new LinkedHashSet<>(); for (Shape shape : getMixins().values()) { diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java index a59c7c892c3..ad1b47428ad 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/Shape.java @@ -98,9 +98,11 @@ protected void validateMixins(Map mixins, Map in } if (!invalid.isEmpty()) { String invalidList = String.join("`, `", invalid); - throw new IllegalStateException(String.format( + throw new SourceException(String.format( "Mixins may only be mixed into shapes of the same type. The following mixins were applied to the " - + "%s shape `%s` which are not %1$s shapes: [`%s`]", getType(), getId(), invalidList)); + + "%s shape `%s` which are not %1$s shapes: [`%s`]", getType(), getId(), invalidList), + source + ); } } @@ -108,6 +110,7 @@ protected MemberShape getRequiredMixinMember( AbstractShapeBuilder builder, String name ) { + // Get the most recently introduced mixin member with the given name. MemberShape mixedMember = null; for (Shape shape : builder.getMixins().values()) { for (MemberShape member : shape.members()) { @@ -123,7 +126,10 @@ protected MemberShape getRequiredMixinMember( } } if (mixedMember == null) { - throw new IllegalStateException(String.format("Missing required member of shape `%s`: %s", getId(), name)); + throw new SourceException( + String.format("Missing required member of shape `%s`: %s", getId(), name), + builder.getSourceLocation() + ); } return mixedMember; } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/loader/ValidSmithyModelLoaderRunnerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/loader/ValidSmithyModelLoaderRunnerTest.java index a511458efdc..d825731396f 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/loader/ValidSmithyModelLoaderRunnerTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/loader/ValidSmithyModelLoaderRunnerTest.java @@ -28,7 +28,6 @@ import software.amazon.smithy.model.Model; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.shapes.ModelSerializer; -import software.amazon.smithy.model.shapes.Shape; /** * Loads all of the ".smithy" files in idl/valid. diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy index 2a6455bb470..0aeee813398 100644 --- a/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/errorfiles/validators/mixins/mixin-illegal-redefined-member.smithy @@ -9,17 +9,17 @@ structure Foo { } structure Baz with [Foo] { - foo: Integer // cannot redefine mixin members! + foo: Integer // cannot redefine mixin members with a different target! bar: String // This is allowed because the target hasn't changed } @mixin structure Bam with [Foo] { - foo: Integer // cannot redefine mixin members! + foo: Integer // cannot redefine mixin members with a different target! bar: String // This is allowed because the target hasn't changed } structure Boo with [Bam] { - foo: Long // cannot redefine mixin members! + foo: Long // cannot redefine mixin members with a different target! bar: String // This is allowed because the target hasn't changed } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line.smithy new file mode 100644 index 00000000000..88df5d1ee88 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line.smithy @@ -0,0 +1,10 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +string MixinString + +string MixedString + with [MixinString] From 8adbf920dd106f3907deaa0c13b4ffa89e896afb Mon Sep 17 00:00:00 2001 From: JordonPhillips Date: Mon, 10 Jan 2022 15:26:08 +0100 Subject: [PATCH 10/10] Fix whitespace handling in mixins --- designs/mixins.md | 16 ++++++++-------- .../smithy/model/loader/IdlModelParser.java | 10 ---------- .../smithy/model/shapes/ListShapeTest.java | 2 +- .../mixins/with-on-next-line-bigDecimal.smithy | 11 +++++++++++ .../mixins/with-on-next-line-bigInteger.smithy | 11 +++++++++++ .../invalid/mixins/with-on-next-line-blob.smithy | 11 +++++++++++ .../mixins/with-on-next-line-boolean.smithy | 11 +++++++++++ .../invalid/mixins/with-on-next-line-byte.smithy | 11 +++++++++++ .../mixins/with-on-next-line-document.smithy | 11 +++++++++++ .../mixins/with-on-next-line-double.smithy | 11 +++++++++++ .../mixins/with-on-next-line-float.smithy | 11 +++++++++++ .../mixins/with-on-next-line-integer.smithy | 11 +++++++++++ .../invalid/mixins/with-on-next-line-list.smithy | 12 ++++++++++++ .../invalid/mixins/with-on-next-line-long.smithy | 11 +++++++++++ .../invalid/mixins/with-on-next-line-map.smithy | 13 +++++++++++++ .../mixins/with-on-next-line-resource.smithy | 10 ++++++++++ .../mixins/with-on-next-line-service.smithy | 10 ++++++++++ .../invalid/mixins/with-on-next-line-set.smithy | 13 +++++++++++++ .../mixins/with-on-next-line-short.smithy | 11 +++++++++++ ...ne.smithy => with-on-next-line-string.smithy} | 0 .../mixins/with-on-next-line-structure.smithy | 10 ++++++++++ .../mixins/with-on-next-line-timestamp.smithy | 11 +++++++++++ .../mixins/with-on-next-line-union.smithy | 10 ++++++++++ 23 files changed, 219 insertions(+), 19 deletions(-) create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-bigDecimal.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-bigInteger.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-blob.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-boolean.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-byte.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-document.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-double.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-float.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-integer.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-list.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-long.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-map.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-resource.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-service.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-set.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-short.smithy rename smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/{with-on-next-line.smithy => with-on-next-line-string.smithy} (100%) create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-structure.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-timestamp.smithy create mode 100644 smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-union.smithy diff --git a/designs/mixins.md b/designs/mixins.md index 39f495f3630..fc10d05f0af 100644 --- a/designs/mixins.md +++ b/designs/mixins.md @@ -606,14 +606,14 @@ shape being defined and MUST be marked with the `@mixin` trait. ``` simple_shape_statement = simple_type_name ws identifier [mixins] -list_statement = "list" ws identifier [mixins ws] shape_members -set_statement = "set" ws identifier [mixins ws] shape_members -map_statement = "map" ws identifier [mixins ws] shape_members -structure_statement = "structure" ws identifier [mixins ws] structure_members -union_statement = "union" ws identifier [mixins ws] union_members -service_statement = "service" ws identifier [mixins ws] node_object -operation_statement = "operation" ws identifier [mixins ws] node_object -resource_statement = "resource" ws identifier [mixins ws] node_object +list_statement = "list" ws identifier [mixins] ws shape_members +set_statement = "set" ws identifier [mixins] ws shape_members +map_statement = "map" ws identifier [mixins] ws shape_members +structure_statement = "structure" ws identifier [mixins] ws structure_members +union_statement = "union" ws identifier [mixins] ws union_members +service_statement = "service" ws identifier [mixins] ws node_object +operation_statement = "operation" ws identifier [mixins] ws node_object +resource_statement = "resource" ws identifier [mixins] ws node_object mixins = sp "with" ws "[" 1*(ws shape_id) ws "]" ``` diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java index 94a9db3b20b..098ac7efd74 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/loader/IdlModelParser.java @@ -453,15 +453,11 @@ private ShapeId parseShapeName() { private void parseSimpleShape(ShapeId id, SourceLocation location, AbstractShapeBuilder builder) { modelFile.onShape(builder.source(location).id(id)); parseMixins(id); - // Pending docs get cleared by parseMixins. It isn't done here because the - // mixin parser needs to consume whitespace that may include doc comments for - // the next shape in the model. } // See parseMap for information on why members are parsed before the // list/set is registered with the ModelFile. private void parseCollection(ShapeId id, SourceLocation location, CollectionShape.Builder builder) { - ws(); builder.id(id).source(location); parseMixins(id); parseMembers(id, SetUtils.of("member")); @@ -558,8 +554,6 @@ private void parseStructuredShape( // "Member `foo.baz#Foo$Baz` cannot be added to software.amazon.smithy.model.shapes.OperationShape$Builder" modelFile.onShape(builder.id(id).source(location)); - ws(); - // Parse optional "with" statements to add mixins, but only if it's supported by the version. parseMixins(id); parseMembers(id, Collections.emptySet()); @@ -567,8 +561,6 @@ private void parseStructuredShape( } private void parseMixins(ShapeId id) { - // This is fine for now, but if we ever add any other keywords that start with - // 'w' then we'll need to peek farther. sp(); if (peek() != 'w') { return; @@ -698,7 +690,6 @@ private void parseIdList(Consumer consumer) { } private void parseServiceStatement(ShapeId id, SourceLocation location) { - ws(); parseMixins(id); ws(); ServiceShape.Builder builder = new ServiceShape.Builder().id(id).source(location); @@ -729,7 +720,6 @@ private void optionalIdList(ObjectNode node, String name, Consumer cons } private void parseResourceStatement(ShapeId id, SourceLocation location) { - ws(); parseMixins(id); ws(); ResourceShape.Builder builder = ResourceShape.builder().id(id).source(location); diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ListShapeTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ListShapeTest.java index a846b6dd721..44aa6da4705 100644 --- a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ListShapeTest.java +++ b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/ListShapeTest.java @@ -49,7 +49,7 @@ public void mustNotContainMembersInShapeId() { @Test public void requiresMember() { - Assertions.assertThrows(IllegalStateException.class, () -> { + Assertions.assertThrows(SourceException.class, () -> { ListShape.builder().id("ns.foo#bar").build(); }); } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-bigDecimal.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-bigDecimal.smithy new file mode 100644 index 00000000000..fef40fbe38f --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-bigDecimal.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +bigDecimal MixinBigdecimal + +bigDecimal MixedBigdecimal + with [MixinBigdecimal] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-bigInteger.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-bigInteger.smithy new file mode 100644 index 00000000000..8a3d2ac9fb8 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-bigInteger.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +bigInteger MixinBiginteger + +bigInteger MixedBiginteger + with [MixinBiginteger] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-blob.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-blob.smithy new file mode 100644 index 00000000000..915f9d2f338 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-blob.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +blob MixinBlob + +blob MixedBlob + with [MixinBlob] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-boolean.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-boolean.smithy new file mode 100644 index 00000000000..f27c986d456 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-boolean.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +boolean MixinBoolean + +boolean MixedBoolean + with [MixinBoolean] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-byte.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-byte.smithy new file mode 100644 index 00000000000..ece46a413e4 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-byte.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +byte MixinByte + +byte MixedByte + with [MixinByte] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-document.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-document.smithy new file mode 100644 index 00000000000..8bf58a2fa98 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-document.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +document MixinDocument + +document MixedDocument + with [MixinDocument] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-double.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-double.smithy new file mode 100644 index 00000000000..ef3ba335ebc --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-double.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +double MixinDouble + +double MixedDouble + with [MixinDouble] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-float.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-float.smithy new file mode 100644 index 00000000000..a00e813883e --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-float.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +float MixinFloat + +float MixedFloat + with [MixinFloat] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-integer.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-integer.smithy new file mode 100644 index 00000000000..f653c969bc8 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-integer.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +integer MixinInteger + +integer MixedInteger + with [MixinInteger] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-list.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-list.smithy new file mode 100644 index 00000000000..f2ed6af4d5c --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-list.smithy @@ -0,0 +1,12 @@ +// Parse error at line 7, column 5 near `with`: Expected: '{', but found 'w' +$version: "2.0" + +namespace smithy.example + +list MixedList + with [MixinList] {} + +@mixin +list MixinList { + member: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-long.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-long.smithy new file mode 100644 index 00000000000..e2ab79e32b9 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-long.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +long MixinLong + +long MixedLong + with [MixinLong] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-map.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-map.smithy new file mode 100644 index 00000000000..35d1311774e --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-map.smithy @@ -0,0 +1,13 @@ +// Parse error at line 7, column 5 near `with`: Expected: '{', but found 'w' +$version: "2.0" + +namespace smithy.example + +map MixedMap + with [MixinMap] {} + +@mixin +map MixinMap { + key: String + value: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-resource.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-resource.smithy new file mode 100644 index 00000000000..48037ed3e5b --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-resource.smithy @@ -0,0 +1,10 @@ +// Parse error at line 7, column 5 near `with`: Expected: '{', but found 'w' +$version: "2.0" + +namespace smithy.example + +resource MixedResource + with [MixinResource] {} + +@mixin +resource MixinResource {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-service.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-service.smithy new file mode 100644 index 00000000000..9fdd1087145 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-service.smithy @@ -0,0 +1,10 @@ +// Parse error at line 7, column 5 near `with`: Expected: '{', but found 'w' +$version: "2.0" + +namespace smithy.example + +service MixedService + with [MixinService] {} + +@mixin +service MixinService {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-set.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-set.smithy new file mode 100644 index 00000000000..35d1311774e --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-set.smithy @@ -0,0 +1,13 @@ +// Parse error at line 7, column 5 near `with`: Expected: '{', but found 'w' +$version: "2.0" + +namespace smithy.example + +map MixedMap + with [MixinMap] {} + +@mixin +map MixinMap { + key: String + value: String +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-short.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-short.smithy new file mode 100644 index 00000000000..314e7f0e5c6 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-short.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +short MixinShort + +short MixedShort + with [MixinShort] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-string.smithy similarity index 100% rename from smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line.smithy rename to smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-string.smithy diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-structure.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-structure.smithy new file mode 100644 index 00000000000..a99c82c8996 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-structure.smithy @@ -0,0 +1,10 @@ +// Parse error at line 7, column 5 near `with`: Expected: '{', but found 'w' +$version: "2.0" + +namespace smithy.example + +structure MixedStructure + with [MixinStructure] {} + +@mixin +structure MixinStructure {} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-timestamp.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-timestamp.smithy new file mode 100644 index 00000000000..2a5bd835eb6 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-timestamp.smithy @@ -0,0 +1,11 @@ +// Parse error at line 10, column 9 near ` `: Unexpected shape type: with +$version: "2.0" + +namespace smithy.example + +@mixin +timestamp MixinTimestamp + +timestamp MixedTimestamp + with [MixinTimestamp] + \ No newline at end of file diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-union.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-union.smithy new file mode 100644 index 00000000000..9aa858348e0 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/loader/invalid/mixins/with-on-next-line-union.smithy @@ -0,0 +1,10 @@ +// Parse error at line 7, column 5 near `with`: Expected: '{', but found 'w' +$version: "2.0" + +namespace smithy.example + +union MixedUnion + with [MixinUnion] {} + +@mixin +union MixinUnion {}