-
Notifications
You must be signed in to change notification settings - Fork 655
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pagination: add connectionFields argument to @typePolicy #4265
Changes from 10 commits
e7f4df4
afe608f
719f05c
a64e0b6
f3a95a3
1ee5748
aa70d65
44ce18d
1505f22
6e5f77d
6591785
dd1eee8
51300ab
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,44 @@ | ||
package com.apollographql.apollo3.ast.internal | ||
|
||
import com.apollographql.apollo3.annotations.ApolloInternal | ||
import com.apollographql.apollo3.ast.* | ||
import com.apollographql.apollo3.ast.ConflictResolution | ||
import com.apollographql.apollo3.ast.GQLDefinition | ||
import com.apollographql.apollo3.ast.GQLDirective | ||
import com.apollographql.apollo3.ast.GQLDirectiveDefinition | ||
import com.apollographql.apollo3.ast.GQLEnumTypeDefinition | ||
import com.apollographql.apollo3.ast.GQLField | ||
import com.apollographql.apollo3.ast.GQLInputObjectTypeDefinition | ||
import com.apollographql.apollo3.ast.GQLInterfaceTypeDefinition | ||
import com.apollographql.apollo3.ast.GQLListType | ||
import com.apollographql.apollo3.ast.GQLListValue | ||
import com.apollographql.apollo3.ast.GQLNamed | ||
import com.apollographql.apollo3.ast.GQLNamedType | ||
import com.apollographql.apollo3.ast.GQLNonNullType | ||
import com.apollographql.apollo3.ast.GQLObjectTypeDefinition | ||
import com.apollographql.apollo3.ast.GQLObjectValue | ||
import com.apollographql.apollo3.ast.GQLOperationTypeDefinition | ||
import com.apollographql.apollo3.ast.GQLResult | ||
import com.apollographql.apollo3.ast.GQLScalarTypeDefinition | ||
import com.apollographql.apollo3.ast.GQLSchemaDefinition | ||
import com.apollographql.apollo3.ast.GQLSchemaExtension | ||
import com.apollographql.apollo3.ast.GQLStringValue | ||
import com.apollographql.apollo3.ast.GQLType | ||
import com.apollographql.apollo3.ast.GQLTypeDefinition | ||
import com.apollographql.apollo3.ast.GQLTypeDefinition.Companion.builtInTypes | ||
import com.apollographql.apollo3.ast.GQLTypeSystemExtension | ||
import com.apollographql.apollo3.ast.GQLUnionTypeDefinition | ||
import com.apollographql.apollo3.ast.Issue | ||
import com.apollographql.apollo3.ast.Schema | ||
import com.apollographql.apollo3.ast.Schema.Companion.TYPE_POLICY | ||
import com.apollographql.apollo3.ast.SourceLocation | ||
import com.apollographql.apollo3.ast.apolloDefinitions | ||
import com.apollographql.apollo3.ast.builtinDefinitions | ||
import com.apollographql.apollo3.ast.canHaveKeyFields | ||
import com.apollographql.apollo3.ast.combineDefinitions | ||
import com.apollographql.apollo3.ast.containsError | ||
import com.apollographql.apollo3.ast.linkDefinitions | ||
import com.apollographql.apollo3.ast.parseAsGQLSelections | ||
import com.apollographql.apollo3.ast.transform2 | ||
|
||
internal fun validateSchema(definitions: List<GQLDefinition>, requiresApolloDefinitions: Boolean = false): GQLResult<Schema> { | ||
val issues = mutableListOf<Issue>() | ||
|
@@ -133,6 +168,7 @@ internal fun validateSchema(definitions: List<GQLDefinition>, requiresApolloDefi | |
mergedScope.validateObjects() | ||
|
||
val keyFields = mergedScope.validateAndComputeKeyFields() | ||
val connectionTypes = mergedScope.computeConnectionTypes() | ||
|
||
return if (issues.containsError()) { | ||
/** | ||
|
@@ -142,10 +178,11 @@ internal fun validateSchema(definitions: List<GQLDefinition>, requiresApolloDefi | |
} else { | ||
GQLResult( | ||
Schema( | ||
mergedDefinitions, | ||
keyFields, | ||
foreignNames, | ||
directivesToStrip | ||
definitions = mergedDefinitions, | ||
keyFields = keyFields, | ||
foreignNames = foreignNames, | ||
directivesToStrip = directivesToStrip, | ||
connectionTypes = connectionTypes, | ||
), | ||
issues | ||
) | ||
|
@@ -246,6 +283,7 @@ private fun List<GQLSchemaExtension>.getForeignSchemas( | |
} | ||
|
||
val foreignName = components[components.size - 2] | ||
val version = components[components.size - 1] | ||
|
||
if (prefix == null) { | ||
prefix = foreignName | ||
|
@@ -283,7 +321,7 @@ private fun List<GQLSchemaExtension>.getForeignSchemas( | |
|
||
|
||
if (foreignName == "kotlin_labs") { | ||
val (definitions, renames) = apolloDefinitions().rename(mappings, prefix) | ||
val (definitions, renames) = apolloDefinitions(version).rename(mappings, prefix) | ||
foreignSchemas.add( | ||
ForeignSchema( | ||
name = foreignName, | ||
|
@@ -452,6 +490,9 @@ private fun List<GQLDirective>.toKeyFields(): Set<String> = extractFields("keyFi | |
@ApolloInternal | ||
fun List<GQLDirective>.toEmbeddedFields(): Set<String> = extractFields("embeddedFields") | ||
|
||
@ApolloInternal | ||
fun List<GQLDirective>.toConnectionFields(): Set<String> = extractFields("connectionFields") | ||
|
||
private fun List<GQLDirective>.extractFields(argumentName: String): Set<String> { | ||
if (isEmpty()) { | ||
return emptySet() | ||
|
@@ -481,4 +522,37 @@ internal fun ValidationScope.validateAndComputeKeyFields(): Map<String, Set<Stri | |
keyFields(it, keyFieldsCache) | ||
} | ||
return keyFieldsCache | ||
} | ||
} | ||
|
||
internal fun ValidationScope.computeConnectionTypes(): Set<String> { | ||
val connectionTypes = mutableSetOf<String>() | ||
for (typeDefinition in typeDefinitions.values) { | ||
val connectionFields = typeDefinition.directives.filter { originalDirectiveName(it.name) == TYPE_POLICY }.toConnectionFields() | ||
for (fieldName in connectionFields) { | ||
val field = typeDefinition.fields.firstOrNull { it.name == fieldName } ?: continue | ||
connectionTypes.add(field.type.name) | ||
} | ||
} | ||
return connectionTypes | ||
} | ||
|
||
private val GQLTypeDefinition.directives | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could be on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we're going that road, we could add a |
||
get() = when (this) { | ||
is GQLObjectTypeDefinition -> directives | ||
is GQLInterfaceTypeDefinition -> directives | ||
else -> emptyList() | ||
} | ||
|
||
private val GQLTypeDefinition.fields | ||
get() = when (this) { | ||
is GQLObjectTypeDefinition -> fields | ||
is GQLInterfaceTypeDefinition -> fields | ||
else -> emptyList() | ||
} | ||
|
||
private val GQLType.name: String | ||
get() = when (this) { | ||
is GQLNonNullType -> type.name | ||
is GQLListType -> type.name | ||
is GQLNamedType -> name | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
# The kotlin_labs directives | ||
# You can import them with | ||
# The kotlin_labs v0.1 directives | ||
# You can import them with: | ||
# | ||
# extend schema @link(url: "https://specs.apollo.dev/kotlin_labs/v0.1", import: ["@optional", "@nonnull", "@typePolicy", "@fieldPolicy"]) | ||
# extend schema @link(url: "https://specs.apollo.dev/kotlin_labs/v0.1", import: ["@optional", "@nonnull", "@typePolicy", "@fieldPolicy", "requiresOptIn", "targetName"]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
# | ||
# They are included unconditionally for historical reasons but this will change in a future version | ||
|
||
|
@@ -47,9 +47,4 @@ on FIELD_DEFINITION | |
# This directive is experimental. | ||
directive @targetName(name: String!) | ||
on OBJECT | ||
| INTERFACE | ||
| ENUM | ||
| ENUM_VALUE | ||
| UNION | ||
| SCALAR | ||
| INPUT_OBJECT |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
# The kotlin_labs v0.2 directives | ||
# You can import them with: | ||
# | ||
# extend schema @link(url: "https://specs.apollo.dev/kotlin_labs/v0.2", import: ["@optional", "@nonnull", "@typePolicy", "@fieldPolicy", "requiresOptIn", "targetName"]) | ||
# | ||
# They are included unconditionally for historical reasons but this will change in a future version | ||
|
||
# Marks a field or variable definition as optional or required | ||
# By default Apollo Kotlin generates all variables of nullable types as optional, in compliance with the GraphQL specification, | ||
# 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 | ||
directive @optional(if: Boolean = true) on FIELD | VARIABLE_DEFINITION | ||
|
||
# Marks a field as non-null. The corresponding Kotlin property will be made non-nullable even if the GraphQL type is nullable. | ||
# 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 | ||
# When used on a field from an executable document, `fields` must always be empty | ||
# | ||
# 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 | ||
# and null in the other | ||
directive @nonnull(fields: String! = "") on OBJECT | FIELD | ||
|
||
# Attach extra information to a given type | ||
# `keyFields`: a selection set containing fields used to compute the cache key of an object. Order is important. | ||
# `embeddedFields`: a selection set containing fields that shouldn't create a new cache Record and should be | ||
# embedded in their parent instead. Order is unimportant. | ||
# `connectionFields`: a selection set containing fields that should be treated as Relay Connection fields. Order is unimportant. | ||
directive @typePolicy(keyFields: String! = "", embeddedFields: String! = "", connectionFields: String! = "") on OBJECT | INTERFACE | UNION | ||
|
||
# Attach extra information to a given field | ||
# `keyArgs`: a list of arguments used to compute the cache key of the object this field is pointing to. | ||
# The list is parsed as a selection set: both spaces and comas are valid separators. | ||
# `paginationArgs` (experimental): a list of arguments that vary when requesting different pages. | ||
# These arguments are omitted when computing the cache key of this field. | ||
# The list is parsed as a selection set: both spaces and comas are valid separators. | ||
directive @fieldPolicy(forField: String!, keyArgs: String! = "", paginationArgs: String! = "") repeatable on OBJECT | ||
|
||
""" | ||
Indicates that the given field, argument, input field or enum value requires | ||
giving explicit consent before being used. | ||
""" | ||
directive @requiresOptIn(feature: String!) repeatable | ||
on FIELD_DEFINITION | ||
| ARGUMENT_DEFINITION | ||
| INPUT_FIELD_DEFINITION | ||
| ENUM_VALUE | ||
|
||
# Use the specified name in the generated code instead of the GraphQL name. | ||
# Use this for instance when the name would clash with a reserved keyword or field in the generated code. | ||
# This directive is experimental. | ||
directive @targetName(name: String!) | ||
on OBJECT | ||
| INTERFACE | ||
| ENUM | ||
| ENUM_VALUE | ||
| UNION | ||
| SCALAR | ||
| INPUT_OBJECT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make Deprecated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably make
apolloDefinitions(version: String)
public if we want to keep offering that possibility. I'd be surprised if anyone is relying on this at the moment but it might be useful for some use cases?