11package graphql.kickstart.tools
22
33import graphql.execution.DataFetcherResult
4+ import graphql.kickstart.tools.SchemaParserOptions.GenericWrapper
45import graphql.kickstart.tools.util.GraphQLLangType
56import graphql.kickstart.tools.util.JavaType
67import graphql.kickstart.tools.util.isSubtypeOf
@@ -15,79 +16,22 @@ import java.util.*
1516 */
1617internal class TypeClassMatcher (private val definitionsByName : Map <String , TypeDefinition <* >>) {
1718
18- private fun error (potentialMatch : PotentialMatch , msg : String ) = SchemaClassScannerError (" Unable to match type definition (${TypeUtil .simplePrint(potentialMatch.graphQLType)} ) for reference ${potentialMatch.reference} with java type (${potentialMatch.javaType} ): $msg " )
19-
2019 fun match (potentialMatch : PotentialMatch ): Match {
21- return match(potentialMatch, potentialMatch.graphQLType, potentialMatch.javaType, true )
22- }
23-
24- private fun match (potentialMatch : PotentialMatch , graphQLType : GraphQLLangType , javaType : JavaType , root : Boolean = false): Match {
25-
26- var realType = potentialMatch.typeResolver.resolve(javaType)
27-
28- if (realType is ParameterizedType && realType.isSubtypeOf(DataFetcherResult ::class .java)) {
29- if (potentialMatch.location != Location .RETURN_TYPE ) {
30- throw error(potentialMatch, " ${DataFetcherResult ::class .java.name} can only be used as a return type" )
31- }
32-
33- realType = potentialMatch.typeResolver.resolve(realType.actualTypeArguments.first())
34-
35- if (realType is ParameterizedType && realType.isSubtypeOf(DataFetcherResult ::class .java)) {
36- throw error(potentialMatch, " ${DataFetcherResult ::class .java.name} cannot be nested within itself" )
37- }
38- }
39-
40- var optional = false
41-
42- // Handle jdk8 Optionals
43- if (realType is ParameterizedType && realType.isSubtypeOf(Optional ::class .java)) {
44- optional = true
45-
46- if (potentialMatch.location == Location .RETURN_TYPE && ! root) {
47- throw error(potentialMatch, " ${Optional ::class .java.name} can only be used at the top level of a return type" )
48- }
49-
50- realType = potentialMatch.typeResolver.resolve(realType.actualTypeArguments.first())
51-
52- if (realType is ParameterizedType && realType.isSubtypeOf(Optional ::class .java)) {
53- throw error(potentialMatch, " ${Optional ::class .java.name} cannot be nested within itself" )
54- }
55- }
56-
57- // Match graphql type to java type.
58- return when (graphQLType) {
59- is NonNullType -> {
60- if (optional) {
61- throw error(potentialMatch, " graphql type is marked as nonnull but ${Optional ::class .java.name} was used" )
62- }
63- match(potentialMatch, graphQLType.type, realType)
64- }
65-
66- is ListType -> when {
67- realType is ParameterizedType && realType.isSubtypeOf(Iterable ::class .java) ->
68- match(potentialMatch, graphQLType.type, realType.actualTypeArguments.first())
69- realType is Class <* > && realType.isArray ->
70- match(potentialMatch, graphQLType.type, realType.componentType)
71- // e.g. a property map value: the element type is unknown, so match it as Object too
72- realType == Any ::class .java ->
73- match(potentialMatch, graphQLType.type, realType)
74- else ->
75- throw error(potentialMatch, " Java class is not a List or generic type information was lost: $realType " )
76- }
77-
20+ val unwrapping = potentialMatch.unwrapping
21+ return when (val graphQLType = unwrapping.graphQLType) {
7822 is TypeName -> {
7923 val typeDefinition = ScalarInfo .GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS [graphQLType.name]
8024 ? : definitionsByName[graphQLType.name]
81- ? : throw error(potentialMatch, " No ${TypeDefinition ::class .java.simpleName} for type name ${graphQLType.name} " )
25+ ? : throw potentialMatch. error(" No ${TypeDefinition ::class .java.simpleName} for type name ${graphQLType.name} " )
8226 if (typeDefinition is ScalarTypeDefinition ) {
8327 ScalarMatch (typeDefinition)
8428 } else {
85- ValidMatch (typeDefinition, realType , potentialMatch.reference)
29+ ValidMatch (typeDefinition, unwrapping.javaType , potentialMatch.reference)
8630 }
8731 }
8832
89- is TypeDefinition <* > -> ValidMatch (graphQLType, realType , potentialMatch.reference)
90- else -> throw error(potentialMatch, " Unknown type: ${realType .javaClass.name} " )
33+ is TypeDefinition <* > -> ValidMatch (graphQLType, unwrapping.javaType , potentialMatch.reference)
34+ else -> throw potentialMatch. error(" Unknown type: ${unwrapping.javaType .javaClass.name} " )
9135 }
9236 }
9337
@@ -109,6 +53,72 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
10953 val reference : ScanDictionary .Reference ,
11054 val location : Location
11155 ) {
56+ val unwrapping: Unwrapping by lazy { unwrap(graphQLType, javaType, root = true , mutableListOf ()) }
57+
58+ fun error (msg : String ) = SchemaClassScannerError (" Unable to match type definition (${TypeUtil .simplePrint(graphQLType)} ) for reference $reference with java type ($javaType ): $msg " )
59+
60+ private fun unwrap (graphQLType : GraphQLLangType , javaType : JavaType , root : Boolean , unwrapped : MutableList <Unwrapped >): Unwrapping {
61+ val onUnwrap = { wrapper: GenericWrapper -> unwrapped + = Unwrapped .Wrapper (wrapper) }
62+ var realType = typeResolver.resolve(javaType, onUnwrap)
63+
64+ if (realType is ParameterizedType && realType.isSubtypeOf(DataFetcherResult ::class .java)) {
65+ if (location != Location .RETURN_TYPE ) {
66+ throw error(" ${DataFetcherResult ::class .java.name} can only be used as a return type" )
67+ }
68+
69+ unwrapped + = Unwrapped .Data
70+ realType = typeResolver.resolve(realType.actualTypeArguments.first(), onUnwrap)
71+
72+ if (realType is ParameterizedType && realType.isSubtypeOf(DataFetcherResult ::class .java)) {
73+ throw error(" ${DataFetcherResult ::class .java.name} cannot be nested within itself" )
74+ }
75+ }
76+
77+ var optional = false
78+
79+ // Handle jdk8 Optionals
80+ if (realType is ParameterizedType && realType.isSubtypeOf(Optional ::class .java)) {
81+ optional = true
82+
83+ if (location == Location .RETURN_TYPE && ! root) {
84+ throw error(" ${Optional ::class .java.name} can only be used at the top level of a return type" )
85+ }
86+
87+ unwrapped + = Unwrapped .Present
88+ realType = typeResolver.resolve(realType.actualTypeArguments.first(), onUnwrap)
89+
90+ if (realType is ParameterizedType && realType.isSubtypeOf(Optional ::class .java)) {
91+ throw error(" ${Optional ::class .java.name} cannot be nested within itself" )
92+ }
93+ }
94+
95+ // Match graphql type to java type.
96+ return when (graphQLType) {
97+ is NonNullType -> {
98+ if (optional) {
99+ throw error(" graphql type is marked as nonnull but ${Optional ::class .java.name} was used" )
100+ }
101+ unwrap(graphQLType.type, realType, root = false , unwrapped)
102+ }
103+
104+ is ListType -> when {
105+ realType is ParameterizedType && realType.isSubtypeOf(Iterable ::class .java) -> {
106+ unwrapped + = Unwrapped .Elements
107+ unwrap(graphQLType.type, realType.actualTypeArguments.first(), root = false , unwrapped)
108+ }
109+ realType is Class <* > && realType.isArray ->
110+ unwrap(graphQLType.type, realType.componentType, root = false , unwrapped)
111+ // e.g. a property map value: the element type is unknown, so match it as Object too
112+ realType == Any ::class .java ->
113+ unwrap(graphQLType.type, realType, root = false , unwrapped)
114+ else ->
115+ throw error(" Java class is not a List or generic type information was lost: $realType " )
116+ }
117+
118+ else -> Unwrapping (unwrapped, graphQLType, realType)
119+ }
120+ }
121+
112122 companion object {
113123 fun returnValue (graphQLType : GraphQLLangType , javaType : JavaType , typeResolver : GenericTypeResolver , reference : ScanDictionary .Reference ) =
114124 PotentialMatch (graphQLType, javaType, typeResolver, reference, Location .RETURN_TYPE )
@@ -117,4 +127,13 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
117127 PotentialMatch (graphQLType, javaType, typeResolver, reference, Location .PARAMETER_TYPE )
118128 }
119129 }
130+
131+ internal class Unwrapping (val unwrapped : List <Unwrapped >, val graphQLType : GraphQLLangType , val javaType : JavaType )
132+
133+ internal sealed interface Unwrapped {
134+ class Wrapper (val wrapper : GenericWrapper ) : Unwrapped
135+ object Data : Unwrapped
136+ object Present : Unwrapped
137+ object Elements : Unwrapped
138+ }
120139}
0 commit comments