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

Anders Kirkeby

12/02/2020, 2:02 PM
Hi. I’m trying to add a conversion from Set<*> to a GraphQLList. I know it is not inherently supported in GQL but the backing data is read-only and has already been implemented using sets. For simplicity lets say we have:
Copy code
data class TestClass(
   val content: Set<String>
)
to remedy this, I’ve added a Hook with the following SchemaGeneratorHooks#willGenerateGrpahQLType method:
Copy code
@ExperimentalStdlibApi
override fun willGenerateGraphQLType(type: KType): GraphQLType? = when {
    type.isSubtypeOf(typeOf<Set<*>?>()) -> {
        type.arguments.first().type?.let { kType ->
            val typeRef = (kType.classifier as KClass<*>)
            GraphQLList.list(GraphQLTypeReference.typeRef(typeRef.simpleName))
        }
    }
    else -> null
}
this works when creating a query that returns TestClass or List<TestClass>. However, when creating a Query that returns a Set<TestClass> it fails as the TestClass reference has not yet been created. From what I could gather, this method should just create the reference to
TestClass::class.simpleName
and the generator should generate the class in “due-time” which should lead to Set<TestClass> resolving as a GQLList<TestClass> in the playground, or am I missing something?
I’ve also had a look at: https://github.com/ExpediaGroup/graphql-kotlin/pull/593, and https://github.com/ExpediaGroup/graphql-kotlin/pull/585, but from what I can see the
SchemaGenerator#generateAdditionalTypes
method is no longer open as indicated by the comments, and PR
d

Dariusz Kuc

12/02/2020, 6:56 PM
gql reference is a pointer to actual gql implementation
so if you create a query that returns that set -> does anything (in your schema) actually reference the actual
TestClass
?
i.e. is your reference pointing to non-existent type?
a

Anders Kirkeby

12/02/2020, 7:39 PM
Ye, scratch those 🤔 Short answer; ye I guess I am referencing a non-existent type as I’m hooking into its creation process and interrupting its creation by creating the List ref instead. So how could I do that in a bottom-up approach, rather than a top-down way?
I guess this is also part of what I’m curious about (from the GraphQLTypeReference):
Copy code
/**
 * A special type to allow a object/interface types to reference itself. It's replaced with the real type
 * object when the schema is built.
 */
I initially assumed that the GraphQLTypeReference.typeRef(typeRef.simpleName) would just “defer” it’s creation time up the last possible moment (ie when the subclass would be part of the schema)
After scratching my head a bit more it is exactly the same as one of the refs i added above: https://github.com/ExpediaGroup/graphql-kotlin/issues/584 . I’m not quite sure how to use the proposed fix either, as the example https://expediagroup.github.io/graphql-kotlin/docs/schema-generator/customizing-schemas/advanced-features - doesn’t work with the methods provided
Copy code
override fun generateAdditionalTypes(types: Set<KType>): Set<GraphQLType> {
        val newTypes = types.toMutableSet().add(MyNewType()::class.createType())
        return super.generateAdditionalTypes(newTypes)
    }
should work but the
SchemaGenerator#generateAdditionalTypes
takes no args
Copy code
/**
     * Generate the GraphQL type for all the `additionalTypes`.
     *
     * If you need to provide more custom additional types that were not picked up from reflection of the schema objects,
     * you can provide more types to be added through [generateSchema].
     *
     * This function loops because while generating the additionalTypes it is possible to create more additional types that need to be processed.
     */
    protected fun generateAdditionalTypes(): Set<GraphQLType> {
        val graphqlTypes = mutableSetOf<GraphQLType>()
        while (this.additionalTypes.isNotEmpty()) {
            val currentlyProcessedTypes = LinkedHashSet(this.additionalTypes)
            this.additionalTypes.clear()
            graphqlTypes.addAll(
                currentlyProcessedTypes.map {
                    GraphQLTypeUtil.unwrapNonNull(generateGraphQLType(this, it.kType, it.inputType))
                }
            )
        }

        return graphqlTypes
    }
I’m sure there’s something simple that I’m missing, but I cannot for the life of me find a way to make the resolver just override Collections with List which seems like the simplest thing 😛
d

Dariusz Kuc

12/02/2020, 9:15 PM
you can pass
additionalTypes
/`additionalInputTypes` as arguments to schema generation process
looks like the docs are outdated
a

Anders Kirkeby

12/03/2020, 7:29 AM
That would still require me to add the TestClass manually though, right? 🤔 Cause I won’t have access to the subtypes of the query in the scope of the generateSchema method
d

Dariusz Kuc

12/03/2020, 12:49 PM
Yep. Add the test class to the list of additional types and then use your hook to convert set to list of references
*that would be the "simplest" approach - alternatively you could build underlying type in the hook (instead of just a reference)
a

Anders Kirkeby

12/03/2020, 1:42 PM
Right, that makes sense! I guess what I was hoping for was to just bypass the Set<*> part and “replace” it with List<*>, and lett the default schemagenerator resolve the T/* part. But that works! Maybe an model integration layer would be a prettier way of exposing the underlying data 🤔 Thanks @Dariusz Kuc!
d

Dariusz Kuc

12/03/2020, 1:43 PM
Since you are serializing it to a list anyway
You could also just do .toList() on it and just expose a list ;)
a

Anders Kirkeby

12/03/2020, 1:45 PM
Ye, for my example there that would be the definite answer (and way cleaner). Was more if, say the TestClass contained a set of a different class, which whould require us to “remake” the TestClass -> TestClassQL containing a list of the same data
eg
Copy code
data class TestClass(val content: Set<OtherTestClass>)
which would require
Copy code
data class TestClassQL(val content: List<OtherTestClass>)
👍 1
d

Dariusz Kuc

12/03/2020, 1:48 PM
You could still convert the raw class with set to a gql model with list but that is also extra logic
a

Anders Kirkeby

12/03/2020, 1:50 PM
Indeed! It would need to be resolved at one place or another so I guess there’s no “magic” way of doing it 🙂
d

Dariusz Kuc

12/03/2020, 1:51 PM
Pretty much
Since its going to be serialized to json array you will loose any set properties anyway so I'd just expose it as a list to keep it consistent
a

Anders Kirkeby

12/03/2020, 1:54 PM
Yep! My initial thought was that since this was read-only converting it from set -> list would stil retain the collection properties. I agree that with mutations this would be a different story as you would potentially loose information on the way back in. so rather than have a
listOf().unique()
we would use setOf() instead, and just expose it as a list of unique values
d

Dariusz Kuc

12/03/2020, 1:55 PM
if it needs to be unique i’d keep it internally as set and do all the processing on it as set but expose it as a read only list
a

Anders Kirkeby

12/03/2020, 1:57 PM
Yep, I agree! I was initially hoping to be able to hook into the schema generator to “automagically” convert from set -> list on the way out, simply to save the need of having multiple models representing the underlying data.
But it might be cleaner overall to just have it expose json-friendly models
👍 1
2 Views