Skip to content

Commit

Permalink
Generate Cache class from @CacheControl directives
Browse files Browse the repository at this point in the history
  • Loading branch information
BoD committed Aug 22, 2024
1 parent 8d14880 commit b0adfa1
Show file tree
Hide file tree
Showing 59 changed files with 2,765 additions and 4 deletions.
3 changes: 3 additions & 0 deletions libraries/apollo-ast/api/apollo-ast.api
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ public final class com/apollographql/apollo/ast/GqldirectiveKt {

public final class com/apollographql/apollo/ast/GqldocumentKt {
public static final fun builtinDefinitions ()Ljava/util/List;
public static final fun cacheDefinitions (Ljava/lang/String;)Ljava/util/List;
public static final fun kotlinLabsDefinitions (Ljava/lang/String;)Ljava/util/List;
public static final fun linkDefinitions ()Ljava/util/List;
public static final fun nullabilityDefinitions (Ljava/lang/String;)Ljava/util/List;
Expand Down Expand Up @@ -927,6 +928,8 @@ public final class com/apollographql/apollo/ast/ReservedEnumValueName : com/apol
}

public final class com/apollographql/apollo/ast/Schema {
public static final field CACHE_CONTROL Ljava/lang/String;
public static final field CACHE_CONTROL_FIELD Ljava/lang/String;
public static final field CATCH Ljava/lang/String;
public static final field CATCH_BY_DEFAULT Ljava/lang/String;
public static final field Companion Lcom/apollographql/apollo/ast/Schema$Companion;
Expand Down
1 change: 1 addition & 0 deletions libraries/apollo-ast/api/apollo-ast.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -1035,6 +1035,7 @@ final fun (kotlin/String).com.apollographql.apollo.ast/toGQLValue(com.apollograp
final fun (okio/Path).com.apollographql.apollo.ast/parseAsGQLDocument(com.apollographql.apollo.ast/ParserOptions = ...): com.apollographql.apollo.ast/GQLResult<com.apollographql.apollo.ast/GQLDocument> // com.apollographql.apollo.ast/parseAsGQLDocument|[email protected](com.apollographql.apollo.ast.ParserOptions){}[0]
final fun (okio/Path).com.apollographql.apollo.ast/toGQLDocument(com.apollographql.apollo.ast/ParserOptions = ..., kotlin/Boolean = ...): com.apollographql.apollo.ast/GQLDocument // com.apollographql.apollo.ast/toGQLDocument|[email protected](com.apollographql.apollo.ast.ParserOptions;kotlin.Boolean){}[0]
final fun com.apollographql.apollo.ast/builtinDefinitions(): kotlin.collections/List<com.apollographql.apollo.ast/GQLDefinition> // com.apollographql.apollo.ast/builtinDefinitions|builtinDefinitions(){}[0]
final fun com.apollographql.apollo.ast/cacheDefinitions(kotlin/String): kotlin.collections/List<com.apollographql.apollo.ast/GQLDefinition> // com.apollographql.apollo.ast/cacheDefinitions|cacheDefinitions(kotlin.String){}[0]
final fun com.apollographql.apollo.ast/kotlinLabsDefinitions(kotlin/String): kotlin.collections/List<com.apollographql.apollo.ast/GQLDefinition> // com.apollographql.apollo.ast/kotlinLabsDefinitions|kotlinLabsDefinitions(kotlin.String){}[0]
final fun com.apollographql.apollo.ast/linkDefinitions(): kotlin.collections/List<com.apollographql.apollo.ast/GQLDefinition> // com.apollographql.apollo.ast/linkDefinitions|linkDefinitions(){}[0]
final fun com.apollographql.apollo.ast/nullabilityDefinitions(kotlin/String): kotlin.collections/List<com.apollographql.apollo.ast/GQLDefinition> // com.apollographql.apollo.ast/nullabilityDefinitions|nullabilityDefinitions(kotlin.String){}[0]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Schema internal constructor(
private val directivesToStrip: List<String>,
@ApolloInternal
val connectionTypes: Set<String>,
@ApolloInternal
val maxAges: Map<String, Int>,
) {
@ApolloInternal
val schemaDefinition: GQLSchemaDefinition? = definitions.filterIsInstance<GQLSchemaDefinition>().singleOrNull()
Expand Down Expand Up @@ -136,6 +138,7 @@ class Schema internal constructor(
"foreignNames" to foreignNames,
"directivesToStrip" to directivesToStrip,
"connectionTypes" to connectionTypes.toList().sorted(),
"maxAges" to maxAges,
)
}

Expand Down Expand Up @@ -218,6 +221,10 @@ class Schema internal constructor(
const val SEMANTIC_NON_NULL_FIELD = "semanticNonNullField"
@ApolloExperimental
const val LINK = "link"
@ApolloExperimental
const val CACHE_CONTROL = "cacheControl"
@ApolloExperimental
const val CACHE_CONTROL_FIELD = "cacheControlField"

const val FIELD_POLICY_FOR_FIELD = "forField"
const val FIELD_POLICY_KEY_ARGS = "keyArgs"
Expand All @@ -238,6 +245,7 @@ class Schema internal constructor(
foreignNames = map["foreignNames"]!! as Map<String, String>,
directivesToStrip = map["directivesToStrip"]!! as List<String>,
connectionTypes = (map["connectionTypes"]!! as List<String>).toSet(),
maxAges = map["maxAges"]!! as Map<String, Int>,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.apollographql.apollo.ast.internal
import com.apollographql.apollo.annotations.ApolloInternal
import com.apollographql.apollo.ast.ConflictResolution
import com.apollographql.apollo.ast.DirectiveRedefinition
import com.apollographql.apollo.ast.GQLBooleanValue
import com.apollographql.apollo.ast.GQLDefinition
import com.apollographql.apollo.ast.GQLDirective
import com.apollographql.apollo.ast.GQLDirectiveDefinition
Expand All @@ -11,6 +12,7 @@ import com.apollographql.apollo.ast.GQLDocument
import com.apollographql.apollo.ast.GQLEnumTypeDefinition
import com.apollographql.apollo.ast.GQLField
import com.apollographql.apollo.ast.GQLInputObjectTypeDefinition
import com.apollographql.apollo.ast.GQLIntValue
import com.apollographql.apollo.ast.GQLInterfaceTypeDefinition
import com.apollographql.apollo.ast.GQLListValue
import com.apollographql.apollo.ast.GQLNamed
Expand All @@ -36,6 +38,8 @@ import com.apollographql.apollo.ast.NULLABILITY_VERSION
import com.apollographql.apollo.ast.NoQueryType
import com.apollographql.apollo.ast.OtherValidationIssue
import com.apollographql.apollo.ast.Schema
import com.apollographql.apollo.ast.Schema.Companion.CACHE_CONTROL
import com.apollographql.apollo.ast.Schema.Companion.CACHE_CONTROL_FIELD
import com.apollographql.apollo.ast.Schema.Companion.TYPE_POLICY
import com.apollographql.apollo.ast.builtinDefinitions
import com.apollographql.apollo.ast.cacheDefinitions
Expand Down Expand Up @@ -215,6 +219,7 @@ internal fun validateSchema(definitions: List<GQLDefinition>, requiresApolloDefi

val keyFields = mergedScope.validateAndComputeKeyFields()
val connectionTypes = mergedScope.computeConnectionTypes()
val maxAges = mergedScope.computeMaxAges()

return GQLResult(
Schema(
Expand All @@ -223,6 +228,7 @@ internal fun validateSchema(definitions: List<GQLDefinition>, requiresApolloDefi
foreignNames = foreignNames,
directivesToStrip = directivesToStrip,
connectionTypes = connectionTypes,
maxAges = maxAges,
),
issues
)
Expand Down Expand Up @@ -518,7 +524,8 @@ private fun ValidationScope.validateCatch(schemaDefinition: GQLSchemaDefinition?
issues.add(OtherValidationIssue(
message = "Schemas that include nullability directives must opt-in a default CatchTo. Use `extend schema @catchByDefault(to: \$to)`",
sourceLocation = null
))
)
)
return
}

Expand All @@ -530,13 +537,15 @@ private fun ValidationScope.validateCatch(schemaDefinition: GQLSchemaDefinition?
issues.add(OtherValidationIssue(
message = "Schemas that include nullability directives must opt-in a default CatchTo. Use `extend schema @catchByDefault(to: \$to)`",
sourceLocation = schemaDefinition.sourceLocation
))
)
)
return
} else if (catches.size > 1) {
issues.add(OtherValidationIssue(
message = "There can be only one `@catch` directive on the schema definition",
sourceLocation = schemaDefinition.sourceLocation
))
)
)
return
}
}
Expand Down Expand Up @@ -676,6 +685,63 @@ internal fun ValidationScope.computeConnectionTypes(): Set<String> {
return connectionTypes
}

