Repository: smithy-lang/smithy (java)

Issue:
SmithyIdlModelSerializer produces broken model
Hi, 

Given following smithy model:
```smithy
$version: "2"

namespace test

@mixin
enum FooMixin {
  BAZ = "baz"
}
     
enum Foo with [FooMixin] {
  BAZ = "baz"
}
```
**Note that both the enum and the mixin defines the same entry**

## Actual behavior

`Model.assembler()` assembles it without any issues. 

But after serializing the model with `SmithyIdlModelSerializer` and reading it again with assembler, the assembler throws an error:
```
Exception in thread "main" software.amazon.smithy.model.validation.ValidatedResultException: Result contained ERROR severity validation events:
[ERROR] test#Foo: Attempted to use test#FooMixin as a mixin, but it is not marked with the mixin trait | Target test.smithy:5:1
        at software.amazon.smithy.model.validation.ValidatedResult.validate(ValidatedResult.java:145)
        at software.amazon.smithy.model.validation.ValidatedResult.unwrap(ValidatedResult.java:132)
        at foo$package$.hello(foo.scala:51)
        at hello.main(foo.scala:14)
```

that is because the model is incorrectly serialized as 
```smithy
$version: "2.0"

namespace test

enum Foo with [FooMixin] {}

apply Foo$BAZ
@mixin
enum FooMixin {
    BAZ = "baz"
}
```



## Expected behavior

Either `Model.assembler()` when reading the duplicated enum entry definition should raise a `ValidationError` or `SmithyIdlModelSerializer` should correctly serialize this model so that reading it for the second time yields the same results as for the first time. 

## Full reproduce code

