Skip to content

Commit 887a259

Browse files
committed
First failed simple attempt of trying to support generic return types
1 parent f1d0977 commit 887a259

File tree

5 files changed

+45
-34
lines changed

5 files changed

+45
-34
lines changed

src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
145145

146146
val sourceName = if (field.sourceLocation != null && field.sourceLocation.sourceName != null) field.sourceLocation.sourceName else "<unknown>"
147147
val sourceLocation = if (field.sourceLocation != null) "$sourceName:${field.sourceLocation.line}" else "<unknown>"
148-
return "No method${if (scannedProperties) " or field" else ""} found as defined in $sourceLocation with any of the following signatures (with or without one of $allowedLastArgumentTypes as the last argument), in priority order:\n${signatures.joinToString("\n ")}"
148+
return "No method${if (scannedProperties) " or field" else ""} found as defined in schema $sourceLocation with any of the following signatures (with or without one of $allowedLastArgumentTypes as the last argument), in priority order:\n${signatures.joinToString("\n ")}"
149149
}
150150

151151
private fun getMissingMethodSignatures(field: FieldDefinition, search: Search, isBoolean: Boolean, scannedProperties: Boolean): List<String> {

src/main/kotlin/com/coxautodev/graphql/tools/ScannedSchemaObjects.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ internal data class ScannedSchemaObjects(
1818
val unusedDefinitions: Set<TypeDefinition<*>>
1919
)
2020

21-
internal typealias TypeClassDictionary = BiMap<TypeDefinition<*>, Class<*>>
21+
internal typealias TypeClassDictionary = BiMap<TypeDefinition<*>, Class<out Any>>
2222
internal typealias CustomScalarMap = Map<String, GraphQLScalarType>

src/main/kotlin/com/coxautodev/graphql/tools/SchemaClassScanner.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
256256
/**
257257
* Enter a found type into the dictionary if it doesn't exist yet, add a reference pointing back to where it was discovered.
258258
*/
259-
private fun handleFoundType(type: TypeDefinition<*>, clazz: Class<*>?, reference: Reference) {
259+
private fun handleFoundType(type: TypeDefinition<*>, clazz: Class<out Any>?, reference: Reference) {
260260
val realEntry = dictionary.getOrPut(type) { DictionaryEntry() }
261261
var typeWasSet = false
262262

@@ -356,10 +356,10 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
356356

357357
private class DictionaryEntry {
358358
private val references = mutableListOf<Reference>()
359-
var typeClass: Class<*>? = null
359+
var typeClass: Class<out Any>? = null
360360
private set
361361

362-
fun setTypeIfMissing(typeClass: Class<*>): Boolean {
362+
fun setTypeIfMissing(typeClass: Class<out Any>): Boolean {
363363
if (this.typeClass == null) {
364364
this.typeClass = typeClass
365365
return true
Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.coxautodev.graphql.tools
22

33
import graphql.execution.DataFetcherResult
4-
import graphql.language.*
4+
import graphql.language.ListType
5+
import graphql.language.NonNullType
6+
import graphql.language.ScalarTypeDefinition
7+
import graphql.language.TypeDefinition
8+
import graphql.language.TypeName
59
import graphql.schema.idl.ScalarInfo
610
import org.apache.commons.lang3.reflect.TypeUtils
711
import java.lang.reflect.ParameterizedType
@@ -19,7 +23,7 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
1923
private fun error(potentialMatch: PotentialMatch, msg: String) = SchemaClassScannerError("Unable to match type definition (${potentialMatch.graphQLType}) with java type (${potentialMatch.javaType}): $msg")
2024

2125
fun match(potentialMatch: PotentialMatch): Match {
22-
return if(potentialMatch.batched) {
26+
return if (potentialMatch.batched) {
2327
match(stripBatchedType(potentialMatch)) // stripBatchedType sets 'batched' to false
2428
} else {
2529
match(potentialMatch, potentialMatch.graphQLType, potentialMatch.javaType, true)
@@ -30,59 +34,61 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
3034

3135
var realType = potentialMatch.generic.unwrapGenericType(javaType)
3236

33-
if(realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, DataFetcherResult::class.java)) {
34-
if(potentialMatch.location != Location.RETURN_TYPE) {
37+
if (realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, DataFetcherResult::class.java)) {
38+
if (potentialMatch.location != Location.RETURN_TYPE) {
3539
throw error(potentialMatch, "${DataFetcherResult::class.java.name} can only be used as a return type")
3640
}
3741

38-
if(!root) {
42+
if (!root) {
3943
throw error(potentialMatch, "${DataFetcherResult::class.java.name} can only be used at the top level of a return type")
4044
}
4145

4246
realType = potentialMatch.generic.unwrapGenericType(realType.actualTypeArguments.first())
4347

44-
if(realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, DataFetcherResult::class.java)) {
48+
if (realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, DataFetcherResult::class.java)) {
4549
throw error(potentialMatch, "${DataFetcherResult::class.java.name} cannot be nested within itself")
4650
}
4751
}
4852

4953
var optional = false
5054

5155
// Handle jdk8 Optionals
52-
if(realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, Optional::class.java)) {
56+
if (realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, Optional::class.java)) {
5357
optional = true
5458

55-
if(potentialMatch.location == Location.RETURN_TYPE && !root) {
59+
if (potentialMatch.location == Location.RETURN_TYPE && !root) {
5660
throw error(potentialMatch, "${Optional::class.java.name} can only be used at the top level of a return type")
5761
}
5862

5963
realType = potentialMatch.generic.unwrapGenericType(realType.actualTypeArguments.first())
6064

61-
if(realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, Optional::class.java)) {
65+
if (realType is ParameterizedType && potentialMatch.generic.isTypeAssignableFromRawClass(realType, Optional::class.java)) {
6266
throw error(potentialMatch, "${Optional::class.java.name} cannot be nested within itself")
6367
}
6468
}
6569

6670
// Match graphql type to java type.
67-
return when(graphQLType) {
71+
return when (graphQLType) {
6872
is NonNullType -> {
69-
if(optional) {
73+
if (optional) {
7074
throw error(potentialMatch, "graphql type is marked as nonnull but ${Optional::class.java.name} was used")
7175
}
7276
match(potentialMatch, graphQLType.type, realType)
7377
}
7478

7579
is ListType -> {
76-
if(realType is ParameterizedType && isListType(realType, potentialMatch)) {
80+
if (realType is ParameterizedType && isListType(realType, potentialMatch)) {
7781
match(potentialMatch, graphQLType.type, realType.actualTypeArguments.first())
7882
} else {
7983
throw error(potentialMatch, "Java class is not a List or generic type information was lost: $realType")
8084
}
8185
}
8286

8387
is TypeName -> {
84-
val typeDefinition = ScalarInfo.STANDARD_SCALAR_DEFINITIONS[graphQLType.name] ?: definitionsByName[graphQLType.name] ?: throw error(potentialMatch, "No ${TypeDefinition::class.java.simpleName} for type name ${graphQLType.name}")
85-
if(typeDefinition is ScalarTypeDefinition) {
88+
val typeDefinition = ScalarInfo.STANDARD_SCALAR_DEFINITIONS[graphQLType.name]
89+
?: definitionsByName[graphQLType.name]
90+
?: throw error(potentialMatch, "No ${TypeDefinition::class.java.simpleName} for type name ${graphQLType.name}")
91+
if (typeDefinition is ScalarTypeDefinition) {
8692
ScalarMatch(typeDefinition)
8793
} else {
8894
ValidMatch(typeDefinition, requireRawClass(realType), potentialMatch.reference)
@@ -96,30 +102,33 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
96102

97103
private fun isListType(realType: ParameterizedType, potentialMatch: PotentialMatch) = isListType(realType, potentialMatch.generic)
98104

99-
private fun requireRawClass(type: JavaType): Class<*> {
100-
if(type !is Class<*>) {
101-
throw RawClassRequiredForGraphQLMappingException("Type ${TypeUtils.toString(type)} cannot be mapped to a GraphQL type! Since GraphQL-Java deals with erased types at runtime, only non-parameterized classes can represent a GraphQL type. This allows for reverse-lookup by java class in interfaces and union types.")
102-
}
105+
private fun requireRawClass(type: JavaType): Class<out Any> {
106+
// if (type is ParameterizedType) {
107+
// return type.rawType.javaClass
108+
// }
109+
// if (type !is Class<*>) {
110+
// throw RawClassRequiredForGraphQLMappingException("Type ${TypeUtils.toString(type)} cannot be mapped to a GraphQL type! Since GraphQL-Java deals with erased types at runtime, only non-parameterized classes can represent a GraphQL type. This allows for reverse-lookup by java class in interfaces and union types.")
111+
// }
103112

104-
return type
113+
return type.javaClass
105114
}
106115

107116
private fun stripBatchedType(potentialMatch: PotentialMatch): PotentialMatch {
108-
if (potentialMatch.location == Location.PARAMETER_TYPE) {
109-
return potentialMatch.copy(javaType = potentialMatch.javaType, batched = false)
117+
return if (potentialMatch.location == Location.PARAMETER_TYPE) {
118+
potentialMatch.copy(javaType = potentialMatch.javaType, batched = false)
110119
} else {
111120
val realType = potentialMatch.generic.unwrapGenericType(potentialMatch.javaType)
112121
if (realType is ParameterizedType && isListType(realType, potentialMatch)) {
113-
return potentialMatch.copy(javaType = realType.actualTypeArguments.first(), batched = false)
122+
potentialMatch.copy(javaType = realType.actualTypeArguments.first(), batched = false)
114123
} else {
115124
throw error(potentialMatch, "Method was marked as @Batched but ${potentialMatch.location.prettyName} was not a list!")
116125
}
117126
}
118127
}
119128

120129
internal interface Match
121-
internal data class ScalarMatch(val type: ScalarTypeDefinition): Match
122-
internal data class ValidMatch(val type: TypeDefinition<*>, val clazz: Class<*>, val reference: SchemaClassScanner.Reference): Match
130+
internal data class ScalarMatch(val type: ScalarTypeDefinition) : Match
131+
internal data class ValidMatch(val type: TypeDefinition<*>, val clazz: Class<out Any>, val reference: SchemaClassScanner.Reference) : Match
123132
internal enum class Location(val prettyName: String) {
124133
RETURN_TYPE("return type"),
125134
PARAMETER_TYPE("parameter"),
@@ -128,11 +137,12 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
128137
internal data class PotentialMatch(val graphQLType: GraphQLLangType, val javaType: JavaType, val generic: GenericType.RelativeTo, val reference: SchemaClassScanner.Reference, val location: Location, val batched: Boolean) {
129138
companion object {
130139
fun returnValue(graphQLType: GraphQLLangType, javaType: JavaType, generic: GenericType.RelativeTo, reference: SchemaClassScanner.Reference, batched: Boolean) =
131-
PotentialMatch(graphQLType, javaType, generic, reference, Location.RETURN_TYPE, batched)
140+
PotentialMatch(graphQLType, javaType, generic, reference, Location.RETURN_TYPE, batched)
132141

133142
fun parameterType(graphQLType: GraphQLLangType, javaType: JavaType, generic: GenericType.RelativeTo, reference: SchemaClassScanner.Reference, batched: Boolean) =
134-
PotentialMatch(graphQLType, javaType, generic, reference, Location.PARAMETER_TYPE, batched)
143+
PotentialMatch(graphQLType, javaType, generic, reference, Location.PARAMETER_TYPE, batched)
135144
}
136145
}
137-
class RawClassRequiredForGraphQLMappingException(msg: String): RuntimeException(msg)
146+
147+
class RawClassRequiredForGraphQLMappingException(msg: String) : RuntimeException(msg)
138148
}

src/test/groovy/com/coxautodev/graphql/tools/RelayConnectionSpec.groovy

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.coxautodev.graphql.tools
22

33
import graphql.relay.Connection
4+
import graphql.relay.DefaultConnection
45
import graphql.relay.SimpleListConnection
56
import graphql.schema.DataFetchingEnvironment
67
import spock.lang.Specification
@@ -39,8 +40,8 @@ class RelayConnectionSpec extends Specification {
3940
}
4041

4142
static class QueryResolver implements GraphQLQueryResolver {
42-
Connection<User> users(int first, String after, DataFetchingEnvironment env) {
43-
new SimpleListConnection<User>(new ArrayList()).get(env)
43+
DefaultConnection<User> users(int first, String after, DataFetchingEnvironment env) {
44+
new SimpleListConnection<User>(new ArrayList()).get(env) as DefaultConnection<User>
4445
}
4546
}
4647

0 commit comments

Comments
 (0)