internal fun ValidationScope.computeMaxAges(): Map<String, Int> {
fun GQLDirective.maxAgeAndInherit(): Pair<Int?, Boolean> {
val maxAge = (arguments.firstOrNull { it.name == "maxAge" }?.value as? GQLIntValue)?.value?.toIntOrNull()
if (maxAge != null && maxAge < 0) {
registerIssue("`maxAge` must not be negative", sourceLocation)
return null to false
}
val inheritMaxAge = (arguments.firstOrNull { it.name == "inheritMaxAge" }?.value as? GQLBooleanValue)?.value == true
if (maxAge == null && !inheritMaxAge || maxAge != null && inheritMaxAge) {
registerIssue("`@$name` must either provide a `maxAge` or an `inheritMaxAge` set to true", sourceLocation)
return null to false
}
return maxAge to inheritMaxAge
}

val maxAges = mutableMapOf<String, Int>()
for (typeDefinition in typeDefinitions.values) {
val typeCacheControlDirective = typeDefinition.directives.firstOrNull { originalDirectiveName(it.name) == CACHE_CONTROL }
if (typeCacheControlDirective != null) {
val (maxAge, inheritMaxAge) = typeCacheControlDirective.maxAgeAndInherit()
if (maxAge != null) {
maxAges[typeDefinition.name] = maxAge
} else if (inheritMaxAge) {
maxAges[typeDefinition.name] = -1
}
}

val typeCacheControlFieldDirectives = typeDefinition.directives.filter { originalDirectiveName(it.name) == CACHE_CONTROL_FIELD }
for (fieldDirective in typeCacheControlFieldDirectives) {
val fieldName = (fieldDirective.arguments.first { it.name == "name" }.value as GQLStringValue).value
if (typeDefinition.fields.none { it.name == fieldName }) {
registerIssue("Field `$fieldName` does not exist on type `${typeDefinition.name}`", fieldDirective.sourceLocation)
continue
}
val (maxAge, inheritMaxAge) = fieldDirective.maxAgeAndInherit()
if (maxAge != null) {
maxAges["${typeDefinition.name}.$fieldName"] = maxAge
} else if (inheritMaxAge) {
maxAges["${typeDefinition.name}.$fieldName"] = -1
}
}

for (field in typeDefinition.fields) {
val fieldCacheControlDirective = field.directives.firstOrNull { originalDirectiveName(it.name) == CACHE_CONTROL }
if (fieldCacheControlDirective != null) {
val (maxAge, inheritMaxAge) = fieldCacheControlDirective.maxAgeAndInherit()
if (maxAge != null) {
maxAges["${typeDefinition.name}.${field.name}"] = maxAge
} else if (inheritMaxAge) {
maxAges["${typeDefinition.name}.${field.name}"] = -1
}
}
}
}
return maxAges
}

