Unverified Commit 2d71e489 authored by Russell Cohen's avatar Russell Cohen Committed by GitHub
Browse files

Add support for unnamed enumerations (#2)

* Add support for unnamed enumerations

Unnamed enumerations are supported by creating a "newtype" that wraps the string. The newtype provides the valid values as a list for convenience.

* Take intellij's suggestion to simplify

* Rename function to values()
parent ddac680d
Loading
Loading
Loading
Loading
+42 −19
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@

package software.amazon.smithy.rust.codegen.smithy.generators

import java.lang.IllegalStateException
import software.amazon.smithy.codegen.core.SymbolProvider
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.traits.EnumDefinition
@@ -12,15 +13,20 @@ import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.rust.codegen.lang.RustWriter
import software.amazon.smithy.rust.codegen.lang.rustBlock
import software.amazon.smithy.rust.codegen.smithy.RuntimeType
import software.amazon.smithy.rust.codegen.util.doubleQuote

class EnumGenerator(
    symbolProvider: SymbolProvider,
    private val writer: RustWriter,
    shape: StringShape,
    enumTrait: EnumTrait
    private val enumTrait: EnumTrait
) {
    companion object {
        const val Values = "values"
    }

    fun render() {
        if (enumTrait.hasNames()) {
            // pub enum Blah { V1, V2, .. }
            renderEnum()
            writer.insertTrailingNewline()
@@ -29,16 +35,39 @@ class EnumGenerator(
            writer.insertTrailingNewline()
            // impl Blah { pub fn as_str(&self) -> &str
            renderAsStr()
        } else {
            renderUnamedEnum()
        }
    }

    private fun EnumDefinition.derivedName(): String {
        // TODO: For unnamed enums, generate a newtype that wraps the string. We can provide a &[&str] with the valid
        // values.
    private fun renderUnamedEnum() {
        writer.write("#[derive(Debug, PartialEq, Eq, Clone)]")
        writer.write("pub struct $enumName(String);")
        writer.rustBlock("impl $enumName") {
            writer.rustBlock("pub fn as_str(&self) -> &str") {
                write("&self.0")
            }

            writer.rustBlock("pub fn ${Values}() -> &'static [&'static str]") {
                withBlock("&[", "]") {
                    val memberList = sortedMembers.joinToString(", ") { it.value.doubleQuote() }
                    write(memberList)
                }
            }
        }

        writer.rustBlock("impl <T> \$T<T> for $enumName where T: \$T<str>", RuntimeType.From, RuntimeType.AsRef) {
            writer.rustBlock("fn from(s: T) -> Self") {
                write("$enumName(s.as_ref().to_owned())")
            }
        }
    }

    private fun EnumDefinition.derivedName(): String {
        // Because enum variants always start with an upper case letter, they will never
        // conflict with reserved words (which are always lower case), therefore, we never need
        // to fall back to raw identifiers
        return deriveName(name.orElse(null), value)
        return name.orElse(null)?.toPascalCase() ?: throw IllegalStateException("Enum variants must be named to derive a name. This is a bug.")
    }

    private val sortedMembers: List<EnumDefinition> = enumTrait.values.sortedBy { it.value }
@@ -83,10 +112,4 @@ class EnumGenerator(
            }
        }
    }

    companion object {
        fun deriveName(name: String?, value: String): String {
            return name?.toPascalCase() ?: value.replace(" ", "_").toPascalCase().filter { it.isLetterOrDigit() }
        }
    }
}
+34 −5
Original line number Diff line number Diff line
@@ -5,10 +5,10 @@

package software.amazon.smithy.rust.codegen.generators

import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import software.amazon.smithy.codegen.core.SymbolProvider
import software.amazon.smithy.model.Model
import software.amazon.smithy.model.shapes.ShapeId
import software.amazon.smithy.model.shapes.StringShape
import software.amazon.smithy.model.traits.DocumentationTrait
import software.amazon.smithy.model.traits.EnumDefinition
@@ -16,6 +16,7 @@ import software.amazon.smithy.model.traits.EnumTrait
import software.amazon.smithy.rust.codegen.lang.RustWriter
import software.amazon.smithy.rust.codegen.smithy.SymbolVisitor
import software.amazon.smithy.rust.codegen.smithy.generators.EnumGenerator
import software.amazon.smithy.rust.testutil.asSmithy
import software.amazon.smithy.rust.testutil.quickTest
import software.amazon.smithy.rust.testutil.shouldCompile
import software.amazon.smithy.rust.testutil.shouldParseAsRust
@@ -63,9 +64,37 @@ class EnumGeneratorTest {
    }

    @Test
    fun `it derives reasonable names`() {
        EnumGenerator.deriveName(null, "Signal creation is in progress") shouldBe("SignalCreationIsInProgress")
        EnumGenerator.deriveName("as", "AS") shouldBe("As")
        EnumGenerator.deriveName(null, "Signal: creation is in progress") shouldBe("SignalCreationIsInProgress")
    fun `it generates unamed enums`() {
        val model = """
            namespace test
            @enum([
            {
                value: "Foo",
            },
            {
                value: "Baz",
            },
            {
                value: "Bar",
            },
            {
                value: "1",
            },
            {
                value: "0",
            },
        ])
        string FooEnum
        """.asSmithy()
        val shape = model.expectShape(ShapeId.from("test#FooEnum"), StringShape::class.java)
        val trait = shape.expectTrait(EnumTrait::class.java)
        val provider: SymbolProvider = SymbolVisitor(model, "test")
        val writer = RustWriter("model.rs", "model")
        val generator = EnumGenerator(provider, writer, shape, trait)
        generator.render()
        writer.shouldCompile("""
            // Values should be sorted
            assert_eq!(FooEnum::${EnumGenerator.Values}(), ["0", "1", "Bar", "Baz", "Foo"]);
        """)
    }
}