Skip to content
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

[apollo-ksp] Initial support for interfaces #5351

Merged
merged 2 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ class ApolloProcessor(

private fun generateMainResolver(): List<KSAnnotated> {

val validationScope = ValidationScope(objectMapping, scalarMapping, schema, codegenMetadata)
val validationScope = ValidationScope(objectMapping, scalarMapping, schema, codegenMetadata, logger)

check (objectMapping.isNotEmpty()) {
"No @ApolloObject found. If this error comes from a compilation where you don't want to generate code, use `ksp.allow.all.target.configuration=false`"
Expand Down Expand Up @@ -380,7 +380,7 @@ private fun KSValueParameter.toIrTargetArgument(
val argumentDefinition = fieldDefinition.arguments.firstOrNull { it.name == name }

if (argumentDefinition == null) {
error("No argument found for '$objectName.${fieldDefinition.name}($name) at $location")
error("No GraphQL argument found for Kotlin argument '$objectName.${fieldDefinition.name}($name)' at $location\nUse @GraphQLName if the GraphQLName is different from the Kotlin name ")
}

if (this.hasDefault) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.apollographql.apollo3.compiler.ir.IrOptionalType
import com.apollographql.apollo3.compiler.ir.IrScalarType
import com.apollographql.apollo3.compiler.ir.IrType
import com.apollographql.apollo3.compiler.resolveSchemaType
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.symbol.KSTypeReference
import com.google.devtools.ksp.symbol.Nullability

Expand All @@ -34,8 +35,25 @@ internal class ValidationScope(
private val objectMapping: Map<String, ObjectInfo>,
private val scalarMapping: Map<String, ScalarInfo>,
private val schema: Schema,
private val codegenMetadata: CodegenMetadata
private val codegenMetadata: CodegenMetadata,
private val logger: KSPLogger,
) {
private val possibleTypes = mutableMapOf<IrClassName, MutableSet<String>>()

init {
objectMapping.forEach { entry ->
val superTypes = entry.value.classDeclaration.superTypes.map { it.resolve().declaration.acClassName() }

superTypes.forEach {
logger.warn("${entry.value.className} extends $it")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be a warning?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤦 shouldn't be there

possibleTypes.getOrPut(it, { mutableSetOf() }).add(entry.key)
}

// also add an entry for the object itself
possibleTypes[entry.value.className] = mutableSetOf(entry.key)
}
}

fun validateAndCoerce(ksTypeReference: KSTypeReference, expectedType: GQLType, allowCovariant: Boolean): IrType {
val ksType = ksTypeReference.resolve()
val className = ksType.declaration.acClassName()
Expand Down Expand Up @@ -73,7 +91,8 @@ internal class ValidationScope(
}

is GQLInputObjectTypeDefinition -> {
val expectedFQDN = codegenMetadata.resolveSchemaType(typeDefinition.name)?.toIrClassName() ?: error("Cannot resolve input ${typeDefinition.name}")
val expectedFQDN = codegenMetadata.resolveSchemaType(typeDefinition.name)?.toIrClassName()
?: error("Cannot resolve input ${typeDefinition.name}")
if (className != expectedFQDN) {
throw IncompatibleType("Input object type '${typeDefinition.name}' is mapped to '${expectedFQDN} but '${className.asString()} was found at ${ksTypeReference.location}")
}
Expand All @@ -82,7 +101,8 @@ internal class ValidationScope(
}

is GQLEnumTypeDefinition -> {
val expectedFQDN = codegenMetadata.resolveSchemaType(typeDefinition.name)?.toIrClassName() ?: error("Cannot resolve enum ${typeDefinition.name}")
val expectedFQDN = codegenMetadata.resolveSchemaType(typeDefinition.name)?.toIrClassName()
?: error("Cannot resolve enum ${typeDefinition.name}")
if (className != expectedFQDN) {
throw IncompatibleType("Enum type '${typeDefinition.name}' is mapped to '${expectedFQDN} but '${className.asString()} was found at ${ksTypeReference.location}")
}
Expand All @@ -94,14 +114,23 @@ internal class ValidationScope(
/**
* Because of interfaces we do the lookup the other way around. Contrary to scalars, there cannot be multiple objects mapped to the same target
*/
val objectInfoEntry =
objectMapping.entries.firstOrNull { it.value.className.asString() == className.asString() }

if (objectInfoEntry == null) {
throw IncompatibleType("Expected a composite type '${typeDefinition.name}' but no object found. Did you forget a @ApolloObject? at ${ksTypeReference.location}")
val schemaPossibleTypes = schema.possibleTypes(typeDefinition.name)
val possibleTypes: Set<String>? = possibleTypes[className]
if (possibleTypes == null) {
if (typeDefinition is GQLObjectTypeDefinition) {
error("Expected a Kotlin object for type '${typeDefinition.name}' but none found. Did you forget a @ApolloObject? at ${ksTypeReference.location}")
} else {
error("Expected that Kotlin '$className' is an object or interface for type '${typeDefinition.name}' but none found.")
}
}
if (!schema.possibleTypes(typeDefinition.name).contains(objectInfoEntry.key)) {
throw IncompatibleType("Expected type '${typeDefinition.name}' but '${objectInfoEntry.key}' is not a subtype at ${ksTypeReference.location}")

if (!schemaPossibleTypes.containsAll(possibleTypes)) {
val wrong = possibleTypes - schemaPossibleTypes
if (typeDefinition is GQLObjectTypeDefinition) {
throw IncompatibleType("Expected type '${typeDefinition.name}' but got '${wrong}' instead at ${ksTypeReference.location}")
} else {
throw IncompatibleType("Expected type '${typeDefinition.name}' but '${wrong}' are not subtype(s) at ${ksTypeReference.location}")
}
}

IrObjectType(typeDefinition.name)
Expand Down Expand Up @@ -141,7 +170,7 @@ internal fun ValidationScope.validateAndCoerceArgumentType(
targetName: String,
typeReference: KSTypeReference,
gqlType: GQLType,
hasDefault: Boolean
hasDefault: Boolean,
): IrType {
val type = typeReference.resolve()
val className = type.declaration.acClassName()
Expand Down
Loading