Skip to content

Commit c35b365

Browse files
authored
Merge branch 'master' into feature/893-reject-scalar-directives
2 parents 9a2af8b + a155694 commit c35b365

15 files changed

Lines changed: 464 additions & 484 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: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,11 @@ class SchemaParser internal constructor(
7979
* Parses the given schema with respect to the given dictionary and returns GraphQL objects.
8080
*/
8181
fun parseSchemaObjects(): SchemaObjects {
82+
@Suppress("DEPRECATION")
8283
if (!options.introspectionEnabled) {
8384
// What the deprecated NoIntrospectionGraphqlFieldVisibility does. Its suggested replacement,
8485
// Introspection.enabledJvmWide, would also turn introspection off for every other schema in the JVM.
85-
// TODO deprecate introspectionEnabled and remove this in 15.0.0 (#858)
86+
// TODO remove this in 15.0.0 (#858)
8687
codeRegistryBuilder.fieldVisibility(BlockedFields.newBlock().addPattern("__.*").build())
8788
}
8889
// this overrides the above introspection enabled setting obviously... todo: add documentation
@@ -171,6 +172,8 @@ class SchemaParser internal constructor(
171172
val name = objectDefinition.name
172173
val extensions = typeExtensions.extensionsOf<ObjectTypeExtensionDefinition>(name)
173174
val directives = sdlDirectives.directivesOf(objectDefinition, extensions)
175+
val fieldDefinitions = objectDefinition.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }
176+
warnRepeatedFields(name, fieldDefinitions)
174177
val builder = GraphQLObjectType.newObject()
175178
.name(name)
176179
.definition(objectDefinition)
@@ -185,7 +188,7 @@ class SchemaParser internal constructor(
185188
}
186189
}
187190
.apply {
188-
objectDefinition.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }.forEach { fieldDefinition ->
191+
fieldDefinitions.forEach { fieldDefinition ->
189192
field { field ->
190193
createField(field, fieldDefinition, inputObjects)
191194
codeRegistryBuilder.dataFetcher(
@@ -209,6 +212,8 @@ class SchemaParser internal constructor(
209212
inputObjectsInProgress: MutableSet<String>): GraphQLInputObjectType {
210213
val extensionDefinitions = typeExtensions.extensionsOf<InputObjectTypeExtensionDefinition>(definition.name)
211214
val directives = sdlDirectives.directivesOf(definition, extensionDefinitions)
215+
val fieldDefinitions = (extensionDefinitions + definition).flatMap { it.inputValueDefinitions }
216+
warnRepeatedFields(definition.name, fieldDefinitions)
212217

213218
inputObjectsInProgress.add(definition.name)
214219

@@ -219,20 +224,18 @@ class SchemaParser internal constructor(
219224
.description(getDocumentation(definition, options))
220225
.withSdlDirectives(directives, INPUT_OBJECT)
221226
.apply {
222-
(extensionDefinitions + definition).forEach { typeDefinition ->
223-
typeDefinition.inputValueDefinitions.forEach { fieldDefinition ->
224-
field(
225-
GraphQLInputObjectField.newInputObjectField()
226-
.name(fieldDefinition.name)
227-
.definition(fieldDefinition)
228-
.description(getDocumentation(fieldDefinition, options))
229-
.apply { fieldDefinition.defaultValue?.let { v -> defaultValueLiteral(v) } }
230-
.apply { sdlDirectives.getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
231-
.type(determineInputType(fieldDefinition.type, inputObjects, inputObjectsInProgress))
232-
.withSdlDirectives(fieldDefinition.directives, INPUT_FIELD_DEFINITION)
233-
.build()
234-
)
235-
}
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+
)
236239
}
237240
}
238241

@@ -377,6 +380,16 @@ class SchemaParser internal constructor(
377380
}
378381
}
379382

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+
380393
private fun createField(field: GraphQLFieldDefinition.Builder, fieldDefinition: FieldDefinition, inputObjects: List<GraphQLInputObjectType>): GraphQLFieldDefinition.Builder {
381394
return field
382395
.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
*/

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ data class SchemaParserOptions internal constructor(
3131
val proxyHandlers: List<ProxyHandler>,
3232
val inputArgumentOptionalDetectOmission: Boolean,
3333
val preferGraphQLResolver: Boolean,
34+
@Deprecated("Will be removed in 15.0.0. Use Introspection.enabledJvmWide(false) for the whole JVM, or put Introspection.INTROSPECTION_DISABLED in the GraphQLContext of the request.")
3435
val introspectionEnabled: Boolean,
3536
val coroutineContextProvider: CoroutineContextProvider,
3637
val typeDefinitionFactories: List<TypeDefinitionFactory>,
@@ -128,6 +129,7 @@ data class SchemaParserOptions internal constructor(
128129
this.proxyHandlers.add(proxyHandler)
129130
}
130131

132+
@Deprecated("Will be removed in 15.0.0. Use Introspection.enabledJvmWide(false) for the whole JVM, or put Introspection.INTROSPECTION_DISABLED in the GraphQLContext of the request.")
131133
fun introspectionEnabled(introspectionEnabled: Boolean) = this.apply {
132134
this.introspectionEnabled = introspectionEnabled
133135
}
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
}

0 commit comments

Comments
 (0)