private val GQLTypeDefinition.fields
get() = when (this) {
is GQLObjectTypeDefinition -> fields
Expand Down
2 changes: 2 additions & 0 deletions libraries/apollo-compiler/api/apollo-compiler.api
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ public final class com/apollographql/apollo/compiler/codegen/ResolverKeyKind : j
public static final field ArgumentDefinition Lcom/apollographql/apollo/compiler/codegen/ResolverKeyKind;
public static final field BuilderFun Lcom/apollographql/apollo/compiler/codegen/ResolverKeyKind;
public static final field BuilderType Lcom/apollographql/apollo/compiler/codegen/ResolverKeyKind;
public static final field Cache Lcom/apollographql/apollo/compiler/codegen/ResolverKeyKind;
public static final field CustomScalarAdapters Lcom/apollographql/apollo/compiler/codegen/ResolverKeyKind;
public static final field Fragment Lcom/apollographql/apollo/compiler/codegen/ResolverKeyKind;
public static final field FragmentSelections Lcom/apollographql/apollo/compiler/codegen/ResolverKeyKind;
Expand All @@ -576,6 +577,7 @@ public abstract interface class com/apollographql/apollo/compiler/codegen/Schema

public abstract interface class com/apollographql/apollo/compiler/codegen/SchemaLayout : com/apollographql/apollo/compiler/codegen/CommonLayout {
public abstract fun assertionsName ()Ljava/lang/String;
public abstract fun cacheName ()Ljava/lang/String;
public abstract fun paginationName ()Ljava/lang/String;
public abstract fun schemaName ()Ljava/lang/String;
public abstract fun schemaPackageName ()Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ internal class LayoutImpl(
return "Pagination"
}

override fun cacheName(): String {
return "Cache"
}

override fun operationName(name: String, capitalizedOperationType: String): String {
return className(name).let {
if (useSemanticNaming) {
Expand Down Expand Up @@ -156,6 +160,7 @@ internal fun SchemaLayout.typeAdapterPackageName() = "${schemaPackageName()}.typ
internal fun SchemaLayout.typeUtilPackageName() = "${schemaPackageName()}.type.util"

internal fun SchemaLayout.paginationPackageName() = "${schemaPackageName()}.pagination"
internal fun SchemaLayout.cachePackageName() = "${schemaPackageName()}.cache"
internal fun SchemaLayout.schemaSubPackageName() = "${schemaPackageName()}.schema"

internal fun SchemaLayout.javaOptionalAdapterClassName() = "OptionalAdapter"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ enum class ResolverKeyKind {
Schema,
CustomScalarAdapters,
Pagination,
Cache,
ArgumentDefinition,
JavaOptionalAdapter,
JavaOptionalAdapters,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ interface SchemaLayout : CommonLayout {
fun schemaName(): String
fun assertionsName(): String
fun paginationName(): String
fun cacheName(): String
}

interface OperationsLayout: CommonLayout {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import com.apollographql.apollo.compiler.codegen.kotlin.operations.OperationBuil
import com.apollographql.apollo.compiler.codegen.kotlin.operations.OperationResponseAdapterBuilder
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.CacheBuilder
import com.apollographql.apollo.compiler.codegen.kotlin.schema.CustomScalarAdaptersBuilder
import com.apollographql.apollo.compiler.codegen.kotlin.schema.EnumAsEnumBuilder
import com.apollographql.apollo.compiler.codegen.kotlin.schema.EnumAsSealedBuilder
Expand Down Expand Up @@ -49,7 +50,6 @@ import com.apollographql.apollo.compiler.generateMethodsKotlin
import com.apollographql.apollo.compiler.ir.DefaultIrSchema
import com.apollographql.apollo.compiler.ir.IrOperations
import com.apollographql.apollo.compiler.ir.IrSchema
import com.apollographql.apollo.compiler.ir.IrTargetObject
import com.apollographql.apollo.compiler.maybeTransform
import com.apollographql.apollo.compiler.operationoutput.OperationOutput
import com.apollographql.apollo.compiler.operationoutput.findOperationId
Expand Down Expand Up @@ -206,6 +206,10 @@ internal object KotlinCodegen {
if (irSchema.connectionTypes.isNotEmpty() && context.resolver.resolve(ResolverKey(ResolverKeyKind.Pagination, "")) == null) {
builders.add(PaginationBuilder(context, irSchema.connectionTypes))
}

if (irSchema.maxAges.isNotEmpty() && context.resolver.resolve(ResolverKey(ResolverKeyKind.Cache, "")) == null) {
builders.add(CacheBuilder(context, irSchema.maxAges))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.apollographql.apollo.compiler.codegen.kotlin.schema

import com.apollographql.apollo.compiler.codegen.ResolverKeyKind
import com.apollographql.apollo.compiler.codegen.cachePackageName
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.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec

internal class CacheBuilder(
private val context: KotlinSchemaContext,
private val maxAges: Map<String, Int>,
) : CgFileBuilder {
private val layout = context.layout
private val packageName = layout.cachePackageName()
private val simpleName = layout.cacheName()

override fun prepare() {
context.resolver.register(ResolverKeyKind.Cache, "", ClassName(packageName, simpleName))
}

override fun build(): CgFile {
return CgFile(
packageName = packageName,
fileName = simpleName,
typeSpecs = listOf(typeSpec())
)
}

private fun typeSpec(): TypeSpec {
return TypeSpec.objectBuilder(simpleName)
.addProperty(maxAgesPropertySpec())
.build()
}

private fun maxAgesPropertySpec(): PropertySpec {
val builder = CodeBlock.builder().apply {
add("mapOf(\n")
indent()
add(
maxAges.map {(k, v) ->
CodeBlock.of("%S to %L", k, v)
}.joinToString(",\n", postfix = ",\n")
)
unindent()
add(")")
}

return PropertySpec.builder("maxAges", KotlinSymbols.Map.parameterizedBy(KotlinSymbols.String, KotlinSymbols.Int))
.initializer(builder.build())
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal class DefaultIrSchema(
val irInterfaces: List<IrInterface>,
val irObjects: List<IrObject>,
val connectionTypes: List<String>,
val maxAges: Map<String, Int>,
) : IrSchema

interface IrSchema
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ internal object IrSchemaBuilder {
irInterfaces = irInterfaces,
irObjects = irObjects,
connectionTypes = schema.connectionTypes.toList(),
maxAges = schema.maxAges,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# maxAge: 0
# Query.book doesn't set a maxAge and it's a root field (default 0).
query GetBookTitle {
book { # 0
cachedTitle # 30
}
}

# maxAge: 60
# Query.cachedBook has a maxAge of 60, and Book.title is a scalar, so it
# inherits maxAge from its parent by default.
query GetCachedBookTitle {
cachedBook { # 60
title # inherits
}
}

# maxAge: 30
# Query.cachedBook has a maxAge of 60, but Book.cachedTitle has
# a maxAge of 30.
query GetCachedBookCachedTitle {
cachedBook { # 60
cachedTitle # 30
}
}

# maxAge: 40
# Query.reader has a maxAge of 40. Reader.Book is set to
# inheritMaxAge from its parent, and Book.title is a scalar
# that inherits maxAge from its parent by default.
query GetReaderBookTitle {
reader { # 40
book { # inherits
title # inherits
}
}
}
Loading

0 comments on commit b0adfa1

Please sign in to comment.