Skip to content

Commit 69b30a4

Browse files
authored
Merge branch 'master' into feature/drop-graphql-java-25
2 parents 00874b1 + d586879 commit 69b30a4

14 files changed

Lines changed: 489 additions & 497 deletions

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ kotlin.version=2.3.10
2626

2727
Add the dependency:
2828
```groovy
29-
compile 'io.github.graphql-java-kickstart:graphql-java-tools:14.4.0'
29+
compile 'io.github.graphql-java-kickstart:graphql-java-tools:14.5.0'
3030
```
3131

3232
### Using Maven
@@ -43,7 +43,7 @@ Add the dependency:
4343
<dependency>
4444
<groupId>io.github.graphql-java-kickstart</groupId>
4545
<artifactId>graphql-java-tools</artifactId>
46-
<version>14.4.0</version>
46+
<version>14.5.0</version>
4747
</dependency>
4848
```
4949

‎pom.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
<groupId>io.github.graphql-java-kickstart</groupId>
66
<artifactId>graphql-java-tools</artifactId>
7-
<version>14.4.1-SNAPSHOT</version>
7+
<version>14.5.1-SNAPSHOT</version>
88
<packaging>jar</packaging>
99

1010
<name>GraphQL Java Tools</name>

‎src/main/kotlin/graphql/kickstart/tools/GenericTypeResolver.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal class GenericTypeResolver(private val containingType: JavaType, private
2929
* - wildcards are replaced by their upper bound, e.g. `? extends Foo` becomes `Foo`
3030
* - primitives are replaced by their boxed class, e.g. `int` becomes `Integer`
3131
*/
32-
fun resolve(javaType: JavaType, onUnwrap: ((wrapped: ParameterizedType) -> Unit)? = null): JavaType {
32+
fun resolve(javaType: JavaType, onUnwrap: ((SchemaParserOptions.GenericWrapper) -> Unit)? = null): JavaType {
3333
return when (val type = resolveTypeVariables(javaType)) {
3434
is ParameterizedType -> {
3535
val rawType = type.rawType
@@ -41,7 +41,7 @@ internal class GenericTypeResolver(private val containingType: JavaType, private
4141
throw IndexOutOfBoundsException("Generic type '${TypeUtils.toString(type)}' does not have a type argument at index ${wrapper.index}!")
4242
}
4343

44-
onUnwrap?.invoke(type)
44+
onUnwrap?.invoke(wrapper)
4545
val unwrapsTo = wrapper.schemaWrapper.invoke(typeArguments[wrapper.index])
4646
resolve(unwrapsTo, onUnwrap)
4747
}

‎src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt‎

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,8 @@ class SchemaParser internal constructor(
172172
val name = objectDefinition.name
173173
val extensions = typeExtensions.extensionsOf<ObjectTypeExtensionDefinition>(name)
174174
val directives = sdlDirectives.directivesOf(objectDefinition, extensions)
175+
val fieldDefinitions = objectDefinition.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }
176+
warnRepeatedFields(name, fieldDefinitions)
175177
val builder = GraphQLObjectType.newObject()
176178
.name(name)
177179
.definition(objectDefinition)
@@ -186,7 +188,7 @@ class SchemaParser internal constructor(
186188
}
187189
}
188190
.apply {
189-
objectDefinition.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }.forEach { fieldDefinition ->
191+
fieldDefinitions.forEach { fieldDefinition ->
190192
field { field ->
191193
createField(field, fieldDefinition, inputObjects)
192194
codeRegistryBuilder.dataFetcher(
@@ -210,6 +212,8 @@ class SchemaParser internal constructor(
210212
inputObjectsInProgress: MutableSet<String>): GraphQLInputObjectType {
211213
val extensionDefinitions = typeExtensions.extensionsOf<InputObjectTypeExtensionDefinition>(definition.name)
212214
val directives = sdlDirectives.directivesOf(definition, extensionDefinitions)
215+
val fieldDefinitions = (extensionDefinitions + definition).flatMap { it.inputValueDefinitions }
216+
warnRepeatedFields(definition.name, fieldDefinitions)
213217

214218
inputObjectsInProgress.add(definition.name)
215219

@@ -220,20 +224,18 @@ class SchemaParser internal constructor(
220224
.description(getDocumentation(definition, options))
221225
.withSdlDirectives(directives, INPUT_OBJECT)
222226
.apply {
223-
(extensionDefinitions + definition).forEach { typeDefinition ->
224-
typeDefinition.inputValueDefinitions.forEach { fieldDefinition ->
225-
field(
226-
GraphQLInputObjectField.newInputObjectField()
227-
.name(fieldDefinition.name)
228-
.definition(fieldDefinition)
229-
.description(getDocumentation(fieldDefinition, options))
230-
.apply { fieldDefinition.defaultValue?.let { v -> defaultValueLiteral(v) } }
231-
.apply { sdlDirectives.getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
232-
.type(determineInputType(fieldDefinition.type, inputObjects, inputObjectsInProgress))
233-
.withSdlDirectives(fieldDefinition.directives, INPUT_FIELD_DEFINITION)
234-
.build()
235-
)
236-
}
227+
fieldDefinitions.forEach { fieldDefinition ->
228+
field(
229+
GraphQLInputObjectField.newInputObjectField()
230+
.name(fieldDefinition.name)
231+
.definition(fieldDefinition)
232+
.description(getDocumentation(fieldDefinition, options))
233+
.apply { fieldDefinition.defaultValue?.let { v -> defaultValueLiteral(v) } }
234+
.apply { sdlDirectives.getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
235+
.type(determineInputType(fieldDefinition.type, inputObjects, inputObjectsInProgress))
236+
.withSdlDirectives(fieldDefinition.directives, INPUT_FIELD_DEFINITION)
237+
.build()
238+
)
237239
}
238240
}
239241

@@ -295,12 +297,7 @@ class SchemaParser internal constructor(
295297
.specifiedByUrl(getSpecifiedByUrl(directives) ?: provided.specifiedByUrl)
296298
// like graphql-java, only the schema's directives are applied
297299
.clearDirectives()
298-
try {
299-
builder.withSdlDirectives(directives, SCALAR)
300-
} catch (e: SchemaError) {
301-
// TODO throw instead in 15.0.0 (#893), like for the other types
302-
log.warn("Scalar '$name' is built without its directives from the schema: ${e.message}")
303-
}
300+
.withSdlDirectives(directives, SCALAR)
304301

305302
return builder.build()
306303
}
@@ -383,6 +380,16 @@ class SchemaParser internal constructor(
383380
}
384381
}
385382

383+
// TODO throw a SchemaError instead in 15.0.0 (#865), like graphql-java does
384+
private fun warnRepeatedFields(typeName: String, fieldDefinitions: List<NamedNode<*>>) {
385+
fieldDefinitions.groupBy { it.name }.values.filter { it.size > 1 }.forEach { declarations ->
386+
val kept = declarations.last()
387+
val location = kept.sourceLocation.describe()
388+
val which = if (location.isEmpty()) "one of them" else "the one$location"
389+
log.warn("Type '$typeName' declares field '${kept.name}' more than once, only $which is kept.")
390+
}
391+
}
392+
386393
private fun createField(field: GraphQLFieldDefinition.Builder, fieldDefinition: FieldDefinition, inputObjects: List<GraphQLInputObjectType>): GraphQLFieldDefinition.Builder {
387394
return field
388395
.name(fieldDefinition.name)

‎src/main/kotlin/graphql/kickstart/tools/SchemaParserDictionary.kt‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@ class SchemaParserDictionary {
1111
*/
1212
fun toMap(): Map<String, Class<*>> = dictionary.toMap()
1313

14-
// TODO remove in 15.0.0 (#900)
15-
/**
16-
* Returns a copy of the entries added so far. If several types share a class, the inverse of the copy only maps that class
17-
* back to the first of them.
18-
*/
19-
@Deprecated("Use toMap() instead", ReplaceWith("toMap()"))
20-
@Suppress("DEPRECATION")
21-
fun getDictionary(): graphql.kickstart.tools.util.BiMap<String, Class<*>> =
22-
graphql.kickstart.tools.util.BiMap.unmodifiableCopyOf(dictionary)
23-
2414
/**
2515
* Add arbitrary classes to the parser's dictionary, overriding the generated type name.
2616
*/
Lines changed: 82 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package graphql.kickstart.tools
22

33
import graphql.execution.DataFetcherResult
4+
import graphql.kickstart.tools.SchemaParserOptions.GenericWrapper
45
import graphql.kickstart.tools.util.GraphQLLangType
56
import graphql.kickstart.tools.util.JavaType
67
import graphql.kickstart.tools.util.isSubtypeOf
@@ -15,79 +16,22 @@ import java.util.*
1516
*/
1617
internal 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
}

‎src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt‎

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,7 @@ internal class MethodFieldResolver(
130130
// Object (e.g. for a union) can't tell them apart, so then every future of a method whose return type is Future is checked
131131
val valueClass = (TypeUtils.getRawType(getResolvedReturnType(), null) ?: Any::class.java)
132132
.takeUnless { it == Any::class.java && TypeUtils.getRawType(returnType, null) == Future::class.java }
133-
val unwrapper = ReturnValueUnwrapper.create(
134-
method, returnType, valueClass, field.type, search.isSubscription, typeResolver, options.genericWrappers, scalarNames
135-
)
133+
val unwrapper = ReturnValueUnwrapper.create(method, returnValueMatch.unwrapping, valueClass, isSuspendFunction, search.isSubscription, options.genericWrappers)
136134

137135
return if (numberOfParameters > 0 || isSuspendFunction) {
138136
// requires arguments and environment or is a suspend function
@@ -160,8 +158,6 @@ internal class MethodFieldResolver(
160158
}
161159

162160
override fun scanForMatches(): List<TypeClassMatcher.PotentialMatch> {
163-
val returnValueMatch = TypeClassMatcher.PotentialMatch.returnValue(field.type, getResolvedReturnType(), typeResolver, ScanDictionary.ReturnValueReference(method))
164-
165161
return field.inputValueDefinitions.mapIndexed { i, inputDefinition ->
166162
TypeClassMatcher.PotentialMatch.parameterType(inputDefinition.type, getMethodParameterType(i)!!, typeResolver, ScanDictionary.MethodParameterReference(method, i))
167163
} + listOf(returnValueMatch)
@@ -184,8 +180,13 @@ internal class MethodFieldResolver(
184180
typeResolver.resolveTypeVariables(declaredType)
185181
}
186182

187-
private fun getResolvedReturnType(): JavaType =
188-
typeResolver.resolve(if (search.isSubscription) returnType.asPublisher() else returnType)
183+
private val returnValueMatch by lazy {
184+
TypeClassMatcher.PotentialMatch.returnValue(field.type, matchedReturnType(), typeResolver, ScanDictionary.ReturnValueReference(method))
185+
}
186+
187+
private fun matchedReturnType(): JavaType = if (search.isSubscription) returnType.asPublisher() else returnType
188+
189+
private fun getResolvedReturnType(): JavaType = typeResolver.resolve(matchedReturnType())
189190

190191
private fun getJavaMethodParameterIndex(index: Int) = index + search.firstArgumentIndex
191192

0 commit comments

Comments
 (0)