diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/IntEnumShape.java b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/IntEnumShape.java index b7d041d258f..c9f6ad3bea8 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/shapes/IntEnumShape.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/shapes/IntEnumShape.java @@ -116,6 +116,11 @@ public Builder id(ShapeId shapeId) { return this; } + @Override + public Builder id(String shapeId) { + return id(ShapeId.from(shapeId)); + } + /** * Replaces the members of the builder. * 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 83fbfb78e7f..bd0742591f0 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 @@ -34,8 +34,16 @@ import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.node.StringNode; +import software.amazon.smithy.model.traits.AddedDefaultTrait; +import software.amazon.smithy.model.traits.BoxTrait; +import software.amazon.smithy.model.traits.ClientOptionalTrait; +import software.amazon.smithy.model.traits.DefaultTrait; +import software.amazon.smithy.model.traits.NotPropertyTrait; +import software.amazon.smithy.model.traits.PropertyTrait; import software.amazon.smithy.model.traits.Trait; +import software.amazon.smithy.model.transform.ModelTransformer; import software.amazon.smithy.utils.FunctionalUtils; +import software.amazon.smithy.utils.SetUtils; import software.amazon.smithy.utils.SmithyBuilder; import software.amazon.smithy.utils.StringUtils; @@ -50,26 +58,62 @@ * to formats like JSON, YAML, Ion, etc. */ public final class ModelSerializer { + + // Explicitly filter out these traits. While some of these are automatically removed in the downgradeToV1 + // model transformation, calling them out here explicitly is a defense in depth. This also has to remove all + // default traits from the output, whereas the downgradeToV1 transform only removes unnecessary default traits + // that don't correlate to boxing in V1 models. + private static final Set V2_TRAITS_TO_FILTER_FROM_V1 = SetUtils.of( + DefaultTrait.ID, + AddedDefaultTrait.ID, + ClientOptionalTrait.ID, + PropertyTrait.ID, + NotPropertyTrait.ID); + private final Predicate metadataFilter; private final Predicate shapeFilter; private final Predicate traitFilter; + private final String version; private ModelSerializer(Builder builder) { metadataFilter = builder.metadataFilter; + version = builder.version; + if (!builder.includePrelude) { shapeFilter = builder.shapeFilter.and(FunctionalUtils.not(Prelude::isPreludeShape)); + } else if (version.equals("1.0")) { + throw new UnsupportedOperationException("Cannot serialize prelude and set model version to 1.0"); } else { shapeFilter = builder.shapeFilter; } - // Never serialize synthetic traits. - traitFilter = builder.traitFilter.and(FunctionalUtils.not(Trait::isSynthetic)); + + if (version.equals("1.0")) { + traitFilter = builder.traitFilter.and(trait -> { + if (trait.toShapeId().equals(BoxTrait.ID)) { + // Include the box trait in 1.0 models. + return true; + } else if (V2_TRAITS_TO_FILTER_FROM_V1.contains(trait.toShapeId())) { + // Exclude V2 specific traits. + return false; + } else { + return !trait.isSynthetic(); + } + }); + } else { + // 2.0 models just need to filter out synthetic traits, including box. + traitFilter = builder.traitFilter.and(FunctionalUtils.not(Trait::isSynthetic)); + } } public ObjectNode serialize(Model model) { ShapeSerializer shapeSerializer = new ShapeSerializer(); + if (version.equals("1.0")) { + model = ModelTransformer.create().downgradeToV1(model); + } + ObjectNode.Builder builder = Node.objectNodeBuilder() - .withMember("smithy", Node.from(Model.MODEL_VERSION)) + .withMember("smithy", Node.from(version)) .withOptionalMember("metadata", createMetadata(model).map(Node::withDeepSortedKeys)); // Sort shapes by ID. @@ -124,6 +168,7 @@ public static final class Builder implements SmithyBuilder { private Predicate shapeFilter = FunctionalUtils.alwaysTrue(); private boolean includePrelude = false; private Predicate traitFilter = FunctionalUtils.alwaysTrue(); + private String version = "2.0"; private Builder() {} @@ -181,6 +226,31 @@ public Builder traitFilter(Predicate traitFilter) { return this; } + /** + * Sets the IDL version to serialize. Defaults to 2.0. + * + *

Version "1.0" serialization cannot be used with {@link #includePrelude}. + * + * @param version IDL version to set. Can be "1", "1.0", "2", or "2.0". + * "1" and "2" are normalized to "1.0" and "2.0". + * @return Returns the builder. + */ + public Builder version(String version) { + switch (version) { + case "2": + case "2.0": + this.version = "2.0"; + break; + case "1": + case "1.0": + this.version = "1.0"; + break; + default: + throw new IllegalArgumentException("Unsupported Smithy model version: " + version); + } + return this; + } + @Override public ModelSerializer build() { return new ModelSerializer(this); diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/DowngradeToV1.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/DowngradeToV1.java new file mode 100644 index 00000000000..3721ab636a3 --- /dev/null +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/DowngradeToV1.java @@ -0,0 +1,132 @@ +/* + * Copyright 2022 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.transform; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.knowledge.NullableIndex; +import software.amazon.smithy.model.shapes.MemberShape; +import software.amazon.smithy.model.shapes.ResourceShape; +import software.amazon.smithy.model.shapes.Shape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.ShapeType; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.AddedDefaultTrait; +import software.amazon.smithy.model.traits.ClientOptionalTrait; +import software.amazon.smithy.model.traits.DefaultTrait; +import software.amazon.smithy.model.traits.NotPropertyTrait; +import software.amazon.smithy.model.traits.PropertyTrait; + +final class DowngradeToV1 { + + Model transform(ModelTransformer transformer, Model model) { + // Flatten and remove mixins since they aren't part of IDL 1.0 + model = transformer.flattenAndRemoveMixins(model); + + // Change enums to string shapes and intEnums to integers. + model = downgradeEnums(transformer, model); + + // Remove resource properties + model = removeResourceProperties(transformer, model); + + // Remove default traits that do not correlate to box traits from v1. + model = removeUnnecessaryDefaults(transformer, model); + + return removeOtherV2Traits(transformer, model); + } + + private Model downgradeEnums(ModelTransformer transformer, Model model) { + Map typeChanges = new HashMap<>(); + + for (Shape shape : model.getEnumShapes()) { + typeChanges.put(shape.getId(), ShapeType.STRING); + } + + for (Shape shape : model.getIntEnumShapes()) { + typeChanges.put(shape.getId(), ShapeType.INTEGER); + } + + return transformer.changeShapeType(model, typeChanges); + } + + private Model removeResourceProperties(ModelTransformer transformer, Model model) { + List updates = new ArrayList<>(); + + // Remove the "properties" key from resources. + for (ResourceShape shape : model.getResourceShapes()) { + if (shape.hasProperties()) { + updates.add(shape.toBuilder().properties(Collections.emptyMap()).build()); + } + } + + // Remove @notProperty. + for (Shape shape : model.getShapesWithTrait(NotPropertyTrait.class)) { + updates.add(Shape.shapeToBuilder(shape).removeTrait(NotPropertyTrait.ID).build()); + } + + // Remove @property. + for (Shape shape : model.getShapesWithTrait(PropertyTrait.class)) { + updates.add(Shape.shapeToBuilder(shape).removeTrait(PropertyTrait.ID).build()); + } + + return transformer.replaceShapes(model, updates); + } + + private Model removeUnnecessaryDefaults(ModelTransformer transformer, Model model) { + Set updates = new HashSet<>(); + + // Remove addedDefault traits, and any found default trait if present. + for (MemberShape shape : model.getMemberShapesWithTrait(AddedDefaultTrait.class)) { + updates.add(shape.toBuilder().removeTrait(DefaultTrait.ID).removeTrait(AddedDefaultTrait.ID).build()); + } + + for (Shape shape : model.getShapesWithTrait(DefaultTrait.class)) { + DefaultTrait trait = shape.expectTrait(DefaultTrait.class); + // Members with a null default are considered boxed. Keep the trait to retain consistency with other + // indexes and checks. + if (!trait.toNode().isNullNode()) { + if (!NullableIndex.isDefaultZeroValueOfTypeInV1(trait.toNode(), shape.getType())) { + updates.add(Shape.shapeToBuilder(shape) + .removeTrait(DefaultTrait.ID) + .removeTrait(AddedDefaultTrait.ID) + .build()); + } + } + } + + return transformer.replaceShapes(model, updates); + } + + private Model removeOtherV2Traits(ModelTransformer transformer, Model model) { + Set updates = new HashSet<>(); + + for (StructureShape structure : model.getStructureShapes()) { + for (MemberShape member : structure.getAllMembers().values()) { + if (member.hasTrait(ClientOptionalTrait.class)) { + updates.add(member.toBuilder().removeTrait(ClientOptionalTrait.ID).build()); + } + } + } + + return transformer.replaceShapes(model, updates); + } +} diff --git a/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java b/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java index 0af40715eb8..28b454208d5 100644 --- a/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java +++ b/smithy-model/src/main/java/software/amazon/smithy/model/transform/ModelTransformer.java @@ -636,4 +636,20 @@ public Model flattenAndRemoveMixins(Model model) { public Model addClientOptional(Model model, boolean applyWhenNoDefaultValue) { return new AddClientOptional(applyWhenNoDefaultValue).transform(this, model); } + + /** + * Removes Smithy IDL 2.0 features from a model that are not strictly necessary to keep for consistency with the + * rest of Smithy. + * + *

This transformer converts enum shapes to string shapes with the enum trait, intEnum shapes to integer shapes, + * flattens and removes mixins, removes properties from resources, and removes default traits that have no impact + * on IDL 1.0 semantics (i.e., default traits on structure members set to something other than null, or default + * traits on any other shape that are not the zero value of the shape of a 1.0 model). + * + * @param model Model to downgrade. + * @return Returns the downgraded model. + */ + public Model downgradeToV1(Model model) { + return new DowngradeToV1().transform(this, model); + } } 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 7d0774a8dfb..dfb7f512864 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 @@ -22,7 +22,6 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.startsWith; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; @@ -34,13 +33,13 @@ import java.util.Optional; import java.util.TreeMap; 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; -import software.amazon.smithy.model.node.NodeMapper; import software.amazon.smithy.model.node.NodePointer; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.model.traits.DocumentationTrait; @@ -53,6 +52,7 @@ public class ModelSerializerTest { public Stream generateTests() throws IOException, URISyntaxException { return Files.list(Paths.get( SmithyIdlModelSerializer.class.getResource("ast-serialization/cases").toURI())) + .filter(path -> !path.toString().endsWith(".1.0.json")) .map(path -> DynamicTest.dynamicTest(path.getFileName().toString(), () -> testRoundTrip(path))); } @@ -63,6 +63,14 @@ public void testRoundTrip(Path path) { ObjectNode expected = Node.parse(IoUtils.readUtf8File(path)).expectObjectNode(); Node.assertEquals(actual, expected); + + // Now validate the file is serialized correctly when downgraded to 1.0. + Path downgradeFile = Paths.get(path.toString().replace(".json", ".1.0.json")); + ObjectNode expectedDowngrade = Node.parse(IoUtils.readUtf8File(downgradeFile)).expectObjectNode(); + ModelSerializer serializer1 = ModelSerializer.builder().version("1.0").build(); + ObjectNode model1 = serializer1.serialize(model); + + Node.assertEquals(model1, expectedDowngrade); } @Test @@ -252,4 +260,18 @@ public void serializesResourceProperties() { "{\"type\":\"resource\",\"properties\":{\"fooProperty\":{\"target\":\"ns.foo#Shape\"}}}}}"); Node.assertEquals(node, expectedNode); } + + @Test + public void failsOnInvalidVersion() { + Assertions.assertThrows(IllegalArgumentException.class, () -> { + ModelSerializer.builder().version("1.5").build(); + }); + } + + @Test + public void failsWhenUsingV1WithPrelude() { + Assertions.assertThrows(UnsupportedOperationException.class, () -> { + ModelSerializer.builder().version("1.0").includePrelude(true).build(); + }); + } } diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/transform/DowngradeToV1Test.java b/smithy-model/src/test/java/software/amazon/smithy/model/transform/DowngradeToV1Test.java new file mode 100644 index 00000000000..a4cbebeadbc --- /dev/null +++ b/smithy-model/src/test/java/software/amazon/smithy/model/transform/DowngradeToV1Test.java @@ -0,0 +1,160 @@ +package software.amazon.smithy.model.transform; + +import static org.hamcrest.MatcherAssert.assertThat; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; +import software.amazon.smithy.model.Model; +import software.amazon.smithy.model.shapes.EnumShape; +import software.amazon.smithy.model.shapes.IntEnumShape; +import software.amazon.smithy.model.shapes.OperationShape; +import software.amazon.smithy.model.shapes.ResourceShape; +import software.amazon.smithy.model.shapes.ShapeId; +import software.amazon.smithy.model.shapes.StringShape; +import software.amazon.smithy.model.shapes.StructureShape; +import software.amazon.smithy.model.traits.AddedDefaultTrait; +import software.amazon.smithy.model.traits.BoxTrait; +import software.amazon.smithy.model.traits.ClientOptionalTrait; +import software.amazon.smithy.model.traits.DefaultTrait; +import software.amazon.smithy.model.traits.EnumTrait; +import software.amazon.smithy.model.traits.MixinTrait; +import software.amazon.smithy.model.traits.NotPropertyTrait; +import software.amazon.smithy.model.traits.PropertyTrait; +import software.amazon.smithy.model.traits.RequiredTrait; +import software.amazon.smithy.model.traits.SensitiveTrait; + +public class DowngradeToV1Test { + @Test + public void convertsIntEnums() { + IntEnumShape intEnumShape = IntEnumShape.builder() + .id("smithy.example#IntEnum") + .addMember("foo", 1) + .build(); + Model model = Model.assembler() + .addShape(intEnumShape) + .assemble() + .unwrap(); + + Model downgraded = ModelTransformer.create().downgradeToV1(model); + + assertThat(downgraded.expectShape(intEnumShape.getId()).isIntEnumShape(), Matchers.is(false)); + assertThat(downgraded.expectShape(intEnumShape.getId()).isIntegerShape(), Matchers.is(true)); + assertThat(downgraded.expectShape(intEnumShape.getId()).getAllTraits().entrySet(), Matchers.empty()); + } + + @Test + public void convertsEnumShapes() { + EnumShape enumShape = EnumShape.builder() + .id("smithy.example#Enum") + .addMember("foo", "hello") + .build(); + Model model = Model.assembler() + .addShape(enumShape) + .assemble() + .unwrap(); + + Model downgraded = ModelTransformer.create().downgradeToV1(model); + + assertThat(downgraded.expectShape(enumShape.getId()).isEnumShape(), Matchers.is(false)); + assertThat(downgraded.expectShape(enumShape.getId()).isStringShape(), Matchers.is(true)); + assertThat(downgraded.expectShape(enumShape.getId()).getAllTraits(), Matchers.hasKey(EnumTrait.ID)); + } + + @Test + public void flattensMixins() { + StringShape mixin = StringShape.builder() + .id("smithy.example#Mixin") + .addTrait(MixinTrait.builder().build()) + .addTrait(new SensitiveTrait()) + .build(); + StringShape concrete = StringShape.builder() + .id("smithy.example#Concrete") + .addMixin(mixin) + .build(); + Model model = Model.assembler() + .addShapes(mixin, concrete) + .assemble() + .unwrap(); + + Model downgraded = ModelTransformer.create().downgradeToV1(model); + + assertThat(downgraded.expectShape(concrete.getId()).getMixins(), Matchers.empty()); + assertThat(downgraded.expectShape(concrete.getId()).getAllTraits(), Matchers.hasKey(SensitiveTrait.ID)); + assertThat(downgraded.getShape(mixin.getId()).isPresent(), Matchers.is(false)); + } + + @Test + public void removesResourceProperties() { + StructureShape input = StructureShape.builder() + .id("smithy.example#Input") + .addMember("foo", ShapeId.from("smithy.api#String"), b -> b.addTrait(PropertyTrait.builder().build())) + .addMember("baz", ShapeId.from("smithy.api#String"), b -> b.addTrait(new NotPropertyTrait())) + .build(); + OperationShape operation = OperationShape.builder() + .id(ShapeId.from("smithy.example#Op")) + .input(input) + .build(); + ResourceShape resource = ResourceShape.builder() + .id(ShapeId.from("smithy.example#Foo")) + .addProperty("foo", "smithy.api#String") + .addOperation(operation) + .build(); + + Model model = Model.assembler() + .addShapes(input, operation, resource) + .assemble() + .unwrap(); + + Model downgraded = ModelTransformer.create().downgradeToV1(model); + + assertThat(downgraded.expectShape(resource.getId(), ResourceShape.class).getProperties(), + Matchers.anEmptyMap()); + + assertThat(downgraded.expectShape(input.getMember("foo").get().getId()).hasTrait(PropertyTrait.class), + Matchers.is(false)); + assertThat(downgraded.expectShape(input.getMember("baz").get().getId()).hasTrait(NotPropertyTrait.class), + Matchers.is(false)); + } + + @Test + public void removesUnnecessaryDefaults() { + String stringModel = "$version: \"2.0\"\n" + + "namespace smithy.example\n" + + "@default(10)\n" + + "integer MyInteger\n" + + "structure Struct {\n" + + " @default(10)\n" + + " foo: MyInteger\n" + + " baz: PrimitiveInteger = null\n" + + " @default(\"hi\")\n" + + " @addedDefault\n" + + " bar: String\n" + + " @required\n" + + " @clientOptional\n" + + " bam: String\n" + + "}"; + + Model model = Model.assembler() + .addUnparsedModel("example.smithy", stringModel) + .assemble() + .unwrap(); + + Model downgraded = ModelTransformer.create().downgradeToV1(model); + + ShapeId integerShape = ShapeId.from("smithy.example#MyInteger"); + assertThat(downgraded.expectShape(integerShape).hasTrait(DefaultTrait.class), Matchers.is(false)); + assertThat(downgraded.expectShape(integerShape).hasTrait(BoxTrait.class), Matchers.is(true)); + + StructureShape dStruct = downgraded.expectShape(ShapeId.from("smithy.example#Struct"), StructureShape.class); + assertThat(dStruct.getAllMembers().get("foo").hasTrait(DefaultTrait.class), Matchers.is(false)); + + assertThat(dStruct.getAllMembers().get("baz").hasTrait(DefaultTrait.class), Matchers.is(true)); + assertThat(dStruct.getAllMembers().get("baz").hasTrait(BoxTrait.class), Matchers.is(true)); + + assertThat(dStruct.getAllMembers().get("bar").hasTrait(DefaultTrait.class), Matchers.is(false)); + assertThat(dStruct.getAllMembers().get("bar").hasTrait(AddedDefaultTrait.class), Matchers.is(false)); + + assertThat(dStruct.getAllMembers().get("bam").hasTrait(RequiredTrait.class), Matchers.is(true)); + assertThat(dStruct.getAllMembers().get("bam").hasTrait(ClientOptionalTrait.class), Matchers.is(false)); + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/data-mixins.1.0.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/data-mixins.1.0.json new file mode 100644 index 00000000000..5e888e0cd07 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/data-mixins.1.0.json @@ -0,0 +1,120 @@ +{ + "smithy": "1.0", + "shapes": { + "smithy.example#B": { + "type": "structure", + "members": {} + }, + "smithy.example#F": { + "type": "structure", + "members": {} + }, + "smithy.example#MixedBigDecimal": { + "type": "bigDecimal" + }, + "smithy.example#MixedBigInt": { + "type": "bigInteger" + }, + "smithy.example#MixedBlob": { + "type": "blob" + }, + "smithy.example#MixedBoolean": { + "type": "boolean", + "traits": { + "smithy.api#box": {} + } + }, + "smithy.example#MixedByte": { + "type": "byte", + "traits": { + "smithy.api#box": {} + } + }, + "smithy.example#MixedDocument": { + "type": "document" + }, + "smithy.example#MixedDouble": { + "type": "double", + "traits": { + "smithy.api#box": {} + } + }, + "smithy.example#MixedFloat": { + "type": "float", + "traits": { + "smithy.api#box": {} + } + }, + "smithy.example#MixedInteger": { + "type": "integer", + "traits": { + "smithy.api#box": {} + } + }, + "smithy.example#MixedList": { + "type": "list", + "member": { + "target": "smithy.api#String" + } + }, + "smithy.example#MixedLong": { + "type": "long", + "traits": { + "smithy.api#box": {} + } + }, + "smithy.example#MixedMap": { + "type": "map", + "key": { + "target": "smithy.api#String" + }, + "value": { + "target": "smithy.api#String" + } + }, + "smithy.example#MixedSet": { + "type": "list", + "member": { + "target": "smithy.api#String" + }, + "traits": { + "smithy.api#uniqueItems": {} + } + }, + "smithy.example#MixedShort": { + "type": "short", + "traits": { + "smithy.api#box": {} + } + }, + "smithy.example#MixedString": { + "type": "string" + }, + "smithy.example#MixedTimestamp": { + "type": "timestamp" + }, + "smithy.example#PrimitiveInteger": { + "type": "integer" + }, + "smithy.example#Struct": { + "type": "structure", + "members": { + "foo": { + "target": "smithy.example#PrimitiveInteger" + }, + "bar": { + "target": "smithy.example#PrimitiveInteger", + "traits": { + "smithy.api#box": {} + } + }, + "baz": { + "target": "smithy.example#MixedInteger" + }, + "bam": { + "target": "smithy.example#MixedInteger" + } + } + } + } +} 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 index a2a23d2f915..e203875c29d 100644 --- 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 @@ -254,6 +254,38 @@ "mixins": [{ "target": "smithy.example#MixinMap" }] + }, + "smithy.example#PrimitiveInteger": { + "type": "integer", + "traits": { + "smithy.api#default": 0 + } + }, + "smithy.example#Struct": { + "type": "structure", + "members": { + "foo": { + "target": "smithy.example#PrimitiveInteger", + "traits": { + "smithy.api#default": 0 + } + }, + "bar": { + "target": "smithy.example#PrimitiveInteger", + "traits": { + "smithy.api#default": null + } + }, + "baz": { + "target": "smithy.example#MixedInteger" + }, + "bam": { + "target": "smithy.example#MixedInteger", + "traits": { + "smithy.api#default": 1 + } + } + } } } } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/enums.1.0.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/enums.1.0.json new file mode 100644 index 00000000000..244ac9414c2 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/enums.1.0.json @@ -0,0 +1,23 @@ +{ + "smithy": "1.0", + "shapes": { + "smithy.example#IntEnum": { + "type": "integer" + }, + "smithy.example#StringEnum": { + "type": "string", + "traits": { + "smithy.api#enum": [ + { + "value": "foo", + "name": "FOO" + }, + { + "value": "bar", + "name": "BAR" + } + ] + } + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/operation-mixins.1.0.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/operation-mixins.1.0.json new file mode 100644 index 00000000000..7bfdaeb0288 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/operation-mixins.1.0.json @@ -0,0 +1,29 @@ +{ + "smithy": "1.0", + "shapes": { + "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" + } + ], + "traits": { + "smithy.api#internal": {} + } + } + } +} 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 index af5f24087fd..1fef5f248c0 100644 --- 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 @@ -5,7 +5,8 @@ "type": "structure", "members": {}, "traits": { - "smithy.api#error": "server" + "smithy.api#error": "server", + "smithy.api#mixin": {} } }, "smithy.example#MixinOperation": { @@ -17,7 +18,7 @@ "target": "smithy.api#Unit" }, "errors": [{ - "target": "smithy.example#MixinError" + "target": "smithy.example#ConcreteError" }], "traits": { "smithy.api#mixin": {}, @@ -27,6 +28,11 @@ "smithy.example#ConcreteError": { "type": "structure", "members": {}, + "mixins": [ + { + "target": "smithy.example#MixinError" + } + ], "traits": { "smithy.api#error": "client" } diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/resource-mixins.1.0.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/resource-mixins.1.0.json new file mode 100644 index 00000000000..8a0a99074d7 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/resource-mixins.1.0.json @@ -0,0 +1,11 @@ +{ + "smithy": "1.0", + "shapes": { + "smithy.example#MixedResource": { + "type": "resource", + "traits": { + "smithy.api#internal": {} + } + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins-with-overrides.1.0.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins-with-overrides.1.0.json new file mode 100644 index 00000000000..8362631ed7b --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins-with-overrides.1.0.json @@ -0,0 +1,99 @@ +{ + "smithy": "1.0", + "shapes": { + "smithy.example#MixedError": { + "type": "structure", + "members": { + "message": { + "target": "smithy.example#MixedRename" + } + }, + "traits": { + "smithy.api#error": "server" + } + }, + "smithy.example#MixedOperation": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + } + }, + "smithy.example#MixedRename": { + "type": "string" + }, + "smithy.example#MixedResource": { + "type": "resource" + }, + "smithy.example#MixedService": { + "type": "service", + "version": "2022-01-01", + "operations": [ + { + "target": "smithy.example#MixedOperation" + }, + { + "target": "smithy.example#MixinOperation" + } + ], + "resources": [ + { + "target": "smithy.example#MixedResource" + }, + { + "target": "smithy.example#MixinResource" + } + ], + "errors": [ + { + "target": "smithy.example#MixedError" + }, + { + "target": "smithy.example#MixinError" + } + ], + "rename": { + "smithy.example#MixinRename": "ThisWillBeInherited", + "smithy.example#OverriddenRename": "Override", + "smithy.example#MixedRename": "LocalRename" + }, + "traits": { + "smithy.api#internal": {} + } + }, + "smithy.example#MixinError": { + "type": "structure", + "members": { + "message": { + "target": "smithy.example#MixinRename" + }, + "state": { + "target": "smithy.example#OverriddenRename" + } + }, + "traits": { + "smithy.api#error": "client" + } + }, + "smithy.example#MixinOperation": { + "type": "operation", + "input": { + "target": "smithy.api#Unit" + }, + "output": { + "target": "smithy.api#Unit" + } + }, + "smithy.example#MixinRename": { + "type": "string" + }, + "smithy.example#MixinResource": { + "type": "resource" + }, + "smithy.example#OverriddenRename": { + "type": "string" + } + } +} diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins.1.0.json b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins.1.0.json new file mode 100644 index 00000000000..2b444d5dab5 --- /dev/null +++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/ast-serialization/cases/service-mixins.1.0.json @@ -0,0 +1,56 @@ +{ + "smithy": "1.0", + "shapes": { + "smithy.example#MixedService": { + "type": "service", + "version": "2021-12-31", + "operations": [ + { + "target": "smithy.example#MixinOperation" + } + ], + "resources": [ + { + "target": "smithy.example#MixinResource" + } + ], + "errors": [ + { + "target": "smithy.example#MixinError" + } + ], + "rename": { + "smithy.example#ShapeToRename": "RenamedShape" + }, + "traits": { + "smithy.api#internal": {} + } + }, + "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#ShapeToRename": { + "type": "string" + } + } +}