Repository: cjbooms/fabrikt
Pull request #87 (2021-11-25 12:44:10)

Adding reverse lookup function to generated enums
Adds the following `fromValue` reverse lookup function to generated enums:
```kotlin
enum class ContentType(
    @JsonValue
    val value: String
) {
    APPLICATION_JSON("application/json"),

    APPLICATION_X_SOME_TYPE_JSON("application/x.some-type+json"),

    APPLICATION_X_SOME_OTHER_TYPE_JSON_VERSION_2("application/x.some-other-type+json;version=2");

    companion object {
        private val mapping: Map<String, ContentType> = values().associateBy(ContentType::value)

        fun fromValue(value: String): ContentType? = mapping[value]
    }
}
```

cjbooms: 👍 

Patch:
diff --git a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
index 7652b35..1ab16c9 100644
--- a/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
+++ b/src/main/kotlin/com/cjbooms/fabrikt/generators/model/JacksonModelGenerator.kt
@@ -50,6 +50,7 @@ import com.squareup.kotlinpoet.ClassName
 import com.squareup.kotlinpoet.CodeBlock
 import com.squareup.kotlinpoet.FunSpec
 import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterSpec
 import com.squareup.kotlinpoet.PropertySpec
 import com.squareup.kotlinpoet.TypeName
 import com.squareup.kotlinpoet.TypeSpec
