diff --git a/libraries/apollo-annotations/api/apollo-annotations.api b/libraries/apollo-annotations/api/apollo-annotations.api index 3885423aa6f..0c8f44e5e2a 100644 --- a/libraries/apollo-annotations/api/apollo-annotations.api +++ b/libraries/apollo-annotations/api/apollo-annotations.api @@ -38,6 +38,9 @@ public abstract interface annotation class com/apollographql/apollo/annotations/ public abstract interface annotation class com/apollographql/apollo/annotations/ApolloInternal : java/lang/annotation/Annotation { } +public abstract interface annotation class com/apollographql/apollo/annotations/ApolloPrivateEnumConstructor : java/lang/annotation/Annotation { +} + public abstract interface annotation class com/apollographql/apollo/annotations/ApolloRequiresOptIn : java/lang/annotation/Annotation { } diff --git a/libraries/apollo-annotations/api/apollo-annotations.klib.api b/libraries/apollo-annotations/api/apollo-annotations.klib.api index df4e0bb814e..dabb4822633 100644 --- a/libraries/apollo-annotations/api/apollo-annotations.klib.api +++ b/libraries/apollo-annotations/api/apollo-annotations.klib.api @@ -57,6 +57,10 @@ open annotation class com.apollographql.apollo.annotations/ApolloInternal : kotl constructor () // com.apollographql.apollo.annotations/ApolloInternal.|(){}[0] } +open annotation class com.apollographql.apollo.annotations/ApolloPrivateEnumConstructor : kotlin/Annotation { // com.apollographql.apollo.annotations/ApolloPrivateEnumConstructor|null[0] + constructor () // com.apollographql.apollo.annotations/ApolloPrivateEnumConstructor.|(){}[0] +} + open annotation class com.apollographql.apollo.annotations/ApolloRequiresOptIn : kotlin/Annotation { // com.apollographql.apollo.annotations/ApolloRequiresOptIn|null[0] constructor () // com.apollographql.apollo.annotations/ApolloRequiresOptIn.|(){}[0] } diff --git a/libraries/apollo-annotations/src/commonMain/kotlin/com/apollographql/apollo/annotations/ApolloPrivateEnumConstructor.kt b/libraries/apollo-annotations/src/commonMain/kotlin/com/apollographql/apollo/annotations/ApolloPrivateEnumConstructor.kt new file mode 100644 index 00000000000..ac8aa0acc07 --- /dev/null +++ b/libraries/apollo-annotations/src/commonMain/kotlin/com/apollographql/apollo/annotations/ApolloPrivateEnumConstructor.kt @@ -0,0 +1,13 @@ +package com.apollographql.apollo.annotations + +/** + * Kotlin has no static factory functions like Java so we rely on an OptIn marker to prevent public usage. + * See https://youtrack.jetbrains.com/issue/KT-19400/Allow-access-to-private-members-between-nested-classes-of-the-same-class + */ +@RequiresOptIn( + level = RequiresOptIn.Level.ERROR, + message = "The `__Unknown` constructor is public for technical reasons only. Use `${'$'}YourEnum.safeValueOf(String)` instead." +) +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.CONSTRUCTOR) +annotation class ApolloPrivateEnumConstructor \ No newline at end of file diff --git a/libraries/apollo-compiler/api/apollo-compiler.api b/libraries/apollo-compiler/api/apollo-compiler.api index 2e5cabba11f..8ae2a5f561d 100644 --- a/libraries/apollo-compiler/api/apollo-compiler.api +++ b/libraries/apollo-compiler/api/apollo-compiler.api @@ -103,12 +103,13 @@ public final class com/apollographql/apollo/compiler/CodegenMetadata$Companion { public final class com/apollographql/apollo/compiler/CodegenOptions : com/apollographql/apollo/compiler/OperationsCodegenOptions, com/apollographql/apollo/compiler/SchemaCodegenOptions { public static final field Companion Lcom/apollographql/apollo/compiler/CodegenOptions$Companion; - public fun (Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Lcom/apollographql/apollo/compiler/JavaNullable;Ljava/lang/Boolean;Ljava/lang/Boolean;)V + public fun (Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Lcom/apollographql/apollo/compiler/JavaNullable;Ljava/lang/Boolean;Ljava/lang/Boolean;)V public fun getAddDefaultArgumentForInputObjects ()Ljava/lang/Boolean; public fun getAddJvmOverloads ()Ljava/lang/Boolean; public fun getAddUnknownForEnums ()Ljava/lang/Boolean; public fun getClassesForEnumsMatching ()Ljava/util/List; public fun getDecapitalizeFields ()Ljava/lang/Boolean; + public fun getGenerateApolloEnums ()Ljava/lang/Boolean; public fun getGenerateAsInternal ()Ljava/lang/Boolean; public fun getGenerateFilterNotNull ()Ljava/lang/Boolean; public fun getGenerateFragmentImplementations ()Ljava/lang/Boolean; @@ -352,6 +353,7 @@ public abstract interface class com/apollographql/apollo/compiler/KotlinCodegenO public abstract fun getAddDefaultArgumentForInputObjects ()Ljava/lang/Boolean; public abstract fun getAddJvmOverloads ()Ljava/lang/Boolean; public abstract fun getAddUnknownForEnums ()Ljava/lang/Boolean; + public abstract fun getGenerateApolloEnums ()Ljava/lang/Boolean; public abstract fun getGenerateAsInternal ()Ljava/lang/Boolean; public abstract fun getGenerateFilterNotNull ()Ljava/lang/Boolean; public abstract fun getGenerateInputBuilders ()Ljava/lang/Boolean; @@ -393,8 +395,8 @@ public final class com/apollographql/apollo/compiler/OptionsKt { public static final field MODELS_OPERATION_BASED Ljava/lang/String; public static final field MODELS_OPERATION_BASED_WITH_INTERFACES Ljava/lang/String; public static final field MODELS_RESPONSE_BASED Ljava/lang/String; - public static final fun buildCodegenOptions (Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Lcom/apollographql/apollo/compiler/JavaNullable;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/apollo/compiler/CodegenOptions; - public static synthetic fun buildCodegenOptions$default (Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Lcom/apollographql/apollo/compiler/JavaNullable;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/apollographql/apollo/compiler/CodegenOptions; + public static final fun buildCodegenOptions (Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Lcom/apollographql/apollo/compiler/JavaNullable;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;)Lcom/apollographql/apollo/compiler/CodegenOptions; + public static synthetic fun buildCodegenOptions$default (Lcom/apollographql/apollo/compiler/TargetLanguage;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Lcom/apollographql/apollo/compiler/JavaNullable;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/apollographql/apollo/compiler/CodegenOptions; public static final fun buildIrOptions (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;)Lcom/apollographql/apollo/compiler/IrOptions; public static synthetic fun buildIrOptions$default (Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;ILjava/lang/Object;)Lcom/apollographql/apollo/compiler/IrOptions; public static final fun validate (Lcom/apollographql/apollo/compiler/CodegenOptions;)V diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Options.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Options.kt index 04eb2e3aca5..ad1fde7e9b9 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Options.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/Options.kt @@ -448,6 +448,14 @@ interface KotlinCodegenOpt { */ val addUnknownForEnums: Boolean? + /** + * Whether to generate enums as ApolloEnum. + * + * Experimental, see https://github.com/apollographql/apollo-kotlin/issues/6243. + */ + @ApolloExperimental + val generateApolloEnums: Boolean? + /** * Whether to add default arguments for input objects. */ @@ -521,6 +529,7 @@ class CodegenOptions( override val generateSchema: Boolean?, override val generatedSchemaName: String?, override val sealedClassesForEnumsMatching: List?, + override val generateApolloEnums: Boolean?, override val generateAsInternal: Boolean?, override val addUnknownForEnums: Boolean?, override val addDefaultArgumentForInputObjects: Boolean?, @@ -548,6 +557,7 @@ fun buildCodegenOptions( sealedClassesForEnumsMatching: List? = null, generateAsInternal: Boolean? = null, addUnknownForEnums: Boolean? = null, + generateApolloEnums: Boolean? = null, addDefaultArgumentForInputObjects: Boolean? = null, generateFilterNotNull: Boolean? = null, generateInputBuilders: Boolean? = null, @@ -573,6 +583,7 @@ fun buildCodegenOptions( sealedClassesForEnumsMatching = sealedClassesForEnumsMatching, generateAsInternal = generateAsInternal, addUnknownForEnums = addUnknownForEnums, + generateApolloEnums = generateApolloEnums, addDefaultArgumentForInputObjects = addDefaultArgumentForInputObjects, generateFilterNotNull = generateFilterNotNull, generateInputBuilders = generateInputBuilders, @@ -703,6 +714,7 @@ internal val defaultAddUnkownForEnums = true internal val defaultAddDefaultArgumentForInputObjects = true internal val defaultCodegenModels = "operationBased" internal val defaultTargetLanguage = TargetLanguage.KOTLIN_1_9 +internal val defaultGenerateApolloEnums = false internal fun defaultTargetLanguage(targetLanguage: TargetLanguage?, upstreamCodegenMetadata: List): TargetLanguage { val upstreamTargetLanguage = upstreamCodegenMetadata.map { it.targetLanguage }.distinct().run { diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/Identifiers.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/Identifiers.kt index 5ecc1246724..1c5e2dffec0 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/Identifiers.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/Identifiers.kt @@ -41,9 +41,7 @@ internal object Identifier { const val copy = "copy" const val Data = "Data" - const val cacheKeyForObject = "cacheKeyForObject" const val field = "field" - const val __map = "__map" const val __path = "__path" const val __fields = "__fields" @@ -63,11 +61,17 @@ internal object Identifier { const val knownValues = "knownValues" const val knownEntries = "knownEntries" - // extra underscores at the end to prevent potential name clashes + /** + * UNKNOWN__ should probably have been __UNKNOWN because GraphQL reserves the leading __ but it's too late now. + * + * All in all it's not too bad because typing 'U', 'N', ... is usually more intuitive and in the very unlikely event that + * there is a name clash, it can always be resolved with `@targetName` + */ const val UNKNOWN__ = "UNKNOWN__" + const val __Unknown = "__Unknown" + const val __Known = "__Known" const val rawValue = "rawValue" const val types = "types" - const val testResolver = "testResolver" const val block = "block" const val resolver = "resolver" const val newBuilder = "newBuilder" diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/KotlinCodegen.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/KotlinCodegen.kt index 1fc654fe945..675c7f7b65b 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/KotlinCodegen.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/KotlinCodegen.kt @@ -27,8 +27,10 @@ import com.apollographql.apollo.compiler.codegen.kotlin.operations.OperationResp import com.apollographql.apollo.compiler.codegen.kotlin.operations.OperationSelectionsBuilder import com.apollographql.apollo.compiler.codegen.kotlin.operations.OperationVariablesAdapterBuilder import com.apollographql.apollo.compiler.codegen.kotlin.schema.CustomScalarAdaptersBuilder +import com.apollographql.apollo.compiler.codegen.kotlin.schema.EnumAsApolloEnumBuilder +import com.apollographql.apollo.compiler.codegen.kotlin.schema.EnumAsApolloEnumSupportBuilder import com.apollographql.apollo.compiler.codegen.kotlin.schema.EnumAsEnumBuilder -import com.apollographql.apollo.compiler.codegen.kotlin.schema.EnumAsSealedBuilder +import com.apollographql.apollo.compiler.codegen.kotlin.schema.EnumAsSealedInterfaceBuilder import com.apollographql.apollo.compiler.codegen.kotlin.schema.EnumResponseAdapterBuilder import com.apollographql.apollo.compiler.codegen.kotlin.schema.InlineClassBuilder import com.apollographql.apollo.compiler.codegen.kotlin.schema.InputObjectAdapterBuilder @@ -43,6 +45,7 @@ import com.apollographql.apollo.compiler.codegen.kotlin.schema.asTargetClassName import com.apollographql.apollo.compiler.defaultAddDefaultArgumentForInputObjects import com.apollographql.apollo.compiler.defaultAddJvmOverloads import com.apollographql.apollo.compiler.defaultAddUnkownForEnums +import com.apollographql.apollo.compiler.defaultGenerateApolloEnums import com.apollographql.apollo.compiler.defaultGenerateAsInternal import com.apollographql.apollo.compiler.defaultGenerateFilterNotNull import com.apollographql.apollo.compiler.defaultGenerateFragmentImplementations @@ -161,6 +164,7 @@ internal object KotlinCodegen { val jsExport = codegenOptions.jsExport ?: defaultJsExport val requiresOptInAnnotation = codegenOptions.requiresOptInAnnotation ?: defaultRequiresOptInAnnotation val sealedClassesForEnumsMatching = codegenOptions.sealedClassesForEnumsMatching ?: defaultSealedClassesForEnumsMatching + val generateApolloEnums = codegenOptions.generateApolloEnums ?: defaultGenerateApolloEnums val addUnknownForEnums = codegenOptions.addUnknownForEnums ?: defaultAddUnkownForEnums val addDefaultArgumentForInputObjects = codegenOptions.addDefaultArgumentForInputObjects ?: defaultAddDefaultArgumentForInputObjects @@ -188,9 +192,14 @@ internal object KotlinCodegen { } builders.add(ScalarBuilder(context, irScalar, inlineClassBuilder?.className)) } + if (generateApolloEnums) { + builders.add(EnumAsApolloEnumSupportBuilder(context)) + } irSchema.irEnums.forEach { irEnum -> - if (sealedClassesForEnumsMatching.any { Regex(it).matches(irEnum.name) }) { - builders.add(EnumAsSealedBuilder(context, irEnum)) + if(generateApolloEnums) { + builders.add(EnumAsApolloEnumBuilder(context, irEnum)) + } else if (sealedClassesForEnumsMatching.any { Regex(it).matches(irEnum.name) }) { + builders.add(EnumAsSealedInterfaceBuilder(context, irEnum)) } else { builders.add(EnumAsEnumBuilder(context, irEnum, addUnknownForEnums)) } diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/KotlinSymbols.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/KotlinSymbols.kt index ba5e13cfcda..0ab0bc4d8dc 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/KotlinSymbols.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/KotlinSymbols.kt @@ -112,6 +112,7 @@ internal object KotlinSymbols { val ApolloAdaptableWith = ClassName(ClassNames.apolloAnnotationsPackageName, "ApolloAdaptableWith") val ApolloExperimental = ClassName(ClassNames.apolloAnnotationsPackageName, "ApolloExperimental") + val ApolloPrivateEnumConstructor = ClassName(ClassNames.apolloAnnotationsPackageName, "ApolloPrivateEnumConstructor") val JsExport = ClassName("kotlin.js", "JsExport") val ExecutableDefinition = ClassNames.ExecutableDefinition.toKotlinPoetClassName() diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsApolloEnumBuilder.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsApolloEnumBuilder.kt new file mode 100644 index 00000000000..d48a18783a8 --- /dev/null +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsApolloEnumBuilder.kt @@ -0,0 +1,228 @@ +package com.apollographql.apollo.compiler.codegen.kotlin.schema + +import com.apollographql.apollo.compiler.codegen.Identifier +import com.apollographql.apollo.compiler.codegen.Identifier.__Known +import com.apollographql.apollo.compiler.codegen.Identifier.__Unknown +import com.apollographql.apollo.compiler.codegen.Identifier.rawValue +import com.apollographql.apollo.compiler.codegen.Identifier.safeValueOf +import com.apollographql.apollo.compiler.codegen.kotlin.CgFile +import com.apollographql.apollo.compiler.codegen.kotlin.CgFileBuilder +import com.apollographql.apollo.compiler.codegen.kotlin.KotlinSchemaContext +import com.apollographql.apollo.compiler.codegen.kotlin.KotlinSymbols +import com.apollographql.apollo.compiler.codegen.kotlin.helpers.addSuppressions +import com.apollographql.apollo.compiler.codegen.kotlin.helpers.maybeAddDeprecation +import com.apollographql.apollo.compiler.codegen.kotlin.helpers.maybeAddDescription +import com.apollographql.apollo.compiler.codegen.kotlin.helpers.maybeAddOptIn +import com.apollographql.apollo.compiler.codegen.kotlin.helpers.maybeAddRequiresOptIn +import com.apollographql.apollo.compiler.codegen.kotlin.schema.util.typePropertySpec +import com.apollographql.apollo.compiler.codegen.typePackageName +import com.apollographql.apollo.compiler.internal.escapeKotlinReservedWordInSealedClass +import com.apollographql.apollo.compiler.ir.IrEnum +import com.squareup.kotlinpoet.AnnotationSpec +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.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.buildCodeBlock +import com.squareup.kotlinpoet.joinToCode +import com.squareup.kotlinpoet.withIndent + +internal class EnumAsApolloEnumBuilder( + private val context: KotlinSchemaContext, + private val enum: IrEnum, +) : CgFileBuilder { + private val layout = context.layout + private val packageName = layout.typePackageName() + private val simpleName = layout.schemaTypeName(enum.name) + + private val selfClassName: ClassName + get() = context.resolver.resolveSchemaType(enum.name) + + override fun prepare() { + context.resolver.registerSchemaType( + enum.name, + ClassName( + packageName, + simpleName + ) + ) + } + + override fun build(): CgFile { + return CgFile( + packageName = packageName, + fileName = simpleName, + typeSpecs = listOf(enum.toSealedInterfaceTypeSpec()) + ) + } + + private fun IrEnum.toSealedInterfaceTypeSpec(): TypeSpec { + return TypeSpec.interfaceBuilder(simpleName) + .maybeAddDescription(description) + .addSuperinterface( + ClassName(packageName.experimental(), "ApolloEnum") + .parameterizedBy( + selfClassName, + ClassName(packageName, simpleName, __Known) + ) + ) + .addModifiers(KModifier.SEALED) + .addProperty( + PropertySpec.builder(rawValue, KotlinSymbols.String) + .addModifiers(KModifier.OVERRIDE) + .build() + ) + .addType(companionTypeSpec()) + .addTypes(values.map { value -> + value.toObjectTypeSpec() + }) + .addType(knownValueTypeSpec()) + .addType(unknownValueTypeSpec()) + .build() + } + + private fun IrEnum.companionTypeSpec(): TypeSpec { + return TypeSpec.companionObjectBuilder() + .addProperty(typePropertySpec()) + .addFunction(safeValueOfFunSpec()) + .addFunction(knownValuesFunSpec()) + .build() + } + + private fun IrEnum.Value.toObjectTypeSpec(): TypeSpec { + return TypeSpec.objectBuilder(targetName.escapeKotlinReservedWordInSealedClass()) + .maybeAddDeprecation(deprecationReason) + .maybeAddDescription(description) + .maybeAddRequiresOptIn(context.resolver, optInFeature) + .addSuperinterface(selfClassName.nestedClass(__Known)) + .addProperty( + PropertySpec.builder("rawValue", KotlinSymbols.String) + .addModifiers(KModifier.OVERRIDE) + .initializer("%S", name) + .build() + ) + .build() + } + + private fun knownValueTypeSpec(): TypeSpec { + return TypeSpec.interfaceBuilder(__Known) + .addKdoc("An enum value that is known at build time.") + .addSuperinterface(selfClassName) + .addSuperinterface(ClassName(packageName.experimental(), "KnownEnum").parameterizedBy(selfClassName)) + .addProperty( + PropertySpec.builder(rawValue, KotlinSymbols.String) + .addModifiers(KModifier.OVERRIDE) + .build() + ) + .addModifiers(KModifier.SEALED) + .addAnnotation(AnnotationSpec.builder(KotlinSymbols.Suppress).addMember("%S", "ClassName").build()) + .build() + } + + private fun unknownValueTypeSpec(): TypeSpec { + return TypeSpec.classBuilder(__Unknown) + .addKdoc("An enum value that isn't known at build time.") + .addSuperinterface(selfClassName) + .primaryConstructor( + FunSpec.constructorBuilder() + .addAnnotation(AnnotationSpec.builder(KotlinSymbols.ApolloPrivateEnumConstructor).build()) + .addParameter(rawValue, KotlinSymbols.String) + .build() + ) + .addProperty( + PropertySpec.builder(rawValue, KotlinSymbols.String) + .addModifiers(KModifier.OVERRIDE) + .initializer(rawValue) + .build() + ) + .addAnnotation(AnnotationSpec.builder(KotlinSymbols.Suppress).addMember("%S", "ClassName").build()) + .addFunction( + FunSpec.builder("equals") + .addModifiers(KModifier.OVERRIDE) + .addParameter(ParameterSpec("other", KotlinSymbols.Any.copy(nullable = true))) + .returns(KotlinSymbols.Boolean) + .addCode("if (other !is $__Unknown) return false\n") + .addCode("return this.$rawValue == other.rawValue") + .build() + ) + .addFunction( + FunSpec.builder("hashCode") + .addModifiers(KModifier.OVERRIDE) + .returns(KotlinSymbols.Int) + .addCode("return this.$rawValue.hashCode()") + .build() + ) + .addFunction( + FunSpec.builder("toString") + .addModifiers(KModifier.OVERRIDE) + .returns(KotlinSymbols.String) + .addCode("return \"$__Unknown(${'$'}$rawValue)\"") + .build() + ) + .build() + } + + private fun IrEnum.safeValueOfFunSpec(): FunSpec { + return FunSpec.builder(safeValueOf) + .addKdoc( + """ + Returns an instance of [%T] representing [$rawValue]. + + The returned value may be an instance of [$__Unknown] if the enum value is not known at build time. + You may want to update your schema instead of calling this function directly. + """.trimIndent(), + selfClassName + ) + .addSuppressions(enum.values.any { it.deprecationReason != null }) + .maybeAddOptIn(context.resolver, enum.values) + .addParameter(rawValue, KotlinSymbols.String) + .returns(selfClassName) + .beginControlFlow("return when($rawValue)") + .addCode( + values + .map { CodeBlock.of("%S -> %T", it.name, it.valueClassName()) } + .joinToCode(separator = "\n", suffix = "\n") + ) + .addCode(buildCodeBlock { + add("else -> {\n") + withIndent { + add("@%T(%T::class)\n", KotlinSymbols.OptIn, KotlinSymbols.ApolloPrivateEnumConstructor) + add("$__Unknown($rawValue)\n") + } + add("}\n") + }) + .endControlFlow() + .build() + } + + private fun IrEnum.knownValuesFunSpec(): FunSpec { + return FunSpec.builder(Identifier.knownValues) + .addKdoc("Returns all [%T] known at build time", selfClassName) + .addSuppressions(enum.values.any { it.deprecationReason != null }) + .maybeAddOptIn(context.resolver, enum.values) + .returns(KotlinSymbols.Array.parameterizedBy(selfClassName)) + .addCode( + CodeBlock.builder() + .add("return arrayOf(\n") + .indent() + .add( + values.map { + CodeBlock.of("%T", it.valueClassName()) + }.joinToCode(",\n") + ) + .unindent() + .add(")\n") + .build() + ) + .build() + } + + private fun IrEnum.Value.valueClassName(): ClassName { + return ClassName(selfClassName.packageName, selfClassName.simpleName, targetName.escapeKotlinReservedWordInSealedClass()) + } + +} diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsApolloEnumSupportBuilder.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsApolloEnumSupportBuilder.kt new file mode 100644 index 00000000000..e2a282da2de --- /dev/null +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsApolloEnumSupportBuilder.kt @@ -0,0 +1,72 @@ +package com.apollographql.apollo.compiler.codegen.kotlin.schema + +import com.apollographql.apollo.compiler.codegen.Identifier.rawValue +import com.apollographql.apollo.compiler.codegen.kotlin.CgFile +import com.apollographql.apollo.compiler.codegen.kotlin.CgFileBuilder +import com.apollographql.apollo.compiler.codegen.kotlin.KotlinSchemaContext +import com.apollographql.apollo.compiler.codegen.kotlin.KotlinSymbols +import com.apollographql.apollo.compiler.codegen.typePackageName +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.LambdaTypeName +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.PropertySpec +import com.squareup.kotlinpoet.TypeSpec +import com.squareup.kotlinpoet.TypeVariableName + +/** + * Generates supporting symbols for ApolloEnum. + * This could have been `apollo-api` symbols but since everything is experimental, generating them + * only exposes the symbols for the actual users. Also because it will be generated in a dedicated + * package name, it allows removing/tweaking them more easily if needed. + */ +internal class EnumAsApolloEnumSupportBuilder( + context: KotlinSchemaContext, +) : CgFileBuilder { + private val layout = context.layout + private val packageName = layout.typePackageName().experimental() + private val simpleName = "apollo-enum" + + override fun prepare() { + } + + override fun build(): CgFile { + return CgFile( + packageName = packageName, + fileName = simpleName, + typeSpecs = listOf( + TypeSpec.interfaceBuilder("ApolloEnum") + .addTypeVariable(TypeVariableName("E")) + .addTypeVariable(TypeVariableName("K", ClassName(packageName, "KnownEnum").parameterizedBy(TypeVariableName("E")))) + .addProperty(PropertySpec.builder(rawValue, KotlinSymbols.String).build()) + .build(), + TypeSpec.interfaceBuilder("KnownEnum") + .addTypeVariable(TypeVariableName("E")) + .build(), + ), + funSpecs = listOf( + FunSpec.builder("knownOrDefault") + .addModifiers(KModifier.INLINE) + .addTypeVariable(TypeVariableName("E", ClassName(packageName, "ApolloEnum").parameterizedBy(TypeVariableName("E"), TypeVariableName("K")))) + .addTypeVariable(TypeVariableName("K", ClassName(packageName, "KnownEnum").parameterizedBy(TypeVariableName("E"))).copy(reified = true)) + .returns(TypeVariableName("K")) + .receiver(TypeVariableName("E")) + .addParameter(ParameterSpec.builder("default", LambdaTypeName.get(receiver = null, parameters = listOf(ParameterSpec.unnamed(TypeVariableName("E"))), returnType = TypeVariableName("K"))).build()) + .addCode("return if (this is K) this else default(this)\n") + .build(), + FunSpec.builder("knownOrNull") + .addModifiers(KModifier.INLINE) + .addTypeVariable(TypeVariableName("E", ClassName(packageName, "ApolloEnum").parameterizedBy(TypeVariableName("E"), TypeVariableName("K")))) + .addTypeVariable(TypeVariableName("K", ClassName(packageName, "KnownEnum").parameterizedBy(TypeVariableName("E"))).copy(reified = true)) + .returns(TypeVariableName("K").copy(nullable = true)) + .receiver(TypeVariableName("E")) + .addCode("return if (this is K) this else null\n") + .build(), + ) + ) + } +} + +internal fun String.experimental(): String = "$this.experimental" diff --git a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsSealedBuilder.kt b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsSealedInterfaceBuilder.kt similarity index 99% rename from libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsSealedBuilder.kt rename to libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsSealedInterfaceBuilder.kt index ea469b65898..9dfe2cf503e 100644 --- a/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsSealedBuilder.kt +++ b/libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/codegen/kotlin/schema/EnumAsSealedInterfaceBuilder.kt @@ -25,7 +25,7 @@ import com.squareup.kotlinpoet.TypeName import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.joinToCode -internal class EnumAsSealedBuilder( +internal class EnumAsSealedInterfaceBuilder( private val context: KotlinSchemaContext, private val enum: IrEnum, ) : CgFileBuilder { diff --git a/libraries/apollo-gradle-plugin-tasks/api/apollo-gradle-plugin-tasks.api b/libraries/apollo-gradle-plugin-tasks/api/apollo-gradle-plugin-tasks.api index 33a21cbd0e9..e644a71cc23 100644 --- a/libraries/apollo-gradle-plugin-tasks/api/apollo-gradle-plugin-tasks.api +++ b/libraries/apollo-gradle-plugin-tasks/api/apollo-gradle-plugin-tasks.api @@ -51,11 +51,11 @@ public final class com/apollographql/apollo/gradle/task/ApolloGenerateIrOperatio public final class com/apollographql/apollo/gradle/task/ApolloGenerateOptionsEntryPoint { public static final field Companion Lcom/apollographql/apollo/gradle/task/ApolloGenerateOptionsEntryPoint$Companion; public fun ()V - public static final fun run (Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;ZLjava/lang/String;ZZLjava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;)V + public static final fun run (Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;ZLjava/lang/String;ZZLjava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;)V } public final class com/apollographql/apollo/gradle/task/ApolloGenerateOptionsEntryPoint$Companion { - public final fun run (Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;ZLjava/lang/String;ZZLjava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;)V + public final fun run (Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/Map;Ljava/util/Map;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/Set;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Boolean;Ljava/lang/String;Ljava/lang/Boolean;Ljava/util/List;Ljava/util/List;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/Boolean;Ljava/lang/String;Ljava/util/List;ZLjava/lang/String;ZZLjava/io/File;Ljava/io/File;Ljava/io/File;Ljava/io/File;)V } public final class com/apollographql/apollo/gradle/task/ApolloGenerateOptionsKt { diff --git a/libraries/apollo-gradle-plugin-tasks/src/main/kotlin/com/apollographql/apollo/gradle/task/apolloGenerateOptions.kt b/libraries/apollo-gradle-plugin-tasks/src/main/kotlin/com/apollographql/apollo/gradle/task/apolloGenerateOptions.kt index 5de26b83c6d..778df3cd088 100644 --- a/libraries/apollo-gradle-plugin-tasks/src/main/kotlin/com/apollographql/apollo/gradle/task/apolloGenerateOptions.kt +++ b/libraries/apollo-gradle-plugin-tasks/src/main/kotlin/com/apollographql/apollo/gradle/task/apolloGenerateOptions.kt @@ -53,6 +53,7 @@ internal fun apolloGenerateOptions( classesForEnumsMatching: List?, // KotlinCodegenOptions sealedClassesForEnumsMatching: List?, + generateApolloEnums: Boolean?, generateAsInternal: Boolean?, generateInputBuilders: Boolean?, addJvmOverloads: Boolean?, @@ -136,6 +137,7 @@ internal fun apolloGenerateOptions( decapitalizeFields = decapitalizeFields, addDefaultArgumentForInputObjects = true, addUnknownForEnums = true, + generateApolloEnums = generateApolloEnums, packageName = packageName, rootPackageName = rootPackageName ).writeTo(codegenOptions) diff --git a/libraries/apollo-gradle-plugin/api/apollo-gradle-plugin.api b/libraries/apollo-gradle-plugin/api/apollo-gradle-plugin.api index 39a247432a3..e99dfc64638 100644 --- a/libraries/apollo-gradle-plugin/api/apollo-gradle-plugin.api +++ b/libraries/apollo-gradle-plugin/api/apollo-gradle-plugin.api @@ -148,6 +148,7 @@ public abstract interface class com/apollographql/apollo/gradle/api/Service { public abstract fun getFailOnWarnings ()Lorg/gradle/api/provider/Property; public abstract fun getFieldsOnDisjointTypesMustMerge ()Lorg/gradle/api/provider/Property; public abstract fun getFlattenModels ()Lorg/gradle/api/provider/Property; + public abstract fun getGenerateApolloEnums ()Lorg/gradle/api/provider/Property; public abstract fun getGenerateApolloMetadata ()Lorg/gradle/api/provider/Property; public abstract fun getGenerateAsInternal ()Lorg/gradle/api/provider/Property; public abstract fun getGenerateDataBuilders ()Lorg/gradle/api/provider/Property; @@ -238,6 +239,6 @@ public abstract interface class com/apollographql/apollo/gradle/api/Service$Outg } public final class com/apollographql/apollo/gradle/task/ApolloGenerateOptionsTaskKt { - public static synthetic fun registerApolloGenerateOptionsTask$default (Lorg/gradle/api/Project;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/gradle/api/file/FileCollection;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/file/FileCollection;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;IILjava/lang/Object;)Lorg/gradle/api/tasks/TaskProvider; + public static synthetic fun registerApolloGenerateOptionsTask$default (Lorg/gradle/api/Project;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lorg/gradle/api/file/FileCollection;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/file/FileCollection;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/provider/Provider;IILjava/lang/Object;)Lorg/gradle/api/tasks/TaskProvider; } diff --git a/libraries/apollo-gradle-plugin/src/main/kotlin/com/apollographql/apollo/gradle/api/Service.kt b/libraries/apollo-gradle-plugin/src/main/kotlin/com/apollographql/apollo/gradle/api/Service.kt index e383cc08368..d37a1997d90 100644 --- a/libraries/apollo-gradle-plugin/src/main/kotlin/com/apollographql/apollo/gradle/api/Service.kt +++ b/libraries/apollo-gradle-plugin/src/main/kotlin/com/apollographql/apollo/gradle/api/Service.kt @@ -521,6 +521,14 @@ interface Service { */ val sealedClassesForEnumsMatching: ListProperty + /** + * Whether to generate enums as ApolloEnum. + * + * Experimental, see https://github.com/apollographql/apollo-kotlin/issues/6243. + */ + @ApolloExperimental + val generateApolloEnums: Property + /** * A list of [Regex] patterns for GraphQL enums that should be generated as Java classes. * @@ -529,7 +537,7 @@ interface Service { * Use this if you want your client to have access to the rawValue of the enum. This can be useful if new GraphQL enums are added but * the client was compiled against an older schema that doesn't have knowledge of the new enums. * - * Default: listOf(".*") + * Default: `listOf(".*")` */ val classesForEnumsMatching: ListProperty @@ -542,8 +550,8 @@ interface Service { * * You can pass the special value "none" to disable adding an annotation. * If you're using a custom annotation, it must be able to target: - * - AnnotationTarget.PROPERTY - * - AnnotationTarget.CLASS + * - [AnnotationTarget.PROPERTY] + * - [AnnotationTarget.CLASS] * * Default: "none" */ diff --git a/libraries/apollo-gradle-plugin/src/main/kotlin/com/apollographql/apollo/gradle/internal/DefaultApolloExtension.kt b/libraries/apollo-gradle-plugin/src/main/kotlin/com/apollographql/apollo/gradle/internal/DefaultApolloExtension.kt index b52c72c622e..e72845693b7 100644 --- a/libraries/apollo-gradle-plugin/src/main/kotlin/com/apollographql/apollo/gradle/internal/DefaultApolloExtension.kt +++ b/libraries/apollo-gradle-plugin/src/main/kotlin/com/apollographql/apollo/gradle/internal/DefaultApolloExtension.kt @@ -405,6 +405,7 @@ abstract class DefaultApolloExtension( * KotlinCodegenOptions */ sealedClassesForEnumsMatching = service.sealedClassesForEnumsMatching, + generateApolloEnums = service.generateApolloEnums, generateAsInternal = service.generateAsInternal, generateInputBuilders = service.generateInputBuilders, addJvmOverloads = service.addJvmOverloads, diff --git a/tests/enums/build.gradle.kts b/tests/enums/build.gradle.kts index b9a72d7523a..a9cb4365dad 100644 --- a/tests/enums/build.gradle.kts +++ b/tests/enums/build.gradle.kts @@ -27,12 +27,16 @@ apollo { service("java") { packageName.set("enums.java") - classesForEnumsMatching.set(listOf(".*avity", "FooClass")) + classesForEnumsMatching.set(listOf(".*avity", "FooClass", "Color")) generateKotlinModels.set(false) outputDirConnection { connectToJavaSourceSet("main") } } + service("apollo") { + packageName.set("enums.apollo") + generateApolloEnums.set(true) + } } //kotlin { diff --git a/tests/enums/src/main/graphql/operation.graphql b/tests/enums/src/main/graphql/operation.graphql index 8b05a607372..ae4122b8193 100644 --- a/tests/enums/src/main/graphql/operation.graphql +++ b/tests/enums/src/main/graphql/operation.graphql @@ -6,3 +6,7 @@ query GetEnums { fooClass fooEnum } + +query GetColor { + color +} \ No newline at end of file diff --git a/tests/enums/src/main/graphql/schema.graphqls b/tests/enums/src/main/graphql/schema.graphqls index 730f4534e2c..c42aa043089 100644 --- a/tests/enums/src/main/graphql/schema.graphqls +++ b/tests/enums/src/main/graphql/schema.graphqls @@ -5,6 +5,7 @@ type Query { fooSealed: FooSealed fooClass: FooClass fooEnum: FooEnum + color: Color! } enum Direction { @@ -64,3 +65,10 @@ enum FooClass { # not renamed in extra.graphqls, will be renamed automatically type, } + +#See https://github.com/apollographql/apollo-kotlin/issues/6243 +enum Color { + BLUEBERRY, + CHERRY + CANDY +} \ No newline at end of file diff --git a/tests/enums/src/test/kotlin/test/ApolloEnumTest.kt b/tests/enums/src/test/kotlin/test/ApolloEnumTest.kt new file mode 100644 index 00000000000..342ac4cd46d --- /dev/null +++ b/tests/enums/src/test/kotlin/test/ApolloEnumTest.kt @@ -0,0 +1,48 @@ +package test + +import enums.apollo.type.Color +import enums.apollo.type.experimental.knownOrDefault +import enums.apollo.type.experimental.knownOrNull +import kotlin.test.Test +import kotlin.test.assertEquals + + +class ApolloEnumTest { + @Test + fun knownOrDefault() { + assertEquals(Color.BLUEBERRY, Color.safeValueOf("UNKNOWN").knownOrDefault { Color.BLUEBERRY }) + assertEquals(Color.CHERRY, Color.safeValueOf("CHERRY").knownOrDefault { Color.CHERRY }) + } + + @Test + fun knownOrNull() { + assertEquals(null, Color.safeValueOf("UNKNOWN").knownOrNull()) + assertEquals(Color.CHERRY, Color.safeValueOf("CHERRY").knownOrNull()) + } + + @Test + fun knownOrCandy() { + assertEquals(Color.CANDY, Color.safeValueOf("UNKNOWN").knownOrCandy()) + } + + /** + * This is only used to check it compiles properly + */ + fun doStuff(color: Color) { + when (color.knownOrCandy()) { + Color.BLUEBERRY -> TODO() + Color.CANDY -> TODO() + Color.CHERRY -> TODO() + } + } + + /** + * Turns a maybe unknown color value into a known one + */ + private fun Color.knownOrCandy(): Color.__Known = when (this) { + is Color.__Unknown -> Color.CANDY + // Sadly cannot use `else ->` here so we use explicit branches + // See https://youtrack.jetbrains.com/issue/KT-18950/Smart-Cast-should-work-within-else-branch-for-sealed-subclasses + is Color.__Known -> this + } +} \ No newline at end of file