diff --git a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/Model.kt b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/Model.kt index ef82544..4dfac28 100644 --- a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/Model.kt +++ b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/Model.kt @@ -168,59 +168,35 @@ sealed interface Model { } } - sealed interface Union : Model { + data class Union( + val schema: Schema, + val context: NamingContext, + val schemas: List, + val inline: List, + val default: String? + ) : Model { + data class Entry(val context: NamingContext, val model: Model) + } + + sealed interface Enum : Model { val schema: Schema val context: NamingContext - val schemas: List - val inline: List - - fun isOpenEnumeration(): Boolean = - this is AnyOf && - schemas.size == 2 && - schemas.count { it.model is Enum } == 1 && - schemas.count { it.model is Primitive.String } == 1 - - data class UnionEntry(val context: NamingContext, val model: Model) + val values: List + val default: String? - /** [OneOf] is an untagged union. This is in Kotlin represented by a `sealed interface`. */ - data class OneOf( - override val schema: Schema, - override val context: NamingContext, - override val schemas: List, - override val inline: List, - val default: String? - ) : Union - - /** - * [AnyOf] is an untagged union, with overlapping schema. - * - * Typically: - * - open enumeration: anyOf a `string`, and [Enum] (also type: `string`). - * - [Object] with a [FreeFormJson], where [FreeFormJson] has overlapping schema with the - * [Object]. - */ - data class AnyOf( + data class Closed( override val schema: Schema, override val context: NamingContext, - override val schemas: List, - override val inline: List, - val default: String? - ) : Union + val inner: Model, + override val values: List, + override val default: String? + ) : Enum - /** [TypeArray] */ - data class TypeArray( + data class Open( override val schema: Schema, override val context: NamingContext, - override val schemas: List, - override val inline: List - ) : Union + override val values: List, + override val default: String? + ) : Enum } - - data class Enum( - val schema: Schema, - val context: NamingContext, - val inner: Model, - val values: List, - val default: String? - ) : Model } diff --git a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/NamingContext.kt b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/NamingContext.kt index 633a16b..9133980 100644 --- a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/NamingContext.kt +++ b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/NamingContext.kt @@ -15,14 +15,8 @@ sealed interface NamingContext { data class Named(override val name: String) : NamingContext + data class Nested(override val name: String, val outer: NamingContext) : NamingContext + data class RouteParam(override val name: String, val operationId: String?, val postfix: String) : NamingContext - - sealed interface Nested : NamingContext { - val outer: NamingContext - } - - data class Inline(override val name: String, override val outer: NamingContext) : Nested - - data class Ref(override val name: String, override val outer: NamingContext) : Nested } diff --git a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/OpenAPITransformer.kt b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/OpenAPITransformer.kt index 35ab4dc..4dc698a 100644 --- a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/OpenAPITransformer.kt +++ b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/OpenAPITransformer.kt @@ -2,9 +2,9 @@ package io.github.nomisrev.openapi import io.github.nomisrev.openapi.AdditionalProperties.Allowed import io.github.nomisrev.openapi.Model.Collection +import io.github.nomisrev.openapi.Model.Enum import io.github.nomisrev.openapi.Model.Object.Property import io.github.nomisrev.openapi.Model.Primitive -import io.github.nomisrev.openapi.Model.Union.TypeArray import io.github.nomisrev.openapi.NamingContext.Named import io.github.nomisrev.openapi.Schema.Type import io.github.nomisrev.openapi.http.MediaType.Companion.ApplicationJson @@ -15,7 +15,7 @@ import io.github.nomisrev.openapi.http.Method import io.github.nomisrev.openapi.http.StatusCode suspend fun OpenAPI.routes(sorter: ApiSorter = ApiSorter.ByPath): Root = - sorter.sort(OpenAPITransformer(this).routes()) + OpenAPITransformer(this).routes().let { sorter.sort(it) } fun OpenAPI.models(): Set = with(OpenAPITransformer(this)) { operationModels() + schemas() } @@ -140,29 +140,30 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { fun Schema.toModel(context: NamingContext): Model = when { + isOpenEnumeration() -> toOpenEnum(context, anyOf!!.firstNotNullOf { it.get().enum }) anyOf != null && anyOf?.size == 1 -> anyOf!![0].get().toModel(context) - anyOf != null -> toAnyOf(context, this, anyOf!!) oneOf != null && oneOf?.size == 1 -> oneOf!![0].get().toModel(context) - oneOf != null -> toOneOf(context, this, oneOf!!) + anyOf != null -> toUnion(context, anyOf!!) + oneOf != null -> toUnion(context, oneOf!!) allOf != null -> TODO("allOf") - enum != null -> - toEnum( - context, - this, - requireNotNull(type) { "Enum requires an inner type" }, - enum.orEmpty() - ) + enum != null -> toEnum(context, enum.orEmpty()) properties != null -> asObject(context) - type != null -> type(context, this, type!!) + type != null -> this.type(context, type!!) else -> TODO("Schema: $this not yet supported. Please report to issue tracker.") } + fun Schema.isOpenEnumeration(): Boolean = + anyOf != null && + anyOf!!.size == 2 && + anyOf!!.count { it.get().enum != null } == 1 && + anyOf!!.count { it.get().type == Type.Basic.String } == 1 + private fun Schema.asObject(context: NamingContext): Model = when { - properties != null -> toObject(context, this, required ?: emptyList(), properties!!) + properties != null -> this.toObject(context, properties!!) additionalProperties != null -> when (val aProps = additionalProperties!!) { - is AdditionalProperties.PSchema -> toMap(context, this, aProps) + is AdditionalProperties.PSchema -> this.toMap(context, aProps) is Allowed -> toRawJson(aProps) } else -> toRawJson(Allowed(true)) @@ -236,59 +237,55 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { fun toPropertyContext( key: String, propSchema: ReferenceOr, - parentSchema: Schema, original: NamingContext ): NamingContext = when (propSchema) { is ReferenceOr.Reference -> Named(propSchema.namedSchema().first) - is ReferenceOr.Value -> NamingContext.Inline(key, original) + is ReferenceOr.Value -> NamingContext.Nested(key, original) } private fun Schema.singleDefaultOrNull(): String? = (default as? ExampleValue.Single)?.value - fun toObject( - context: NamingContext, - schema: Schema, - required: List, - properties: Map> - ): Model = + fun Schema.toObject(context: NamingContext, properties: Map>): Model = Model.Object( - schema, + this, context, - schema.description, + description, properties.map { (name, ref) -> - val pContext = toPropertyContext(name, ref, schema, context) + val pContext = toPropertyContext(name, ref, context) val pSchema = ref.get() val model = pSchema.toModel(pContext) - // TODO Property interceptor Property( pSchema, name, pContext.name, model, - schema.required?.contains(name) == true, - pSchema.nullable ?: schema.required?.contains(name)?.not() ?: true, + required?.contains(name) == true, + pSchema.nullable ?: required?.contains(name)?.not() ?: true, pSchema.description ) }, properties.mapNotNull { (name, ref) -> ref.valueOrNull()?.let { pSchema -> - when (val model = pSchema.toModel(toPropertyContext(name, ref, schema, context))) { + when (val model = pSchema.toModel(toPropertyContext(name, ref, context))) { is Collection -> model.schema.items ?.valueOrNull() - ?.toModel(toPropertyContext(name, model.schema.items!!, schema, context)) + ?.toModel(toPropertyContext(name, model.schema.items!!, context)) else -> model } } } ) - fun toMap( - context: NamingContext, - schema: Schema, - additionalSchema: AdditionalProperties.PSchema - ): Model = Collection.Map(schema, additionalSchema.value.get().toModel(context)) + fun Schema.toOpenEnum(context: NamingContext, values: List): Enum.Open { + require(values.isNotEmpty()) { "OpenEnum requires at least 1 possible value" } + val default = singleDefaultOrNull() + return Enum.Open(this, context, values, default) + } + + fun Schema.toMap(context: NamingContext, additionalSchema: AdditionalProperties.PSchema): Model = + Collection.Map(this, additionalSchema.value.get().toModel(context)) fun toRawJson(allowed: Allowed): Model = if (allowed.value) Model.FreeFormJson @@ -297,29 +294,28 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { "Illegal State: No additional properties allowed on empty object." ) - fun toPrimitive(context: NamingContext, schema: Schema, basic: Type.Basic): Model = + fun Schema.toPrimitive(context: NamingContext, basic: Type.Basic): Model = when (basic) { Type.Basic.Object -> toRawJson(Allowed(true)) - Type.Basic.Boolean -> Primitive.Boolean(schema, schema.default?.toString()?.toBoolean()) - Type.Basic.Integer -> Primitive.Int(schema, schema.default?.toString()?.toIntOrNull()) - Type.Basic.Number -> Primitive.Double(schema, schema.default?.toString()?.toDoubleOrNull()) - Type.Basic.Array -> collection(schema, context) + Type.Basic.Boolean -> Primitive.Boolean(this, default?.toString()?.toBoolean()) + Type.Basic.Integer -> Primitive.Int(this, default?.toString()?.toIntOrNull()) + Type.Basic.Number -> Primitive.Double(this, default?.toString()?.toDoubleOrNull()) + Type.Basic.Array -> this.collection(context) Type.Basic.String -> - if (schema.format == "binary") Model.Binary - else Primitive.String(schema, schema.default?.toString()) + if (format == "binary") Model.Binary else Primitive.String(this, default?.toString()) Type.Basic.Null -> TODO("Schema.Type.Basic.Null") } - private fun collection(schema: Schema, context: NamingContext): Collection { - val items = requireNotNull(schema.items) { "Array type requires items to be defined." } + private fun Schema.collection(context: NamingContext): Collection { + val items = requireNotNull(items) { "Array type requires items to be defined." } val inner = when (items) { is ReferenceOr.Reference -> - schema.items!!.get().toModel(Named(items.ref.drop(schemaRef.length))) + this.items!!.get().toModel(Named(items.ref.drop(schemaRef.length))) is ReferenceOr.Value -> items.value.toModel(context) } val default = - when (val example = schema.default) { + when (val example = default) { is ExampleValue.Multiple -> example.values is ExampleValue.Single -> when (val value = example.value) { @@ -328,57 +324,45 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { } null -> null } - return if (schema.uniqueItems == true) Collection.Set(schema, inner, default) - else Collection.List(schema, inner, default) + return if (uniqueItems == true) Collection.Set(this, inner, default) + else Collection.List(this, inner, default) } - fun type(context: NamingContext, schema: Schema, type: Type): Model = + fun Schema.type(context: NamingContext, type: Type): Model = when (type) { is Type.Array -> when { type.types.size == 1 -> - type(context, schema.copy(type = type.types.single()), type.types.single()) + copy(type = type.types.single()).type(context, type.types.single()) else -> - TypeArray( - schema, + Model.Union( + this, context, type.types.sorted().map { - Model.Union.UnionEntry(context, Schema(type = it).toModel(context)) + Model.Union.Entry(context, Schema(type = it).toModel(context)) }, - emptyList() + emptyList(), + null ) } - is Type.Basic -> toPrimitive(context, schema, type) + is Type.Basic -> toPrimitive(context, type) } - fun toEnum(context: NamingContext, schema: Schema, type: Type, enums: List): Model { + fun Schema.toEnum(context: NamingContext, enums: List): Model.Enum.Closed { require(enums.isNotEmpty()) { "Enum requires at least 1 possible value" } /* To resolve the inner type, we erase the enum values. * Since the schema is still on the same level - we keep the topLevelName */ - val inner = schema.copy(enum = null).toModel(context) - val default = schema.singleDefaultOrNull() - return Model.Enum(schema, context, inner, enums, default) + val inner = copy(enum = null).toModel(context) + val default = singleDefaultOrNull() + return Enum.Closed(this, context, inner, enums, default) } - // Support AnyOf = null | A, should become A? - fun toAnyOf( - context: NamingContext, - schema: Schema, - anyOf: List>, - ): Model = toUnion(context, schema, anyOf, Model.Union::AnyOf) - - fun toOneOf( - context: NamingContext, - schema: Schema, - oneOf: List>, - ): Model = toUnion(context, schema, oneOf, Model.Union::OneOf) - fun toUnionCaseContext(context: NamingContext, caseSchema: ReferenceOr): NamingContext = when (caseSchema) { is ReferenceOr.Reference -> Named(caseSchema.ref.drop(schemaRef.length)) is ReferenceOr.Value -> when (context) { - is NamingContext.Inline -> + is NamingContext.Nested -> when { caseSchema.value.type != null && caseSchema.value.type is Type.Array -> throw IllegalStateException("Cannot generate name for $caseSchema, ctx: $context.") @@ -396,7 +380,6 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { */ is Named -> generateName(context, caseSchema.value) is NamingContext.RouteParam -> context - is NamingContext.Ref -> context } } @@ -404,23 +387,29 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { * TODO This needs a rock solid implementation, and should be super easy to override from Gradle. * This is what we use to generate names for inline schemas, most of the time we can get away with * other information, but not always. + * + * This typically occurs when a top-level oneOf, or anyOf has inline schemas. Since union cases + * don't have names, we need to generate a name for an inline schema. Generically we cannot do + * better than First, Second, etc. */ private fun generateName(context: Named, schema: Schema): NamingContext = - when (val type = schema.type) { + when (schema.type) { Type.Basic.Array -> { val inner = requireNotNull(schema.items) { "Array type requires items to be defined." } inner.get().type - TODO() + TODO("Name generation for Type Arrays not yet supported") } Type.Basic.Object -> { - // TODO OpenAI specific - NamingContext.Inline( + // OpenAI specific: + // When there is an `event` property, + // rely on the single enum event name as generated name. + NamingContext.Nested( schema.properties ?.firstNotNullOfOrNull { (key, value) -> if (key == "event") value.get().enum else null } ?.singleOrNull() - ?: TODO(), + ?: TODO("Name Generated for inline objects of unions not yet supported."), context ) } @@ -433,7 +422,7 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { when (val enum = schema.enum) { null -> context.copy(name = "CaseString") else -> - NamingContext.Inline( + NamingContext.Nested( name = enum.joinToString(prefix = "", separator = "Or") { it.replaceFirstChar(Char::uppercaseChar) @@ -444,13 +433,10 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { null -> TODO() } - private fun toUnion( + private fun Schema.toUnion( context: NamingContext, - schema: Schema, - subtypes: List>, - transform: - (Schema, NamingContext, List, inline: List, String?) -> A - ): A { + subtypes: List> + ): Model.Union { val inline = subtypes.mapNotNull { ref -> when (val model = ref.valueOrNull()?.toModel(toUnionCaseContext(context, ref))) { @@ -461,12 +447,12 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { else -> model } } - return transform( - schema, + return Model.Union( + this, context, subtypes.map { ref -> val caseContext = toUnionCaseContext(context, ref) - Model.Union.UnionEntry(caseContext, ref.get().toModel(caseContext)) + Model.Union.Entry(caseContext, ref.get().toModel(caseContext)) }, inline, // TODO We need to check the parent for default? @@ -508,6 +494,7 @@ private class OpenAPITransformer(private val openAPI: OpenAPI) { fun ctx(name: String): NamingContext = when (val s = mediaType.schema) { is ReferenceOr.Reference -> Named(s.namedSchema().first) + // TODO inline Multipart should be flattened into function is ReferenceOr.Value -> NamingContext.RouteParam(name, operation.operationId, "Request") null -> diff --git a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/generation/NamingStrategy.kt b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/generation/NamingStrategy.kt index 0160296..d56bc81 100644 --- a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/generation/NamingStrategy.kt +++ b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/generation/NamingStrategy.kt @@ -94,13 +94,12 @@ object DefaultNamingStrategy : NamingStrategy { override fun toEnumClassName(context: NamingContext): String = when (context) { - is NamingContext.Inline -> context.name.toPascalCase() - is NamingContext.Ref -> context.outer.name.toPascalCase() + is NamingContext.Nested -> context.name.toPascalCase() is NamingContext.Named -> context.name.toPascalCase() is NamingContext.RouteParam -> { requireNotNull(context.operationId) { "Need operationId to generate enum name" } - // $MyObject$Param$Request, this allows for multiple custom objects in a single operation - "${context.operationId.toPascalCase()}${context.name.toPascalCase()}${context.postfix.toPascalCase()}" + // $MyObject$Param, this allows for multiple custom objects in a single operation + "${context.operationId.toPascalCase()}${context.name.toPascalCase()}" } }.dropArraySyntax() @@ -128,8 +127,7 @@ object DefaultNamingStrategy : NamingStrategy { override fun toUnionClassName(model: Model.Union): String { val context = model.context return when { - model.isOpenEnumeration() -> toEnumClassName(context) - context is NamingContext.Inline -> + context is NamingContext.Nested -> "${context.outer.name.toPascalCase()}${context.name.toPascalCase()}" else -> context.name.toPascalCase() } diff --git a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/generation/default.kt b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/generation/default.kt index cbe2575..b2f4561 100644 --- a/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/generation/default.kt +++ b/generation/src/commonMain/kotlin/io/github/nomisrev/openapi/generation/default.kt @@ -23,23 +23,7 @@ fun Model.default(naming: NamingStrategy): String? = "${naming.toEnumClassName(context)}.${naming.toEnumValueName(it)}" } is Model.Primitive -> default() - is Model.Union.AnyOf -> - when { - default == null -> null - isOpenEnumeration() -> { - val case = schemas.firstNotNullOf { it.model as? Model.Enum } - val defaultEnum = - case.values.find { it == default }?.let { naming.toEnumClassName(case.context) } - defaultEnum ?: "Custom(\"${default}\")" - } - else -> - schemas - .find { it.model is Model.Primitive.String } - ?.let { case -> - "${naming.toUnionClassName(this)}.${naming.toUnionCaseName(case.model)}(\"${default}\")" - } - } - is Model.Union.OneOf -> + is Model.Union -> schemas .find { it.model is Model.Primitive.String } ?.takeIf { default != null } diff --git a/generation/src/jvmMain/kotlin/io/github/nomisrev/openapi/Models.kt b/generation/src/jvmMain/kotlin/io/github/nomisrev/openapi/Models.kt index d57f130..e84f4a2 100644 --- a/generation/src/jvmMain/kotlin/io/github/nomisrev/openapi/Models.kt +++ b/generation/src/jvmMain/kotlin/io/github/nomisrev/openapi/Models.kt @@ -25,6 +25,7 @@ import io.github.nomisrev.openapi.Model.Collection import io.github.nomisrev.openapi.generation.DefaultNamingStrategy import io.github.nomisrev.openapi.generation.NamingStrategy import io.github.nomisrev.openapi.generation.default +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.Required import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -58,12 +59,10 @@ tailrec fun Model.toTypeSpec(naming: NamingStrategy): TypeSpec? = is Collection -> value.toTypeSpec(naming) is Model.Enum -> toTypeSpec(naming) is Model.Object -> toTypeSpec(naming) - is Model.Union -> { - val isOpenEnumeration = isOpenEnumeration() - if (isOpenEnumeration) toOpenEnumTypeSpec(naming) else toTypeSpec(naming) - } + is Model.Union -> toTypeSpec(naming) } +@OptIn(ExperimentalSerializationApi::class) fun Model.Union.toTypeSpec(naming: NamingStrategy): TypeSpec = TypeSpec.interfaceBuilder(naming.toUnionClassName(this)) .addModifiers(KModifier.SEALED) @@ -181,30 +180,6 @@ fun Model.Union.toTypeSpec(naming: NamingStrategy): TypeSpec = ) .build() -fun Model.Enum.toTypeSpec(naming: NamingStrategy): TypeSpec { - val rawToName = values.map { rawName -> Pair(rawName, naming.toEnumValueName(rawName)) } - val isSimple = rawToName.all { (rawName, valueName) -> rawName == valueName } - val enumName = naming.toEnumClassName(context) - return TypeSpec.enumBuilder(enumName) - .apply { - if (!isSimple) - primaryConstructor(FunSpec.constructorBuilder().addParameter("value", STRING).build()) - rawToName.forEach { (rawName, valueName) -> - if (isSimple) addEnumConstant(rawName) - else - addEnumConstant( - valueName, - TypeSpec.anonymousClassBuilder() - .addAnnotation(serialName(rawName)) - .addSuperclassConstructorParameter("\"$rawName\"") - .build() - ) - } - } - .addAnnotation(annotationSpec()) - .build() -} - private inline fun annotationSpec(): AnnotationSpec = AnnotationSpec.builder(A::class).build() @@ -268,26 +243,26 @@ fun Model.toTypeName( `package`: String = "io.github.nomisrev.openapi" ): TypeName = when (this) { - Model.Binary -> ClassName(`package`, "UploadFile") + is Model.Primitive.Boolean -> BOOLEAN + is Model.Primitive.Double -> DOUBLE + is Model.Primitive.Int -> INT + is Model.Primitive.String -> STRING + Model.Primitive.Unit -> UNIT is Collection.List -> LIST.parameterizedBy(value.toTypeName(naming, `package`)) is Collection.Set -> SET.parameterizedBy(value.toTypeName(naming, `package`)) is Collection.Map -> MAP.parameterizedBy(STRING, value.toTypeName(naming, `package`)) + Model.Binary -> ClassName(`package`, "UploadFile") + Model.FreeFormJson -> ClassName("kotlinx.serialization.json", "JsonElement") is Model.Enum -> when (context) { is NamingContext.Named -> ClassName(`package`, naming.toEnumClassName(context)) else -> ClassName.bestGuess(naming.toEnumClassName(context)) } - Model.FreeFormJson -> ClassName("kotlinx.serialization.json", "JsonElement") is Model.Object -> when (context) { is NamingContext.Named -> ClassName(`package`, naming.toObjectClassName(context)) else -> ClassName.bestGuess(naming.toObjectClassName(context)) } - is Model.Primitive.Boolean -> BOOLEAN - is Model.Primitive.Double -> DOUBLE - is Model.Primitive.Int -> INT - is Model.Primitive.String -> STRING - Model.Primitive.Unit -> UNIT is Model.Union -> when (context) { is NamingContext.Named -> ClassName(`package`, naming.toUnionClassName(this)) @@ -295,9 +270,38 @@ fun Model.toTypeName( } } -fun Model.Union.toOpenEnumTypeSpec(naming: NamingStrategy): TypeSpec { - val enum = schemas.firstNotNullOf { it.model as? Model.Enum } - val rawToName = enum.values.map { rawName -> Pair(rawName, naming.toEnumValueName(rawName)) } +fun Model.Enum.toTypeSpec(naming: NamingStrategy): TypeSpec = + when (this) { + is Model.Enum.Closed -> toTypeSpec(naming) + is Model.Enum.Open -> toTypeSpec(naming) + } + +fun Model.Enum.Closed.toTypeSpec(naming: NamingStrategy): TypeSpec { + val rawToName = values.map { rawName -> Pair(rawName, naming.toEnumValueName(rawName)) } + val isSimple = rawToName.all { (rawName, valueName) -> rawName == valueName } + val enumName = naming.toEnumClassName(context) + return TypeSpec.enumBuilder(enumName) + .apply { + if (!isSimple) + primaryConstructor(FunSpec.constructorBuilder().addParameter("value", STRING).build()) + rawToName.forEach { (rawName, valueName) -> + if (isSimple) addEnumConstant(rawName) + else + addEnumConstant( + valueName, + TypeSpec.anonymousClassBuilder() + .addAnnotation(serialName(rawName)) + .addSuperclassConstructorParameter("\"$rawName\"") + .build() + ) + } + } + .addAnnotation(annotationSpec()) + .build() +} + +fun Model.Enum.Open.toTypeSpec(naming: NamingStrategy): TypeSpec { + val rawToName = values.map { rawName -> Pair(rawName, naming.toEnumValueName(rawName)) } val enumName = naming.toEnumClassName(context) return TypeSpec.interfaceBuilder(enumName) .addModifiers(KModifier.SEALED)