Skip to content

Commit c7b62db

Browse files
authored
Merge branch 'master' into feature/846-remove-missing-resolver-data-fetcher
2 parents eede0bf + f480042 commit c7b62db

22 files changed

Lines changed: 760 additions & 677 deletions

‎.github/workflows/pull-request.yml‎

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -24,23 +24,3 @@ jobs:
2424
restore-keys: ${{ runner.os }}-m2
2525
- name: Build with Maven
2626
run: mvn --batch-mode --update-snapshots verify
27-
28-
# The same jar has to work with the oldest supported graphql-java, so build and test against it too
29-
graphql-java-25:
30-
runs-on: ubuntu-latest
31-
steps:
32-
- name: Checkout
33-
uses: actions/checkout@v7
34-
- name: Setup java
35-
uses: actions/setup-java@v6
36-
with:
37-
java-version: '11'
38-
distribution: 'temurin'
39-
- name: Cache Maven packages
40-
uses: actions/cache@v6
41-
with:
42-
path: ~/.m2
43-
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
44-
restore-keys: ${{ runner.os }}-m2
45-
- name: Build with Maven
46-
run: mvn --batch-mode --update-snapshots verify -Dgraphql-java.version=25.1

‎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/SchemaClassScanner.kt‎

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import graphql.kickstart.tools.ScanDictionary.InitialDictionaryEntry
66
import graphql.kickstart.tools.ScanDictionary.UnionMemberReference
77
import graphql.kickstart.tools.ScanDictionary.UnusedTypeReference
88
import graphql.kickstart.tools.resolver.FieldResolver
9+
import graphql.kickstart.tools.resolver.FieldResolverError
910
import graphql.kickstart.tools.resolver.FieldResolverScanner
1011
import graphql.kickstart.tools.resolver.MethodFieldResolver
1112
import graphql.kickstart.tools.resolver.PropertyFieldResolver
@@ -66,6 +67,7 @@ internal class SchemaClassScanner(
6667
private val sealedSubclassMatcher = SealedSubclassMatcher(dictionary, typeExtensions)
6768

6869
private val fieldResolversByType = mutableMapOf<ObjectTypeDefinition, MutableMap<FieldDefinition, FieldResolver>>()
70+
private val notCallableOnResolver = mutableListOf<NotCallableOnResolver>()
6971

7072
init {
7173
initialDictionary.forEach { (name, clazz) ->
@@ -145,6 +147,8 @@ internal class SchemaClassScanner(
145147
}
146148
}
147149

150+
throwIfNotCallableOnResolver()
151+
148152
return validateAndCreateResult(rootTypeHolder)
149153
}
150154

@@ -450,19 +454,18 @@ internal class SchemaClassScanner(
450454
private fun scanResolverInfoForPotentialMatches(type: ObjectTypeDefinition, resolverInfo: ResolverInfo) {
451455
type.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }.forEach { field ->
452456
val fieldResolver = fieldResolverScanner.findFieldResolver(field, resolverInfo)
453-
warnIfNotCallableOnResolver(type, fieldResolver)
457+
collectIfNotCallableOnResolver(type, fieldResolver)
454458

455459
fieldResolversByType.getOrPut(type) { mutableMapOf() }[fieldResolver.field] = fieldResolver
456460

457461
fieldResolver.scanForMatches().forEach { potentialMatch ->
458462
dictionary.handleFoundType(typeClassMatcher.match(potentialMatch))
459463
}
460-
warnIfParameterMissing(type, fieldResolver)
464+
requireParameterForEachArgument(type, fieldResolver)
461465
}
462466
}
463467

464-
// TODO throw a FieldResolverError instead in 15.0.0 (#868)
465-
private fun warnIfNotCallableOnResolver(type: ObjectTypeDefinition, fieldResolver: FieldResolver) {
468+
private fun collectIfNotCallableOnResolver(type: ObjectTypeDefinition, fieldResolver: FieldResolver) {
466469
val source = fieldResolver.search.source ?: return
467470
val member: Member = when (fieldResolver) {
468471
is MethodFieldResolver -> fieldResolver.method
@@ -473,17 +476,26 @@ internal class SchemaClassScanner(
473476
return
474477
}
475478

476-
// class-based proxies don't delegate field reads to the target, so a field needs a getter too
477-
val fix = if (member is Field) "Add a getter for the field and use class-based proxies" else "Use class-based proxies instead"
478-
log.warn(
479+
notCallableOnResolver += NotCallableOnResolver(
479480
"Field '${type.name}.${fieldResolver.field.name}' resolves to ${member.declaringClass.name}.${member.name}, but the resolver is a " +
480-
"${source.javaClass.name}, which isn't a ${member.declaringClass.name}, so every query of the field will fail. This happens " +
481-
"with JDK dynamic proxies, which only implement interfaces. $fix, e.g. proxyTargetClass = true in Spring."
481+
"${source.javaClass.name}, which isn't a ${member.declaringClass.name}, so every query of the field will fail.",
482+
member is Field
483+
)
484+
}
485+
486+
private fun throwIfNotCallableOnResolver() {
487+
if (notCallableOnResolver.isEmpty()) {
488+
return
489+
}
490+
491+
val fix = "Use class-based proxies instead, e.g. proxyTargetClass = true in Spring." +
492+
if (notCallableOnResolver.any { it.isField }) " For a public field, also add a getter, since class-based proxies don't pass field reads to the target." else ""
493+
throw FieldResolverError(
494+
notCallableOnResolver.joinToString("\n", postfix = "\nThis happens with JDK dynamic proxies, which only implement interfaces. $fix") { it.message }
482495
)
483496
}
484497

485-
// TODO throw a FieldResolverError instead in 15.0.0 (#889)
486-
private fun warnIfParameterMissing(type: ObjectTypeDefinition, fieldResolver: FieldResolver) {
498+
private fun requireParameterForEachArgument(type: ObjectTypeDefinition, fieldResolver: FieldResolver) {
487499
if (fieldResolver !is MethodFieldResolver) {
488500
return
489501
}
@@ -495,7 +507,7 @@ internal class SchemaClassScanner(
495507
}
496508

497509
val method = fieldResolver.method
498-
log.warn(
510+
throw FieldResolverError(
499511
"Field '${type.name}.${fieldResolver.field.name}' has no parameter for its argument '${argument.name}: " +
500512
"${TypeUtil.simplePrint(argument.type)}' in ${method.declaringClass.name}.${method.name}, so the ${parameter.simpleName} " +
501513
"parameter gets the argument instead. Add a parameter for each argument, in order, before the ${parameter.simpleName} parameter."
@@ -528,6 +540,8 @@ internal class SchemaClassScanner(
528540

529541
private data class QueueItem(val type: ObjectTypeDefinition, val clazz: JavaType)
530542

543+
private data class NotCallableOnResolver(val message: String, val isField: Boolean)
544+
531545
private class RootTypesHolder(
532546
options: SchemaParserOptions,
533547
rootInfo: RootTypeInfo,

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

Lines changed: 27 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import graphql.language.*
2323
import graphql.schema.*
2424
import graphql.schema.idl.RuntimeWiring
2525
import graphql.schema.idl.ScalarInfo
26-
import graphql.schema.visibility.BlockedFields
2726
import org.slf4j.LoggerFactory
2827
import kotlin.reflect.KClass
2928

@@ -79,13 +78,6 @@ class SchemaParser internal constructor(
7978
* Parses the given schema with respect to the given dictionary and returns GraphQL objects.
8079
*/
8180
fun parseSchemaObjects(): SchemaObjects {
82-
if (!options.introspectionEnabled) {
83-
// What the deprecated NoIntrospectionGraphqlFieldVisibility does. Its suggested replacement,
84-
// 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-
codeRegistryBuilder.fieldVisibility(BlockedFields.newBlock().addPattern("__.*").build())
87-
}
88-
// this overrides the above introspection enabled setting obviously... todo: add documentation
8981
options.fieldVisibility?.let { codeRegistryBuilder.fieldVisibility(it) }
9082

9183
// Create GraphQL objects
@@ -171,6 +163,8 @@ class SchemaParser internal constructor(
171163
val name = objectDefinition.name
172164
val extensions = typeExtensions.extensionsOf<ObjectTypeExtensionDefinition>(name)
173165
val directives = sdlDirectives.directivesOf(objectDefinition, extensions)
166+
val fieldDefinitions = objectDefinition.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }
167+
rejectRepeated(name, "field", fieldDefinitions)
174168
val builder = GraphQLObjectType.newObject()
175169
.name(name)
176170
.definition(objectDefinition)
@@ -185,7 +179,7 @@ class SchemaParser internal constructor(
185179
}
186180
}
187181
.apply {
188-
objectDefinition.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }.forEach { fieldDefinition ->
182+
fieldDefinitions.forEach { fieldDefinition ->
189183
field { field ->
190184
createField(field, fieldDefinition, inputObjects)
191185
codeRegistryBuilder.dataFetcher(
@@ -209,6 +203,8 @@ class SchemaParser internal constructor(
209203
inputObjectsInProgress: MutableSet<String>): GraphQLInputObjectType {
210204
val extensionDefinitions = typeExtensions.extensionsOf<InputObjectTypeExtensionDefinition>(definition.name)
211205
val directives = sdlDirectives.directivesOf(definition, extensionDefinitions)
206+
val inputValueDefinitions = definition.withExtensions(typeExtensions).flatMap { it.inputValueDefinitions }
207+
rejectRepeated(definition.name, "field", inputValueDefinitions)
212208

213209
inputObjectsInProgress.add(definition.name)
214210

@@ -219,20 +215,18 @@ class SchemaParser internal constructor(
219215
.description(getDocumentation(definition, options))
220216
.withSdlDirectives(directives, INPUT_OBJECT)
221217
.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-
}
218+
inputValueDefinitions.forEach { fieldDefinition ->
219+
field(
220+
GraphQLInputObjectField.newInputObjectField()
221+
.name(fieldDefinition.name)
222+
.definition(fieldDefinition)
223+
.description(getDocumentation(fieldDefinition, options))
224+
.apply { fieldDefinition.defaultValue?.let { v -> defaultValueLiteral(v) } }
225+
.apply { sdlDirectives.getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
226+
.type(determineInputType(fieldDefinition.type, inputObjects, inputObjectsInProgress))
227+
.withSdlDirectives(fieldDefinition.directives, INPUT_FIELD_DEFINITION)
228+
.build()
229+
)
236230
}
237231
}
238232

@@ -249,15 +243,15 @@ class SchemaParser internal constructor(
249243
val extensions = typeExtensions.extensionsOf<EnumTypeExtensionDefinition>(name)
250244
val directives = sdlDirectives.directivesOf(definition, extensions)
251245
val values = definition.withExtensions(typeExtensions).flatMap { it.enumValueDefinitions }
252-
warnRepeated(name, "value", values.map { it.name })
246+
rejectRepeated(name, "value", values)
253247
val builder = GraphQLEnumType.newEnum()
254248
.name(name)
255249
.definition(definition)
256250
.extensionDefinitions(extensions)
257251
.description(getDocumentation(definition, options))
258252
.withSdlDirectives(directives, ENUM)
259253
.apply {
260-
values.distinctBy { it.name }.forEach { valueDefinition ->
254+
values.forEach { valueDefinition ->
261255
val enumName = valueDefinition.name
262256
val enumValue = if (enumClass == null) {
263257
enumName
@@ -294,12 +288,7 @@ class SchemaParser internal constructor(
294288
.specifiedByUrl(getSpecifiedByUrl(directives) ?: provided.specifiedByUrl)
295289
// like graphql-java, only the schema's directives are applied
296290
.clearDirectives()
297-
try {
298-
builder.withSdlDirectives(directives, SCALAR)
299-
} catch (e: SchemaError) {
300-
// TODO throw instead in 15.0.0 (#893), like for the other types
301-
log.warn("Scalar '$name' is built without its directives from the schema: ${e.message}")
302-
}
291+
.withSdlDirectives(directives, SCALAR)
303292

304293
return builder.build()
305294
}
@@ -308,15 +297,15 @@ class SchemaParser internal constructor(
308297
val extensions = typeExtensions.extensionsOf<InterfaceTypeExtensionDefinition>(interfaceDefinition.name)
309298
val directives = sdlDirectives.directivesOf(interfaceDefinition, extensions)
310299
val fieldDefinitions = interfaceDefinition.withExtensions(typeExtensions).flatMap { it.fieldDefinitions }
311-
warnRepeated(interfaceDefinition.name, "field", fieldDefinitions.map { it.name })
300+
rejectRepeated(interfaceDefinition.name, "field", fieldDefinitions)
312301
val builder = GraphQLInterfaceType.newInterface()
313302
.name(interfaceDefinition.name)
314303
.definition(interfaceDefinition)
315304
.extensionDefinitions(extensions)
316305
.description(getDocumentation(interfaceDefinition, options))
317306
.withSdlDirectives(directives, INTERFACE)
318307
.apply {
319-
fieldDefinitions.distinctBy { it.name }.forEach { fieldDefinition ->
308+
fieldDefinitions.forEach { fieldDefinition ->
320309
field { field -> createField(field, fieldDefinition, inputObjects) }
321310
}
322311
}
@@ -333,7 +322,7 @@ class SchemaParser internal constructor(
333322
private fun createUnionObject(definition: UnionTypeDefinition, types: List<GraphQLObjectType>): GraphQLUnionType {
334323
val extensions = typeExtensions.extensionsOf<UnionTypeExtensionDefinition>(definition.name)
335324
val directives = sdlDirectives.directivesOf(definition, extensions)
336-
warnRepeated(definition.name, "member", definition.withExtensions(typeExtensions).flatMap { it.memberTypes }.map { (it as TypeName).name })
325+
rejectRepeated(definition.name, "member", definition.withExtensions(typeExtensions).flatMap { it.memberTypes }.map { it as TypeName })
337326
val builder = GraphQLUnionType.newUnionType()
338327
.name(definition.name)
339328
.definition(definition)
@@ -375,11 +364,10 @@ class SchemaParser internal constructor(
375364
return leafObjects
376365
}
377366

378-
// TODO throw a SchemaError instead in 15.0.0 (#863), like graphql-java does
379-
private fun warnRepeated(typeName: String, kind: String, names: List<String?>) {
380-
names.groupingBy { it }.eachCount().filterValues { it > 1 }.keys.forEach {
381-
log.warn("Type '$typeName' declares $kind '$it' more than once, only the first one is kept.")
382-
}
367+
private fun rejectRepeated(typeName: String, kind: String, declarations: List<NamedNode<*>>) {
368+
val names = mutableSetOf<String?>()
369+
val repeat = declarations.firstOrNull { !names.add(it.name) } ?: return
370+
throw SchemaError("Type '$typeName' declares $kind '${repeat.name}' more than once${repeat.sourceLocation.describe()}.")
383371
}
384372

385373
private fun createField(field: GraphQLFieldDefinition.Builder, fieldDefinition: FieldDefinition, inputObjects: List<GraphQLInputObjectType>): GraphQLFieldDefinition.Builder {

‎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: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ data class SchemaParserOptions internal constructor(
2828
val proxyHandlers: List<ProxyHandler>,
2929
val inputArgumentOptionalDetectOmission: Boolean,
3030
val preferGraphQLResolver: Boolean,
31-
val introspectionEnabled: Boolean,
3231
val coroutineContextProvider: CoroutineContextProvider,
3332
val typeDefinitionFactories: List<TypeDefinitionFactory>,
3433
val fieldVisibility: GraphqlFieldVisibility?,
@@ -58,7 +57,6 @@ data class SchemaParserOptions internal constructor(
5857
private val proxyHandlers: MutableList<ProxyHandler> = mutableListOf(Spring4AopProxyHandler(), GuiceAopProxyHandler(), JavassistProxyHandler(), WeldProxyHandler())
5958
private var inputArgumentOptionalDetectOmission = false
6059
private var preferGraphQLResolver = false
61-
private var introspectionEnabled = true
6260
private var coroutineContextProvider: CoroutineContextProvider? = null
6361
private var typeDefinitionFactories: MutableList<TypeDefinitionFactory> = mutableListOf(RelayConnectionFactory())
6462
private var fieldVisibility: GraphqlFieldVisibility? = null
@@ -119,10 +117,6 @@ data class SchemaParserOptions internal constructor(
119117
this.proxyHandlers.add(proxyHandler)
120118
}
121119

122-
fun introspectionEnabled(introspectionEnabled: Boolean) = this.apply {
123-
this.introspectionEnabled = introspectionEnabled
124-
}
125-
126120
fun coroutineContext(context: CoroutineContext) = this.apply {
127121
this.coroutineContextProvider = DefaultCoroutineContextProvider(context)
128122
}
@@ -207,7 +201,6 @@ data class SchemaParserOptions internal constructor(
207201
proxyHandlers,
208202
inputArgumentOptionalDetectOmission,
209203
preferGraphQLResolver,
210-
introspectionEnabled,
211204
coroutineContextProvider,
212205
typeDefinitionFactories,
213206
fieldVisibility,

0 commit comments

Comments
 (0)