https://kotlinlang.org logo
#graphql-kotlin
Title
# graphql-kotlin
j

Jabez Magomere

03/23/2021, 11:07 AM
I’m trying to return a class that extends graphql-relay Connection class in a Query function as shown in the snippet below.
Copy code
import com.androidmaestro.users.data.repository.UserRepository
import com.androidmaestro.users.domain.entity.UserEntity
import com.expediagroup.graphql.types.operations.Query
import graphql.relay.*


class UsersQuery(private val repository: UserRepository) : Query{
    suspend fun users(first:Int, cursor : String?) : UsersConnection = repository.getAll(first, cursor)
}

class UsersConnection(users:List<Edge<UserEntity>>, userPageInfo : DefaultPageInfo) : DefaultConnection<UserEntity>(users, userPageInfo)
However the schema generation fails with the error shown below.
Copy code
Caused by: java.lang.IllegalArgumentException: Class declares 1 type parameters, but 0 were provided.
        at kotlin.reflect.full.KClassifiers.createType(KClassifiers.kt:53)
        at kotlin.reflect.full.KClassifiers.createType$default(KClassifiers.kt:45)
        at com.expediagroup.graphql.generator.internal.types.GenerateObjectKt.generateObject(generateObject.kt:46)
        at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.getGraphQLType(generateGraphQLType.kt:74)
        at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.access$getGraphQLType(generateGraphQLType.kt:1)
        at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt$objectFromReflection$1.invoke(generateGraphQLType.kt:63)
        at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt$objectFromReflection$1.invoke(generateGraphQLType.kt)
        at com.expediagroup.graphql.generator.internal.state.TypesCache.buildIfNotUnderConstruction$graphql_kotlin_schema_generator(TypesCache.kt:108)
        at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.objectFromReflection(generateGraphQLType.kt:62)
        at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.generateGraphQLType(generateGraphQLType.kt:40)
        at com.expediagroup.graphql.generator.internal.types.GenerateGraphQLTypeKt.generateGraphQLType$default(generateGraphQLType.kt:36)
        at com.expediagroup.graphql.generator.internal.types.GenerateFunctionKt.generateFunction(generateFunction.kt:54)
        at com.expediagroup.graphql.generator.internal.types.GenerateFunctionKt.generateFunction$default(generateFunction.kt:33)
        at com.expediagroup.graphql.generator.internal.types.GenerateQueryKt.generateQueries(generateQuery.kt:43)
        at com.expediagroup.graphql.generator.SchemaGenerator.generateSchema(SchemaGenerator.kt:80)
        at com.expediagroup.graphql.generator.SchemaGenerator.generateSchema$default(SchemaGenerator.kt:73)
        at com.expediagroup.graphql.generator.ToSchemaKt.toSchema(toSchema.kt:41)
        at com.expediagroup.graphql.generator.ToSchemaKt.toSchema$default(toSchema.kt:37)
        at com.androidmaestro.graphql.Schema.<clinit>(Schema.kt:51)
        ... 76 common frames omitted
2021-03-23 14:01:21.087 [eventLoopGroupProxy-4-1] ERROR Application - Unhandled exception
java.lang.NoClassDefFoundError: Could not initialize class com.androidmaestro.graphql.Schema
s

Shane Myrick

03/23/2021, 4:33 PM
It is the same issue as this: https://kotlinlang.slack.com/archives/CQLNT7B29/p1616235196005400 With the changes in 4.0.0 to support default arguments, nullable arguments must have a default as the client may not pass in a value. You can think of it in terms of calling the Kotlin function too. What do we do if someone doesn’t provide a value for a GraphQL optional arg?
j

Jabez Magomere

03/23/2021, 5:51 PM
@Shane Myrick thank you for the detailed explanation, i’ve made a change to the nullable query parameter and i have assigned it a default null value, however i still get the same exception, the return type of the query is a class with a generic superclass DefaultConnection<T> from graphql.relay, might the error be related to the lack of generic support?
Copy code
import com.androidmaestro.users.data.repository.UserRepository
import com.androidmaestro.users.domain.entity.UserEntity
import com.expediagroup.graphql.types.operations.Query
import graphql.relay.*


class UsersQuery(private val repository: UserRepository) : Query {
    suspend fun users(first: Int, cursor : String? = null): UsersConnection = repository.getAll(first, cursor)
}

class UsersConnection(users: List<Edge<UserEntity>>, userPageInfo: DefaultPageInfo) :
    DefaultConnection<UserEntity>(users, userPageInfo)
DefaultConnection snippet
Copy code
/**
 * A default implementation of {@link graphql.relay.Connection}
 */
@PublicApi
public class DefaultConnection<T> implements Connection<T> {
 /**
}
s

Shane Myrick

03/23/2021, 5:56 PM
Yep, we cannot support generics as GraphQL as no concept of generics in the type system either. You could create a custom scalar to be some “generic” object but then you are loosing the benefits of actually having a strongly typed API
j

Jabez Magomere

03/23/2021, 6:02 PM
True, thank you for the much needed help, any ideas or samples on implementing cursor based pagination with graphql-kotlin? I initially thought of using the relay spec, the classes already exist in the graphql-java package, but since generics are not supported i have to find a cleaner alternative to custom scalars.
s

Shane Myrick

03/23/2021, 7:07 PM
If you are implementing these classes, we can still support the
Connection
wrappers: https://www.graphql-java-kickstart.com/tools/relay/ You just need to make sure your schema actually defines a type and not a
Connection<T>
The library just needs to know how to unwrap a
Connection
, and you can do that with the schema hooks. You just need to implement the
willResolveMonad
hooks similar to how you unwrap a custom async class https://expediagroup.github.io/graphql-kotlin/docs/schema-generator/execution/async-models
You could use that graphql-java
connectionType
to return from the
willGenerateGraphQLType
hook
Copy code
fun willGenerateGraphQLType(type: KType): GraphQLType? {
  if (type.isMyConnectionType()) {
    return relay.connectionType(.....)
  } else {
    null
  }
}
The
willResolveMonad
converts a
KType
to a
KType
that will then be used by
willGenerateGraphQLType
. The end result is that you still want to have some standard GraphQL type that matches the relay spec.
f

Filip Lastic

05/04/2022, 9:51 AM
Okay now I understand more. From what you said, maybe it would better to create
...Connection
entity for each object and then I won't have to modify hook. Because
relay.connectionType(.....)
needs another
GraphQLObjectType
and
GraphQLFieldDefinition
fields. I am not sure if it is good idea to generate all these types in my hook
120 Views