@@ -279,13 +280,9 @@ class JacksonModelGenerator(
             .filter { Overlay.of(it).isPresent }
 
     private fun buildEnumClass(enum: KotlinTypeInfo.Enum): TypeSpec {
+        val enumType = generatedType(packages.base, enum.enumClassName)
         val classBuilder = TypeSpec
-            .enumBuilder(
-                generatedType(
-                    packages.base,
-                    enum.enumClassName
-                )
-            )
+            .enumBuilder(enumType)
             .primaryConstructor(
                 FunSpec.constructorBuilder()
                     .addParameter("value", String::class)
@@ -307,7 +304,22 @@ class JacksonModelGenerator(
                 .initializer("value")
                 .build()
         )
-        return classBuilder.build()
+        val companion = TypeSpec.companionObjectBuilder()
+            .addProperty(
+                PropertySpec.builder("mapping", createMapOfStringToType(enumType))
+                    .initializer("values().associateBy(%T::value)", enumType)
+                    .addModifiers(KModifier.PRIVATE)
+                    .build()
+            )
+            .addFunction(
+                FunSpec.builder("fromValue")
+                    .addParameter(ParameterSpec.builder("value", String::class).build())
+                    .returns(enumType.copy(nullable = true))
+                    .addStatement("return mapping[value]")
+                    .build()
+            )
+            .build()
+        return classBuilder.addType(companion).build()
     }
 
     private fun buildMapModel(mapField: PropertyInfo.MapField): TypeSpec? =


Test patch:
diff --git a/src/test/resources/examples/defaultValues/models/Models.kt b/src/test/resources/examples/defaultValues/models/Models.kt
index 25db828..4c94ffa 100644
--- a/src/test/resources/examples/defaultValues/models/Models.kt
+++ b/src/test/resources/examples/defaultValues/models/Models.kt
@@ -6,6 +6,7 @@ import javax.validation.constraints.NotNull
 import kotlin.Boolean
 import kotlin.Int
 import kotlin.String
+import kotlin.collections.Map
 
 data class PersonWithDefaults(
     @param:JsonProperty("required_so_default_ignored")
@@ -37,4 +38,11 @@ enum class PersonWithDefaultsEnumDefault(
     TALL("tall"),
 
     SHORT("short");
+
+    companion object {
+        private val mapping: Map<String, PersonWithDefaultsEnumDefault> =
+            values().associateBy(PersonWithDefaultsEnumDefault::value)
+
+        fun fromValue(value: String): PersonWithDefaultsEnumDefault? = mapping[value]
+    }
 }
diff --git a/src/test/resources/examples/enumExamples/api.yaml b/src/test/resources/examples/enumExamples/api.yaml
index 341039e..41627ce 100644
--- a/src/test/resources/examples/enumExamples/api.yaml
+++ b/src/test/resources/examples/enumExamples/api.yaml
@@ -44,7 +44,7 @@ components:
       x-extensible-enum:
         - active
         - inactive
-        - 
+ 
     ContentType:
       type: string
       x-extensible-enum:
diff --git a/src/test/resources/examples/enumExamples/models/Models.kt b/src/test/resources/examples/enumExamples/models/Models.kt
index 8be593b..d1f0417 100644
--- a/src/test/resources/examples/enumExamples/models/Models.kt
+++ b/src/test/resources/examples/enumExamples/models/Models.kt
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
 import com.fasterxml.jackson.annotation.JsonValue
 import kotlin.String
 import kotlin.collections.List
+import kotlin.collections.Map
 
 data class EnumHolder(
     @param:JsonProperty("array_of_enums")
@@ -30,6 +31,13 @@ enum class EnumHolderArrayOfEnums(
     ARRAY_ENUM_ONE("array_enum_one"),
 
     ARRAY_ENUM_TWO("array_enum_two");
+
+    companion object {
+        private val mapping: Map<String, EnumHolderArrayOfEnums> =
+            values().associateBy(EnumHolderArrayOfEnums::value)
+
+        fun fromValue(value: String): EnumHolderArrayOfEnums? = mapping[value]
+    }
 }
 
 enum class EnumHolderInlinedEnum(
@@ -41,6 +49,13 @@ enum class EnumHolderInlinedEnum(
     INLINED_TWO("inlined_two"),
 
     INLINED_THREE("inlined_three");
+
+    companion object {
+        private val mapping: Map<String, EnumHolderInlinedEnum> =
+            values().associateBy(EnumHolderInlinedEnum::value)
+
+        fun fromValue(value: String): EnumHolderInlinedEnum? = mapping[value]
+    }
 }
 
 enum class EnumHolderInlinedExtensibleEnum(
@@ -52,6 +67,13 @@ enum class EnumHolderInlinedExtensibleEnum(
     INLINED_TWO("inlined_two"),
 
     INLINED_THREE("inlined_three");
+
+    companion object {
+        private val mapping: Map<String, EnumHolderInlinedExtensibleEnum> =
+            values().associateBy(EnumHolderInlinedExtensibleEnum::value)
+
+        fun fromValue(value: String): EnumHolderInlinedExtensibleEnum? = mapping[value]
+    }
 }
 
 enum class EnumObject(
@@ -63,6 +85,12 @@ enum class EnumObject(
     TWO("two"),
 
     THREE("three");
+
+    companion object {
+        private val mapping: Map<String, EnumObject> = values().associateBy(EnumObject::value)
+
+        fun fromValue(value: String): EnumObject? = mapping[value]
+    }
 }
 
 enum class ExtensibleEnumObject(
@@ -72,6 +100,13 @@ enum class ExtensibleEnumObject(
     ACTIVE("active"),
 
     INACTIVE("inactive");
+
+    companion object {
+        private val mapping: Map<String, ExtensibleEnumObject> =
+            values().associateBy(ExtensibleEnumObject::value)
+
+        fun fromValue(value: String): ExtensibleEnumObject? = mapping[value]
+    }
 }
 
 enum class ContentType(
@@ -83,4 +118,10 @@ enum class ContentType(
     APPLICATION_X_SOME_TYPE_JSON("application/x.some-type+json"),
 
     APPLICATION_X_SOME_OTHER_TYPE_JSON_VERSION_2("application/x.some-other-type+json;version=2");
+
+    companion object {
+        private val mapping: Map<String, ContentType> = values().associateBy(ContentType::value)
+
+        fun fromValue(value: String): ContentType? = mapping[value]
+    }
 }
diff --git a/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt b/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
index b7d8ec2..820c176 100644
--- a/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
+++ b/src/test/resources/examples/enumPolymorphicDiscriminator/models/Models.kt
@@ -6,6 +6,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo
 import com.fasterxml.jackson.annotation.JsonValue
 import javax.validation.constraints.NotNull
 import kotlin.String
+import kotlin.collections.Map
 
 @JsonTypeInfo(
     use = JsonTypeInfo.Id.NAME,
@@ -39,6 +40,13 @@ enum class EnumDiscriminator(
     OBJ_TWO_FIRST("obj_two_first"),
 
     OBJ_TWO_SECOND("obj_two_second");
+
+    companion object {
+        private val mapping: Map<String, EnumDiscriminator> =
+            values().associateBy(EnumDiscriminator::value)
+
+        fun fromValue(value: String): EnumDiscriminator? = mapping[value]
+    }
 }
 
 data class ConcreteImplOne(
diff --git a/src/test/resources/examples/externalReferences/models/Models.kt b/src/test/resources/examples/externalReferences/models/Models.kt
index 6aa9048..d96f00a 100644
--- a/src/test/resources/examples/externalReferences/models/Models.kt
+++ b/src/test/resources/examples/externalReferences/models/Models.kt
@@ -41,6 +41,13 @@ enum class ExternalObjectThreeEnum(
     TWO("two"),
 
     THREE("three");
+
+    companion object {
+        private val mapping: Map<String, ExternalObjectThreeEnum> =
+            values().associateBy(ExternalObjectThreeEnum::value)
+
+        fun fromValue(value: String): ExternalObjectThreeEnum? = mapping[value]
+    }
 }
 
 data class ExternalObjectThree(
diff --git a/src/test/resources/examples/githubApi/models/Models.kt b/src/test/resources/examples/githubApi/models/Models.kt
index a5bf47a..41794dd 100644
--- a/src/test/resources/examples/githubApi/models/Models.kt
+++ b/src/test/resources/examples/githubApi/models/Models.kt
@@ -116,6 +116,13 @@ enum class ContributorStatus(
     ACTIVE("active"),
 
     INACTIVE("inactive");
+
+    companion object {
+        private val mapping: Map<String, ContributorStatus> =
+            values().associateBy(ContributorStatus::value)
+
+        fun fromValue(value: String): ContributorStatus? = mapping[value]
+    }
 }
 
 data class Contributor(
@@ -181,6 +188,13 @@ enum class OrganisationStatus(
     ACTIVE("active"),
 
     INACTIVE("inactive");
+
+    companion object {
+        private val mapping: Map<String, OrganisationStatus> =
+            values().associateBy(OrganisationStatus::value)
+
+        fun fromValue(value: String): OrganisationStatus? = mapping[value]
+    }
 }
 
 data class Webhook(
@@ -260,6 +274,13 @@ enum class RepositoryStatus(
     ACTIVE("active"),
 
     INACTIVE("inactive");
+
+    companion object {
+        private val mapping: Map<String, RepositoryStatus> =
+            values().associateBy(RepositoryStatus::value)
+
+        fun fromValue(value: String): RepositoryStatus? = mapping[value]
+    }
 }
 
 enum class RepositoryVisibility(
@@ -269,6 +290,13 @@ enum class RepositoryVisibility(
     PRIVATE("Private"),
 
     PUBLIC("Public");
+
+    companion object {
+        private val mapping: Map<String, RepositoryVisibility> =
+            values().associateBy(RepositoryVisibility::value)
+
+        fun fromValue(value: String): RepositoryVisibility? = mapping[value]
+    }
 }
 
 data class Repository(
@@ -341,6 +369,13 @@ enum class PullRequestStatus(
     ACTIVE("active"),
 
     INACTIVE("inactive");
+
+    companion object {
+        private val mapping: Map<String, PullRequestStatus> =
+            values().associateBy(PullRequestStatus::value)
+
+        fun fromValue(value: String): PullRequestStatus? = mapping[value]
+    }
 }
 
 data class Author(
@@ -406,4 +441,11 @@ enum class StatusQueryParam(
     INACTIVE("inactive"),
 
     ALL("all");
+
+    companion object {
+        private val mapping: Map<String, StatusQueryParam> =
+            values().associateBy(StatusQueryParam::value)
+
+        fun fromValue(value: String): StatusQueryParam? = mapping[value]
+    }
 }
diff --git a/src/test/resources/examples/javaSerializableModels/models/Models.kt b/src/test/resources/examples/javaSerializableModels/models/Models.kt
index d446eae..e8e5f7f 100644
--- a/src/test/resources/examples/javaSerializableModels/models/Models.kt
+++ b/src/test/resources/examples/javaSerializableModels/models/Models.kt
@@ -13,6 +13,7 @@ import kotlin.Boolean
 import kotlin.Int
 import kotlin.String
 import kotlin.collections.List
+import kotlin.collections.Map
 
 data class QueryResult(
     @param:JsonProperty("items")
@@ -59,6 +60,13 @@ enum class ContentThirdAttr(
     ENUM_TYPE_1("enum_type_1"),
 
     ENUM_TYPE_2("enum_type_2");
+
+    companion object {
+        private val mapping: Map<String, ContentThirdAttr> =
+            values().associateBy(ContentThirdAttr::value)
+
+        fun fromValue(value: String): ContentThirdAttr? = mapping[value]
+    }
 }
 
 enum class ContentModelType(
@@ -70,6 +78,13 @@ enum class ContentModelType(
     SECOND_MODEL("second_model"),
 
     THIRD_MODEL("third_model");
+
+    companion object {
+        private val mapping: Map<String, ContentModelType> =
+            values().associateBy(ContentModelType::value)
+
+        fun fromValue(value: String): ContentModelType? = mapping[value]
+    }
 }
 
 data class FirstModel(
diff --git a/src/test/resources/examples/micronautIntrospectedModels/models/Models.kt b/src/test/resources/examples/micronautIntrospectedModels/models/Models.kt
index 8ade43e..b8dde39 100644
--- a/src/test/resources/examples/micronautIntrospectedModels/models/Models.kt
+++ b/src/test/resources/examples/micronautIntrospectedModels/models/Models.kt
@@ -13,6 +13,7 @@ import kotlin.Boolean
 import kotlin.Int
 import kotlin.String
 import kotlin.collections.List
+import kotlin.collections.Map
 
 @Introspected
 data class QueryResult(
@@ -62,6 +63,13 @@ enum class ContentThirdAttr(
     ENUM_TYPE_1("enum_type_1"),
 
     ENUM_TYPE_2("enum_type_2");
+
+    companion object {
+        private val mapping: Map<String, ContentThirdAttr> =
+            values().associateBy(ContentThirdAttr::value)
+
+        fun fromValue(value: String): ContentThirdAttr? = mapping[value]
+    }
 }
 
 @Introspected
@@ -74,6 +82,13 @@ enum class ContentModelType(
     SECOND_MODEL("second_model"),
 
     THIRD_MODEL("third_model");
+
+    companion object {
+        private val mapping: Map<String, ContentModelType> =
+            values().associateBy(ContentModelType::value)
+
+        fun fromValue(value: String): ContentModelType? = mapping[value]
+    }
 }
 
 @Introspected
diff --git a/src/test/resources/examples/okHttpClient/models/Models.kt b/src/test/resources/examples/okHttpClient/models/Models.kt
index 045d577..ad04465 100644
--- a/src/test/resources/examples/okHttpClient/models/Models.kt
+++ b/src/test/resources/examples/okHttpClient/models/Models.kt
@@ -12,6 +12,7 @@ import kotlin.Boolean
 import kotlin.Int
 import kotlin.String
 import kotlin.collections.List
+import kotlin.collections.Map
 
 data class QueryResult(
     @param:JsonProperty("items")
@@ -58,6 +59,13 @@ enum class ContentThirdAttr(
     ENUM_TYPE_1("enum_type_1"),
 
     ENUM_TYPE_2("enum_type_2");
+
+    companion object {
+        private val mapping: Map<String, ContentThirdAttr> =
+            values().associateBy(ContentThirdAttr::value)
+
+        fun fromValue(value: String): ContentThirdAttr? = mapping[value]
+    }
 }
 
 enum class ContentModelType(
@@ -69,6 +77,13 @@ enum class ContentModelType(
     SECOND_MODEL("second_model"),
 
     THIRD_MODEL("third_model");
+
+    companion object {
+        private val mapping: Map<String, ContentModelType> =
+            values().associateBy(ContentModelType::value)
+
+        fun fromValue(value: String): ContentModelType? = mapping[value]
+    }
 }
 
 data class FirstModel(
diff --git a/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt b/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt
index 3d2cbfc..12681de 100644
--- a/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt
+++ b/src/test/resources/examples/okHttpClientMultiMediaType/models/Models.kt
@@ -12,6 +12,7 @@ import kotlin.Boolean
 import kotlin.Int
 import kotlin.String
 import kotlin.collections.List
+import kotlin.collections.Map
 
 enum class ContentType(
     @JsonValue
@@ -20,6 +21,12 @@ enum class ContentType(
     APPLICATION_JSON("application/json"),
 
     APPLICATION_VND_CUSTOM_MEDIA_JSON("application/vnd.custom.media+json");
+
+    companion object {
+        private val mapping: Map<String, ContentType> = values().associateBy(ContentType::value)
+
+        fun fromValue(value: String): ContentType? = mapping[value]
+    }
 }
 
 data class QueryResult(
@@ -67,6 +74,13 @@ enum class ContentThirdAttr(
     ENUM_TYPE_1("enum_type_1"),
 
     ENUM_TYPE_2("enum_type_2");
+
+    companion object {
+        private val mapping: Map<String, ContentThirdAttr> =
+            values().associateBy(ContentThirdAttr::value)
+
+        fun fromValue(value: String): ContentThirdAttr? = mapping[value]
+    }
 }
 
 enum class ContentModelType(
@@ -78,6 +92,13 @@ enum class ContentModelType(
     SECOND_MODEL("second_model"),
 
     THIRD_MODEL("third_model");
+
+    companion object {
+        private val mapping: Map<String, ContentModelType> =
+            values().associateBy(ContentModelType::value)
+
+        fun fromValue(value: String): ContentModelType? = mapping[value]
+    }
 }
 
 data class FirstModel(
diff --git a/src/test/resources/examples/quarkusReflectionModels/models/Models.kt b/src/test/resources/examples/quarkusReflectionModels/models/Models.kt
index bc2dbc7..2811f0c 100644
--- a/src/test/resources/examples/quarkusReflectionModels/models/Models.kt
+++ b/src/test/resources/examples/quarkusReflectionModels/models/Models.kt
@@ -13,6 +13,7 @@ import kotlin.Boolean
 import kotlin.Int
 import kotlin.String
 import kotlin.collections.List
+import kotlin.collections.Map
 
 @RegisterForReflection
 data class QueryResult(
@@ -62,6 +63,13 @@ enum class ContentThirdAttr(
     ENUM_TYPE_1("enum_type_1"),
 
     ENUM_TYPE_2("enum_type_2");
+
+    companion object {
+        private val mapping: Map<String, ContentThirdAttr> =
+            values().associateBy(ContentThirdAttr::value)
+
+        fun fromValue(value: String): ContentThirdAttr? = mapping[value]
+    }
 }
 
 @RegisterForReflection
@@ -74,6 +82,13 @@ enum class ContentModelType(
     SECOND_MODEL("second_model"),
 
     THIRD_MODEL("third_model");
+
+    companion object {
+        private val mapping: Map<String, ContentModelType> =
+            values().associateBy(ContentModelType::value)
+
+        fun fromValue(value: String): ContentModelType? = mapping[value]
+    }
 }
 
 @RegisterForReflection