Skip to content

Commit

Permalink
add support for @experimental
Browse files Browse the repository at this point in the history
  • Loading branch information
martinbonnin committed May 9, 2022
1 parent 9dab1f3 commit addb4fe
Show file tree
Hide file tree
Showing 26 changed files with 249 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ fun List<GQLDirective>.findDeprecationReason() = firstOrNull { it.name == "depre
?: "No longer supported"
}

fun List<GQLDirective>.findExperimentalReason() = firstOrNull { it.name == "experimental" }
?.let {
it.arguments
?.arguments
?.firstOrNull { it.name == "reason" }
?.value
?.let { value ->
if (value !is GQLStringValue) {
throw ConversionException("reason must be a string", it.sourceLocation)
}
value.value
}
?: "Experimental"
}

/**
* @return `true` or `false` based on the `if` argument if the `optional` directive is present, `null` otherwise
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import com.apollographql.apollo3.ast.SourceLocation
import com.apollographql.apollo3.ast.VariableUsage
import com.apollographql.apollo3.ast.definitionFromScope
import com.apollographql.apollo3.ast.findDeprecationReason
import com.apollographql.apollo3.ast.findExperimentalReason
import com.apollographql.apollo3.ast.leafType
import com.apollographql.apollo3.ast.pretty
import com.apollographql.apollo3.ast.responseName
Expand Down
6 changes: 6 additions & 0 deletions apollo-ast/src/main/resources/apollo.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ directive @typePolicy(keyFields: String!) on OBJECT
# Indicates how to compute a key from a field arguments.
# `keyArgs` should contain a selection set. Composite args are not supported yet.
directive @fieldPolicy(forField: String!, keyArgs: String!) repeatable on OBJECT

# Indicates that the given field or enum value is still experimental and might be changed
# in a backward incompatible manner
directive @experimental(
reason: String! = "Experimental"
) on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ object ApolloCompiler {
scalarMapping = options.scalarMapping,
nameToClassName = nameToClassName,
addJvmOverloads = options.addJvmOverloads,
experimentalAnnotation = options.experimentalAnnotation,
).write(outputDir = outputDir, testDir = testDir)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,7 @@ class Options(
*/
val addJvmOverloads: Boolean = false,
val addTypename: String = defaultAddTypename,
val experimentalAnnotation: String? = defaultExperimentalAnnotation
) {

/**
Expand Down Expand Up @@ -239,6 +240,7 @@ class Options(
generateOptionalOperationVariables: Boolean = this.generateOptionalOperationVariables,
addJvmOverloads: Boolean = this.addJvmOverloads,
addTypename: String = this.addTypename,
experimentalAnnotation: String? = this.experimentalAnnotation
) = Options(
executableFiles = executableFiles,
schema = schema,
Expand Down Expand Up @@ -273,6 +275,7 @@ class Options(
generateOptionalOperationVariables = generateOptionalOperationVariables,
addJvmOverloads = addJvmOverloads,
addTypename = addTypename,
experimentalAnnotation = experimentalAnnotation,
)

companion object {
Expand All @@ -292,6 +295,7 @@ class Options(
const val defaultModuleName = "apollographql"
const val defaultCodegenModels = MODELS_OPERATION_BASED
const val defaultAddTypename = ADD_TYPENAME_IF_FRAGMENTS
const val defaultExperimentalAnnotation = "com.apollographql.apollo3.annotations.ApolloExperimental"
const val defaultFlattenModels = true
val defaultTargetLanguage = TargetLanguage.KOTLIN_1_5
const val defaultGenerateSchema = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,14 +65,15 @@ class KotlinCodeGen(
private val scalarMapping: Map<String, ScalarInfo>,
private val nameToClassName: Map<String, String>,
private val addJvmOverloads: Boolean,
private val experimentalAnnotation: String?
) {
/**
* @param outputDir: the directory where to write the Kotlin files
* @return a ResolverInfo to be used by downstream modules
*/
fun write(outputDir: File, testDir: File): ResolverInfo {
val upstreamResolver = resolverInfos.fold(null as KotlinResolver?) { acc, resolverInfo ->
KotlinResolver(resolverInfo.entries, acc, scalarMapping)
KotlinResolver(resolverInfo.entries, acc, scalarMapping, experimentalAnnotation)
}

val layout = KotlinCodegenLayout(
Expand All @@ -85,7 +86,7 @@ class KotlinCodeGen(

val context = KotlinContext(
layout = layout,
resolver = KotlinResolver(emptyList(), upstreamResolver, scalarMapping),
resolver = KotlinResolver(emptyList(), upstreamResolver, scalarMapping, experimentalAnnotation),
targetLanguageVersion = targetLanguageVersion,
)
val builders = mutableListOf<CgFileBuilder>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,12 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.TypeName


class KotlinResolver(entries: List<ResolverEntry>, val next: KotlinResolver?, private val scalarMapping: Map<String, ScalarInfo>) {
class KotlinResolver(
entries: List<ResolverEntry>,
val next: KotlinResolver?,
private val scalarMapping: Map<String, ScalarInfo>,
private val experimentalAnnotation: String?
) {
fun resolve(key: ResolverKey): ClassName? = classNames[key] ?: next?.resolve(key)

private var classNames = entries.associateBy(
Expand Down Expand Up @@ -72,7 +77,7 @@ class KotlinResolver(entries: List<ResolverEntry>, val next: KotlinResolver?, pr

private fun resolveIrScalarType(type: IrScalarType): ClassName {
// Try mapping first, then built-ins, then fallback to Any
return resolveScalarTaget(type.name) ?: when (type.name) {
return resolveScalarTarget(type.name) ?: when (type.name) {
"String" -> KotlinSymbols.String
"Float" -> KotlinSymbols.Double
"Int" -> KotlinSymbols.Int
Expand All @@ -82,8 +87,8 @@ class KotlinResolver(entries: List<ResolverEntry>, val next: KotlinResolver?, pr
}
}

fun resolveScalarTaget(name: String): ClassName? {
return scalarMapping.get(name)?.targetName?.let {
private fun resolveScalarTarget(name: String): ClassName? {
return scalarMapping[name]?.targetName?.let {
ClassName.bestGuess(it)
}
}
Expand All @@ -98,7 +103,7 @@ class KotlinResolver(entries: List<ResolverEntry>, val next: KotlinResolver?, pr
type is IrScalarType && type.name == "String" && scalarWithoutCustomMapping -> CodeBlock.of("%M", KotlinSymbols.NullableStringAdapter)
type is IrScalarType && type.name == "Int" && scalarWithoutCustomMapping -> CodeBlock.of("%M", KotlinSymbols.NullableIntAdapter)
type is IrScalarType && type.name == "Float" && scalarWithoutCustomMapping -> CodeBlock.of("%M", KotlinSymbols.NullableDoubleAdapter)
type is IrScalarType && resolveScalarTaget(type.name) == null -> {
type is IrScalarType && resolveScalarTarget(type.name) == null -> {
CodeBlock.of("%M", KotlinSymbols.NullableAnyAdapter)
}
else -> {
Expand Down Expand Up @@ -161,7 +166,7 @@ class KotlinResolver(entries: List<ResolverEntry>, val next: KotlinResolver?, pr
CodeBlock.of(adapterInitializer.expression)
}
is RuntimeAdapterInitializer -> {
val target = resolveScalarTaget(type.name)
val target = resolveScalarTarget(type.name)
CodeBlock.of(
"$customScalarAdapters.responseAdapterFor<%T>(%L)",
target,
Expand All @@ -176,7 +181,7 @@ class KotlinResolver(entries: List<ResolverEntry>, val next: KotlinResolver?, pr
"Int" -> CodeBlock.of("%M", KotlinSymbols.IntAdapter)
"Float" -> CodeBlock.of("%M", KotlinSymbols.DoubleAdapter)
else -> {
val target = resolveScalarTaget(type.name)
val target = resolveScalarTarget(type.name)
if (target == null) {
CodeBlock.of("%M", KotlinSymbols.AnyAdapter)
} else {
Expand Down Expand Up @@ -237,4 +242,10 @@ class KotlinResolver(entries: List<ResolverEntry>, val next: KotlinResolver?, pr

fun registerTestBuilder(path: String, className: ClassName) = register(ResolverKeyKind.TestBuilder, path, className)
fun resolveTestBuilder(path: String) = resolveAndAssert(ResolverKeyKind.TestBuilder, path)
fun resolveExperimentalAnnotation(): ClassName? {
if (experimentalAnnotation == "none") {
return null
}
return experimentalAnnotation?.let { ClassName.bestGuess(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinContext
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinSymbols
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDeprecation
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDescription
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddExperimental
import com.apollographql.apollo3.compiler.ir.IrEnum
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
Expand Down Expand Up @@ -103,6 +104,7 @@ class EnumAsEnumBuilder(
private fun IrEnum.Value.enumConstTypeSpec(): TypeSpec {
return TypeSpec.anonymousClassBuilder()
.maybeAddDeprecation(deprecationReason)
.maybeAddExperimental(context.resolver, experimentalReason)
.maybeAddDescription(description)
.addSuperclassConstructorParameter("%S", name)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import com.apollographql.apollo3.compiler.codegen.kotlin.CgFileBuilder
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinContext
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinSymbols
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.deprecatedAnnotation
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDeprecation
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDescription
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddExperimental
import com.apollographql.apollo3.compiler.ir.IrEnum
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
Expand Down Expand Up @@ -75,8 +77,9 @@ class EnumAsSealedBuilder(

private fun IrEnum.Value.toObjectTypeSpec(superClass: TypeName): TypeSpec {
return TypeSpec.objectBuilder(layout.enumAsSealedClassValueName(name))
.applyIf(description?.isNotBlank() == true) { addKdoc("%L\n", description!!) }
.applyIf(deprecationReason != null) { addAnnotation(deprecatedAnnotation(deprecationReason!!)) }
.maybeAddDeprecation(deprecationReason)
.maybeAddDescription(description)
.maybeAddExperimental(context.resolver, experimentalReason)
.superclass(superClass)
.addSuperclassConstructorParameter("rawValue·=·%S", name)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import com.apollographql.apollo3.compiler.codegen.kotlin.CgFile
import com.apollographql.apollo3.compiler.codegen.kotlin.CgFileBuilder
import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinContext
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.makeDataClass
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDeprecation
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDescription
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddExperimental
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.toNamedType
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.toParameterSpec
import com.apollographql.apollo3.compiler.ir.IrInputObject
Expand Down Expand Up @@ -35,7 +38,7 @@ class InputObjectBuilder(
private fun IrInputObject.typeSpec() =
TypeSpec
.classBuilder(simpleName)
.applyIf(description?.isNotBlank()== true) { addKdoc("%L\n", description!!) }
.maybeAddDescription(description)
.makeDataClass(fields.map {
it.toNamedType().toParameterSpec(context)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ fun TypeSpec.Builder.makeDataClassFromProperties(properties: List<PropertySpec>)
primaryConstructor(FunSpec.constructorBuilder()
.apply {
properties.forEach {
addParameter(it.name, it.type)
addParameter(
ParameterSpec.builder(it.name, it.type)
.addAnnotations(it.annotations)
.build()
)
}
}
.build())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.apollographql.apollo3.compiler.codegen.kotlin.helpers

import com.apollographql.apollo3.compiler.codegen.kotlin.KotlinResolver
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec

Expand All @@ -19,6 +22,15 @@ internal fun PropertySpec.Builder.maybeAddDescription(description: String?): Pro
return addKdoc("%L", description)
}

internal fun ParameterSpec.Builder.maybeAddDescription(description: String?): ParameterSpec.Builder {
if (description.isNullOrBlank()) {
return this
}

return addKdoc("%L", description)
}


internal fun TypeSpec.Builder.maybeAddDeprecation(deprecationReason: String?): TypeSpec.Builder {
if (deprecationReason.isNullOrBlank()) {
return this
Expand All @@ -34,3 +46,38 @@ internal fun PropertySpec.Builder.maybeAddDeprecation(deprecationReason: String?

return addAnnotation(deprecatedAnnotation(deprecationReason))
}

internal fun ParameterSpec.Builder.maybeAddDeprecation(deprecationReason: String?): ParameterSpec.Builder {
if (deprecationReason.isNullOrBlank()) {
return this
}

return addAnnotation(deprecatedAnnotation(deprecationReason))
}

internal fun TypeSpec.Builder.maybeAddExperimental(resolver: KotlinResolver, experimentalReason: String?): TypeSpec.Builder {
if (experimentalReason.isNullOrBlank()) {
return this
}

val annotation = resolver.resolveExperimentalAnnotation() ?: return this
return addAnnotation(AnnotationSpec.builder(annotation).build())
}

internal fun PropertySpec.Builder.maybeAddExperimental(resolver: KotlinResolver, experimentalReason: String?): PropertySpec.Builder {
if (experimentalReason.isNullOrBlank()) {
return this
}

val annotation = resolver.resolveExperimentalAnnotation() ?: return this
return addAnnotation(AnnotationSpec.builder(annotation).build())
}

internal fun ParameterSpec.Builder.maybeAddExperimental(resolver: KotlinResolver, experimentalReason: String?): ParameterSpec.Builder {
if (experimentalReason.isNullOrBlank()) {
return this
}

val annotation = resolver.resolveExperimentalAnnotation() ?: return this
return addAnnotation(AnnotationSpec.builder(annotation).build())
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class NamedType(
val graphQlName: String,
val description: String?,
val deprecationReason: String?,
val experimentalReason: String?,
val type: IrType,
)

Expand All @@ -26,7 +27,9 @@ internal fun NamedType.toParameterSpec(context: KotlinContext): ParameterSpec {
name = context.layout.propertyName(graphQlName),
type = context.resolver.resolveIrType(type)
)
.applyIf(description?.isNotBlank() == true) { addKdoc("%L\n", description!!) }
.maybeAddDescription(description)
.maybeAddDeprecation(deprecationReason)
.maybeAddExperimental(context.resolver, experimentalReason)
.applyIf(type.isOptional()) { defaultValue("%T", KotlinSymbols.Absent) }
.build()
}
Expand All @@ -37,13 +40,15 @@ fun IrInputField.toNamedType() = NamedType(
type = type,
description = description,
deprecationReason = deprecationReason,
experimentalReason = experimentalReason
)

fun IrVariable.toNamedType() = NamedType(
graphQlName = name,
type = type,
description = null,
deprecationReason = null,
experimentalReason = null
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import com.apollographql.apollo3.compiler.codegen.kotlin.adapter.from
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.makeDataClassFromProperties
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDeprecation
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddDescription
import com.apollographql.apollo3.compiler.codegen.kotlin.helpers.maybeAddExperimental
import com.apollographql.apollo3.compiler.decapitalizeFirstLetter
import com.apollographql.apollo3.compiler.ir.IrAccessor
import com.apollographql.apollo3.compiler.ir.IrFragmentAccessor
Expand Down Expand Up @@ -59,6 +60,7 @@ class ModelBuilder(
.applyIf(it.override) { addModifiers(KModifier.OVERRIDE) }
.maybeAddDescription(it.info.description)
.maybeAddDeprecation(it.info.deprecationReason)
.maybeAddExperimental(context.resolver, it.info.experimentalReason)
.build()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ data class IrEnum(
val name: String,
val description: String?,
val deprecationReason: String?,
val experimentalReason: String?
)
}

Expand All @@ -60,6 +61,7 @@ data class IrInputField(
val name: String,
val description: String?,
val deprecationReason: String?,
val experimentalReason: String?,
val type: IrType,
val defaultValue: IrValue?,
)
Expand Down Expand Up @@ -126,6 +128,7 @@ data class IrFieldInfo(
* from the fieldDefinition directives
*/
val deprecationReason: String?,
val experimentalReason: String?,
)

sealed class IrAccessor {
Expand Down

0 comments on commit addb4fe

Please sign in to comment.