Following snippet was created with the use of [scala-cli](https://scala-cli.virtuslab.org/)

```scala
//> using scala "3.3.0"
//> using lib "com.lihaoyi::os-lib:0.8.1"
//> using lib "software.amazon.smithy:smithy-model:1.34.0"
​
import software.amazon.smithy.model.Model
import scala.collection.immutable.ListMap
import java.nio.file.Files
import java.nio.file.Paths
import java.nio.file.Path
​
import software.amazon.smithy.model.shapes.SmithyIdlModelSerializer
import collection.JavaConverters._
​
@main def hello() = {
​
  val content =
    """$version: "2"
      |
      |namespace test
      |
      |@mixin
      |enum FooMixin {
      |    BAZ = "baz"
      |}
      |
      |enum Foo with [FooMixin] {
      |    BAZ = "baz"
      |}""".stripMargin
​
  val assembler = Model.assembler()
  assembler
    .addUnparsedModel("issue.smithy", content)
​
  val model = assembler.assemble().unwrap()
​
  val serializer =
    SmithyIdlModelSerializer
      .builder
      .build
​
  val contentMapping: Map[Path, String] =
    serializer
      .serialize(model)
      .asScala
      .toMap
​
  println(contentMapping.toString())
​
  val assembler2 = Model.assembler()
  contentMapping.foreach { case (k, v) => assembler2.addUnparsedModel(k.toString(), v) }
  assembler2.assemble().unwrap()
}
```



Pull request:
Fix IDL serialization of enums with mixins
Fixes #1864 

This commit fixes an issue where the IDL serializer would write an empty apply statement on an enum member that was mixed in, as the value was written using the special IDL syntax for enum values.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.


Patch:
diff --git a/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuild.java b/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuild.java
index 136b03f77..6c6429eca 100644
--- a/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuild.java
+++ b/smithy-build/src/main/java/software/amazon/smithy/build/SmithyBuild.java
@@ -15,8 +15,6 @@
 
 package software.amazon.smithy.build;
 
-import java.io.IOException;
-import java.io.UncheckedIOException;
 import java.nio.file.FileSystems;
 import java.nio.file.Files;
 import java.nio.file.Path;
@@ -199,23 +197,10 @@ public final class SmithyBuild {
     //
     // Ignores and logs when an unsupported model file is encountered.
     private void addSource(Path path) {
-        try {
-            if (Files.isDirectory(path)) {
-                // Pre-emptively crawl the given models to filter them up front into a flat, file-only, list.
-                Files.list(path).filter(Files::isRegularFile).forEach(this::addSourceFile);
-            } else {
-                addSourceFile(path);
-            }
-        } catch (IOException e) {
-            throw new UncheckedIOException(e);
-        }
-    }
-
-    private void addSourceFile(Path file) {
-        if (!VALID_MODELS.matches(file.getFileName())) {
-            LOGGER.warning("Omitting unsupported Smithy model file from model sources: " + file);
+        if (Files.isRegularFile(path) && !VALID_MODELS.matches(path.getFileName())) {
+            LOGGER.warning("Omitting unsupported Smithy model file from model sources: " + path);
         } else {
-            sources.add(file.toAbsolutePath());
+            sources.add(path.toAbsolutePath());
         }
     }
 
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 8404fb9de..08ae9d87f 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
@@ -477,12 +477,17 @@ public final class SmithyIdlModelSerializer {
 
         private void applyIntroducedTraits(Collection<MemberShape> members) {
             for (MemberShape member : members) {
+                Map<ShapeId, Trait> introducedTraits = new LinkedHashMap<>(member.getIntroducedTraits());
+
+                // The @enumValue trait is serialized using the `=` IDL syntax, so remove it here.
+                introducedTraits.remove(EnumValueTrait.ID);
+
                 // Use short form for a single trait, and block form for multiple traits.
-                if (member.getIntroducedTraits().size() == 1) {
+                if (introducedTraits.size() == 1) {
                     codeWriter.writeInline("apply $I ", member.getId());
                     serializeTraits(member.getIntroducedTraits(), TraitFeature.NO_SPECIAL_DOCS_SYNTAX);
                     codeWriter.write("");
-                } else if (!member.getIntroducedTraits().isEmpty()) {
+                } else if (!introducedTraits.isEmpty()) {
                     codeWriter.openBlock("apply $I {", "}", member.getId(), () -> {
                         // Only serialize local traits, and don't use special documentation syntax here.
                         serializeTraits(member.getIntroducedTraits(), TraitFeature.NO_SPECIAL_DOCS_SYNTAX);


Test patch:
diff --git a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializerTest.java b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializerTest.java
index 8aa756b55..62dedc3a6 100644
--- a/smithy-model/src/test/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializerTest.java
+++ b/smithy-model/src/test/java/software/amazon/smithy/model/shapes/SmithyIdlModelSerializerTest.java
@@ -243,4 +243,20 @@ public class SmithyIdlModelSerializerTest {
 
         assertThat(modelResult, equalTo(IoUtils.readUtf8Url(resource).replace("\r\n", "\n")));
     }
+
+    @Test
+    public void handlesEnumMixins() {
+        URL resource = getClass().getResource("idl-serialization/enum-mixin-input.smithy");
+        Model model = Model.assembler().addImport(resource).assemble().unwrap();
+        Map<Path, String> serialized = SmithyIdlModelSerializer.builder().build().serialize(model);
+
+        if (serialized.size() != 1) {
+            throw new RuntimeException("Exactly one smithy file should be output for generated tests.");
+        }
+
+        String expectedOutput = IoUtils.readUtf8Resource(getClass(), "idl-serialization/enum-mixin-output.smithy")
+                .replaceAll("\\R", "\n");
+        String serializedString = serialized.entrySet().iterator().next().getValue();
+        Assertions.assertEquals(expectedOutput, serializedString);
+    }
 }
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/enum-mixin-input.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/enum-mixin-input.smithy
new file mode 100644
index 000000000..d09dab55b
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/enum-mixin-input.smithy
@@ -0,0 +1,23 @@
+$version: "2.0"
+
+namespace ns.foo
+
+@mixin
+intEnum IntMixin {
+    FOO = 1
+    BAR = 2
+}
+
+intEnum IntEnum with [IntMixin] {
+    FOO = 1
+    BAR = 2
+}
+
+@mixin
+enum StringMixin {
+    BAZ = "baz"
+}
+
+enum StringEnum with [StringMixin] {
+    BAZ = "baz"
+}
diff --git a/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/enum-mixin-output.smithy b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/enum-mixin-output.smithy
new file mode 100644
index 000000000..560cb6b03
--- /dev/null
+++ b/smithy-model/src/test/resources/software/amazon/smithy/model/shapes/idl-serialization/enum-mixin-output.smithy
@@ -0,0 +1,18 @@
+$version: "2.0"
+
+namespace ns.foo
+
+intEnum IntEnum with [IntMixin] {}
+
+@mixin
+intEnum IntMixin {
+    FOO = 1
+    BAR = 2
+}
+
+enum StringEnum with [StringMixin] {}
+
+@mixin
+enum StringMixin {
+    BAZ = "baz"
+}