Skip to content

Commit 6a30066

Browse files
authored
Exclude auto-imported cache related directives when the Apollo cache plugin is present (#6766)
* Exclude auto-imported cache related directives when the Apollo cache plugin is present * Don't break SchemaValidationOptions and check cache plugin version * Minor and patch can't be negative * Bogus comment * Add kotlin_labs v0.3-noCache definitions
1 parent a27e207 commit 6a30066

File tree

4 files changed

+116
-18
lines changed

4 files changed

+116
-18
lines changed

libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/gqldocument.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.apollographql.apollo.ast.internal.builtinsDefinitionsStr
88
import com.apollographql.apollo.ast.internal.compilerOptions_0_0
99
import com.apollographql.apollo.ast.internal.compilerOptions_0_1_additions
1010
import com.apollographql.apollo.ast.internal.kotlinLabsDefinitions_0_3
11+
import com.apollographql.apollo.ast.internal.kotlinLabsDefinitions_0_3_no_cache
1112
import com.apollographql.apollo.ast.internal.kotlinLabsDefinitions_0_4
1213
import com.apollographql.apollo.ast.internal.linkDefinitionsStr
1314
import com.apollographql.apollo.ast.internal.nullabilityDefinitionsStr
@@ -127,6 +128,8 @@ fun kotlinLabsDefinitions(version: String): List<GQLDefinition> {
127128
return definitionsFromString(when (version) {
128129
// v0.3 has no behavior change over v0.2, so both versions map to the same definitions
129130
"v0.2", "v0.3" -> kotlinLabsDefinitions_0_3
131+
// v0.3-noCache is the same as v0.3 without `@typePolicy` and `@fieldPolicy`
132+
"v0.3-noCache" -> kotlinLabsDefinitions_0_3_no_cache
130133
// v0.4 doesn't have `@nonnull`
131134
"v0.4" -> kotlinLabsDefinitions_0_4
132135
// v0.5 adds `@map` and `@mapTo`
@@ -138,6 +141,8 @@ fun kotlinLabsDefinitions(version: String): List<GQLDefinition> {
138141
}
139142

140143
internal val autoLinkedKotlinLabsForeignSchema = ForeignSchema("kotlin_labs", "v0.3", kotlinLabsDefinitions("v0.3"), listOf("optional", "nonnull"))
144+
internal val autoLinkedKotlinLabsForeignSchemaNoCache =
145+
ForeignSchema("kotlin_labs", "v0.3-noCache", kotlinLabsDefinitions("v0.3-noCache"), listOf("optional", "nonnull"))
141146

142147
/**
143148
* The foreign schemas supported by Apollo Kotlin.

libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/SchemaValidationScope.kt

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import com.apollographql.apollo.ast.Schema
3838
import com.apollographql.apollo.ast.Schema.Companion.TYPE_POLICY
3939
import com.apollographql.apollo.ast.SourceLocation
4040
import com.apollographql.apollo.ast.autoLinkedKotlinLabsForeignSchema
41+
import com.apollographql.apollo.ast.autoLinkedKotlinLabsForeignSchemaNoCache
4142
import com.apollographql.apollo.ast.builtinDefinitions
4243
import com.apollographql.apollo.ast.canHaveKeyFields
4344
import com.apollographql.apollo.ast.findOneOf
@@ -49,14 +50,25 @@ import com.apollographql.apollo.ast.transform2
4950
import com.apollographql.apollo.ast.withBuiltinDefinitions
5051

5152
/**
52-
* @param addKotlinLabsDefinitions automatically import all the kotlin_labs definitions, even if no `@link` is present
53+
* @param addKotlinLabsDefinitions automatically import the kotlin_labs definitions, even if no `@link` is present. If [excludeCacheDirectives] is `true`, cache related directives are excluded.
5354
* @param foreignSchemas a list of known [ForeignSchema] that may or may not be imported depending on the `@link` directives
55+
* @param excludeCacheDirectives whether to exclude cache related directives when auto-importing the kotlin_labs definitions. Has no effect if [addKotlinLabsDefinitions] is `false`.
5456
*/
5557
@ApolloExperimental
5658
class SchemaValidationOptions(
5759
val addKotlinLabsDefinitions: Boolean,
5860
val foreignSchemas: List<ForeignSchema>,
59-
)
61+
val excludeCacheDirectives: Boolean,
62+
) {
63+
constructor(
64+
addKotlinLabsDefinitions: Boolean,
65+
foreignSchemas: List<ForeignSchema>,
66+
) : this(
67+
addKotlinLabsDefinitions = addKotlinLabsDefinitions,
68+
foreignSchemas = foreignSchemas,
69+
excludeCacheDirectives = false,
70+
)
71+
}
6072

6173
private fun ForeignSchema.asNonPrefixedImport(): LinkedSchema {
6274
return LinkedSchema(this, definitions, definitions.map { (it as GQLNamed).definitionName() }.associateBy { it }, null)
@@ -81,7 +93,13 @@ internal fun validateSchema(definitions: List<GQLDefinition>, options: SchemaVal
8193
val linkedSchemas = definitions.filterIsInstance<GQLSchemaExtension>().getLinkedSchemas(issues, options.foreignSchemas).toMutableList()
8294

8395
if (options.addKotlinLabsDefinitions && linkedSchemas.none { it.foreignSchema.name == "kotlin_labs" }) {
84-
linkedSchemas.add(autoLinkedKotlinLabsForeignSchema.asNonPrefixedImport())
96+
linkedSchemas.add(
97+
if (options.excludeCacheDirectives) {
98+
autoLinkedKotlinLabsForeignSchemaNoCache
99+
} else {
100+
autoLinkedKotlinLabsForeignSchema
101+
}.asNonPrefixedImport()
102+
)
85103
}
86104

87105
val typeDefinitions = mutableMapOf<String, GQLTypeDefinition>()
@@ -213,7 +231,7 @@ internal fun validateSchema(definitions: List<GQLDefinition>, options: SchemaVal
213231
importedDirectiveDefinitions.forEach { entry ->
214232
val existing = directiveDefinitions.get(entry.key)
215233
if (existing != null) {
216-
if (entry.value.linkedSchema.foreignSchema == autoLinkedKotlinLabsForeignSchema) {
234+
if (entry.value.linkedSchema.foreignSchema == autoLinkedKotlinLabsForeignSchema || entry.value.linkedSchema.foreignSchema == autoLinkedKotlinLabsForeignSchemaNoCache) {
217235
/*
218236
* This may be an auto-linked definition.
219237
* For compatibility reasons, it takes precedence over the user-provided ones.

libraries/apollo-ast/src/commonMain/kotlin/com/apollographql/apollo/ast/internal/definitions.kt

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,56 @@ internal val kotlinLabsDefinitions_0_3 = """
132132
| INPUT_OBJECT
133133
""".trimIndent()
134134

135+
/**
136+
* This is the same as `kotlinLabsDefinitions_0_3` without `@typePolicy` and `@fieldPolicy`.
137+
*/
138+
internal val kotlinLabsDefinitions_0_3_no_cache = """
139+
""${'"'}
140+
Marks a field or variable definition as optional or required
141+
By default Apollo Kotlin generates all variables of nullable types as optional, in compliance with the GraphQL specification,
142+
but this can be configured with this directive, because if the variable was added in the first place, it's usually to pass a value
143+
Since: 3.0.0
144+
""${'"'}
145+
directive @optional(if: Boolean = true) on FIELD | VARIABLE_DEFINITION
146+
147+
""${'"'}
148+
Marks a field as non-null. The corresponding Kotlin property will be made non-nullable even if the GraphQL type is nullable.
149+
When used on an object definition in a schema document, `fields` must be non-empty and contain a selection set of fields that should be non-null
150+
When used on a field from an executable document, `fields` must always be empty
151+
152+
Setting the directive at the schema level is usually easier as there is little reason that a field would be non-null in one place
153+
and null in the other
154+
Since: 3.0.0
155+
""${'"'}
156+
directive @nonnull(fields: String! = "") on OBJECT | FIELD
157+
158+
""${'"'}
159+
Indicates that the given field, argument, input field or enum value requires
160+
giving explicit consent before being used.
161+
Since: 3.3.1
162+
""${'"'}
163+
directive @requiresOptIn(feature: String!) repeatable
164+
on FIELD_DEFINITION
165+
| ARGUMENT_DEFINITION
166+
| INPUT_FIELD_DEFINITION
167+
| ENUM_VALUE
168+
169+
""${'"'}
170+
Use the specified name in the generated code instead of the GraphQL name.
171+
Use this for instance when the name would clash with a reserved keyword or field in the generated code.
172+
This directive is experimental.
173+
Since: 3.3.1
174+
""${'"'}
175+
directive @targetName(name: String!)
176+
on OBJECT
177+
| INTERFACE
178+
| ENUM
179+
| ENUM_VALUE
180+
| UNION
181+
| SCALAR
182+
| INPUT_OBJECT
183+
""".trimIndent()
184+
135185
internal val kotlinLabsDefinitions_0_4 = """
136186
""${'"'}
137187
Marks a field or variable definition as optional or required
@@ -588,4 +638,4 @@ directive @nonnull(fields: String! = "") on OBJECT | FIELD
588638

589639
internal val disableErrorPropagationStr = """
590640
directive @experimental_disableErrorPropagation on QUERY | MUTATION | SUBSCRIPTION
591-
""".trimIndent()
641+
""".trimIndent()

libraries/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/ApolloCompiler.kt

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,33 @@ object ApolloCompiler {
5959
fun error(message: String)
6060
}
6161

62+
private val cacheCompilerPluginVersion: String? = try {
63+
Class.forName("com.apollographql.cache.apollocompilerplugin.VersionKt")
64+
.getDeclaredField("VERSION")
65+
.get(null) as String
66+
} catch (_: Exception) {
67+
null
68+
}
69+
70+
/**
71+
* The Cache plugin starts providing the cache directives in v1.0.0-alpha.7
72+
*/
73+
private fun String.hasCacheDirectives(): Boolean {
74+
val parts = this.split('.', '-')
75+
val major = parts[0].toInt()
76+
if (major < 1) return false
77+
if (major > 1) return true
78+
val minor = parts[1].toInt()
79+
if (minor > 0) return true
80+
val patch = parts[2].toInt()
81+
if (patch > 0) return true
82+
if (parts.size <= 3) return true
83+
val preRelease = parts[3]
84+
if (preRelease != "alpha") return true
85+
val alphaVersion = parts[4].toInt()
86+
return alphaVersion >= 7
87+
}
88+
6289
fun buildCodegenSchema(
6390
schemaFiles: List<InputFile>,
6491
logger: Logger?,
@@ -146,7 +173,11 @@ object ApolloCompiler {
146173
* TODO: switch to false
147174
*/
148175
addKotlinLabsDefinitions = true,
149-
builtinForeignSchemas() + foreignSchemas
176+
foreignSchemas = builtinForeignSchemas() + foreignSchemas,
177+
/**
178+
* If the cache compiler plugin is present and provides the cache directives, don't automatically import the cache related directives to avoid a conflict
179+
*/
180+
excludeCacheDirectives = cacheCompilerPluginVersion?.hasCacheDirectives() == true,
150181
)
151182
)
152183

@@ -241,18 +272,12 @@ object ApolloCompiler {
241272
* If we detect that the cache compiler plugin is present, we skip adding the keyfields because it will do it.
242273
* TODO: deprecate `addTypename`
243274
*/
244-
val hasCacheCompilerPlugin = try {
245-
Class.forName("com.apollographql.cache.apollocompilerplugin.internal.ApolloCacheCompilerPlugin")
246-
true
247-
} catch (_: ClassNotFoundException) {
248-
false
249-
}
250-
251-
var document = ApolloExecutableDocumentTransform(options.addTypename ?: defaultAddTypename, !hasCacheCompilerPlugin).transform(
252-
schema = schema,
253-
document = GQLDocument(userDefinitions, sourceLocation = null),
254-
upstreamFragmentDefinitions
255-
)
275+
var document =
276+
ApolloExecutableDocumentTransform(options.addTypename ?: defaultAddTypename, cacheCompilerPluginVersion == null).transform(
277+
schema = schema,
278+
document = GQLDocument(userDefinitions, sourceLocation = null),
279+
upstreamFragmentDefinitions
280+
)
256281

257282
if (documentTransform != null) {
258283
document = documentTransform.transform(schema, document, upstreamFragmentDefinitions)

0 commit comments

Comments
 (0)