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