From 2231b9b908598c6a49b8b7e62500836867fb9a17 Mon Sep 17 00:00:00 2001 From: Daniil Penkin Date: Mon, 2 Oct 2023 20:04:20 +1100 Subject: [PATCH] Quote enum item names when necessary (#241) (#242) Add backticks to the enum item name in case it doesn't start with a letter or an underscore and hence is not a valid Kotlin identifier. --- .../cjbooms/fabrikt/generators/OasDefault.kt | 4 ++-- .../cjbooms/fabrikt/util/NormalisedString.kt | 4 +++- .../fabrikt/util/NormalisedStringTest.kt | 10 +++++++++ .../resources/examples/defaultValues/api.yaml | 6 ++++++ .../examples/defaultValues/models/Models.kt | 21 +++++++++++++++++++ .../resources/examples/enumExamples/api.yaml | 3 +++ .../examples/enumExamples/models/Models.kt | 3 +++ 7 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/OasDefault.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/OasDefault.kt index 76f25aca..b94ad5c5 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/generators/OasDefault.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/OasDefault.kt @@ -1,7 +1,7 @@ package com.cjbooms.fabrikt.generators import com.cjbooms.fabrikt.model.KotlinTypeInfo -import com.cjbooms.fabrikt.util.toUpperCase +import com.cjbooms.fabrikt.util.NormalisedString.toEnumName import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.CodeBlock import com.squareup.kotlinpoet.TypeName @@ -39,7 +39,7 @@ sealed class OasDefault { data class EnumValue(val type: TypeName, val enumValue: String) : OasDefault() { override fun getDefault(): CodeBlock = - CodeBlock.of("%T.${enumValue.toUpperCase()}", type) + CodeBlock.of("%T.${enumValue.toEnumName()}", type) } data class JsonNullableValue(val inner: OasDefault) : OasDefault() { diff --git a/src/main/kotlin/com/cjbooms/fabrikt/util/NormalisedString.kt b/src/main/kotlin/com/cjbooms/fabrikt/util/NormalisedString.kt index f957d11c..6f146991 100644 --- a/src/main/kotlin/com/cjbooms/fabrikt/util/NormalisedString.kt +++ b/src/main/kotlin/com/cjbooms/fabrikt/util/NormalisedString.kt @@ -30,6 +30,7 @@ object NormalisedString { replaceSpecialCharacters() .camelToSnake() .toUpperCase() + .quoteIfNotValidIdentifier() fun String.toKotlinParameterName(): String = this.camelCase() @@ -42,4 +43,5 @@ object NormalisedString { fun String.toUpperCase() = uppercase(Locale.getDefault()) fun String.toLowerCase() = lowercase(Locale.getDefault()) fun String.capitalized() = replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } -fun String.decapitalized() = replaceFirstChar { it.lowercase(Locale.getDefault()) } \ No newline at end of file +fun String.decapitalized() = replaceFirstChar { it.lowercase(Locale.getDefault()) } +fun String.quoteIfNotValidIdentifier() = if (first().isLetter() || first() == '_') this else "`$this`" diff --git a/src/test/kotlin/com/cjbooms/fabrikt/util/NormalisedStringTest.kt b/src/test/kotlin/com/cjbooms/fabrikt/util/NormalisedStringTest.kt index c12314a1..94163e53 100644 --- a/src/test/kotlin/com/cjbooms/fabrikt/util/NormalisedStringTest.kt +++ b/src/test/kotlin/com/cjbooms/fabrikt/util/NormalisedStringTest.kt @@ -49,4 +49,14 @@ class NormalisedStringTest { assertThat("PascalCase_åØÆÅ_enumWith-special/characters.json".toEnumName()) .isEqualTo("PASCAL_CASE_Å_ØÆÅ_ENUM_WITH_SPECIAL_CHARACTERS_JSON") } + + @Test + fun `toEnumName should backtick enum if it starts with non-letter character`() { + assertThat("42".toEnumName()).isEqualTo("`42`") + } + + @Test + fun `toEnumName should not backtick enum if it starts with underscore character`() { + assertThat("_42".toEnumName()).isEqualTo("_42") + } } diff --git a/src/test/resources/examples/defaultValues/api.yaml b/src/test/resources/examples/defaultValues/api.yaml index ed530e3c..aa9c3b15 100644 --- a/src/test/resources/examples/defaultValues/api.yaml +++ b/src/test/resources/examples/defaultValues/api.yaml @@ -18,6 +18,12 @@ components: - tall - short default: tall + enum_quoted_default: + type: string + enum: + - 1x + - 2x + default: 2x boolean_default: type: boolean default: true diff --git a/src/test/resources/examples/defaultValues/models/Models.kt b/src/test/resources/examples/defaultValues/models/Models.kt index e918523d..ec06304f 100644 --- a/src/test/resources/examples/defaultValues/models/Models.kt +++ b/src/test/resources/examples/defaultValues/models/Models.kt @@ -24,6 +24,11 @@ public data class PersonWithDefaults( @get:JsonProperty("enum_default") @get:NotNull public val enumDefault: PersonWithDefaultsEnumDefault = PersonWithDefaultsEnumDefault.TALL, + @param:JsonProperty("enum_quoted_default") + @get:JsonProperty("enum_quoted_default") + @get:NotNull + public val enumQuotedDefault: PersonWithDefaultsEnumQuotedDefault = + PersonWithDefaultsEnumQuotedDefault.`2X`, @param:JsonProperty("boolean_default") @get:JsonProperty("boolean_default") @get:NotNull @@ -57,3 +62,19 @@ public enum class PersonWithDefaultsEnumDefault( public fun fromValue(`value`: String): PersonWithDefaultsEnumDefault? = mapping[value] } } + +public enum class PersonWithDefaultsEnumQuotedDefault( + @JsonValue + public val `value`: String, +) { + `1X`("1x"), + `2X`("2x"), + ; + + public companion object { + private val mapping: Map = + values().associateBy(PersonWithDefaultsEnumQuotedDefault::value) + + public fun fromValue(`value`: String): PersonWithDefaultsEnumQuotedDefault? = mapping[value] + } +} diff --git a/src/test/resources/examples/enumExamples/api.yaml b/src/test/resources/examples/enumExamples/api.yaml index 00f93bed..08b3c400 100644 --- a/src/test/resources/examples/enumExamples/api.yaml +++ b/src/test/resources/examples/enumExamples/api.yaml @@ -40,6 +40,9 @@ components: - one - two - three + - 4 + - -5 + - _6 ExtensibleEnumObject: type: string diff --git a/src/test/resources/examples/enumExamples/models/Models.kt b/src/test/resources/examples/enumExamples/models/Models.kt index 1b8929a9..6f3beb74 100644 --- a/src/test/resources/examples/enumExamples/models/Models.kt +++ b/src/test/resources/examples/enumExamples/models/Models.kt @@ -107,6 +107,9 @@ public enum class EnumObject( ONE("one"), TWO("two"), THREE("three"), + `4`("4"), + _5("-5"), + _6("_6"), ; public companion object {