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