Skip to content

Commit f5b5fcd

Browse files
authored
Merge pull request #818 from pfyod/graphql-java-26
Support graphql-java 26
2 parents d46d10c + 00aec7b commit f5b5fcd

16 files changed

Lines changed: 169 additions & 36 deletions

‎pom.xml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
<kotlin.version>2.3.10</kotlin.version>
1818
<kotlin-coroutines.version>1.10.2</kotlin-coroutines.version>
1919
<jackson.version>2.22.3</jackson.version>
20-
<graphql-java.version>25.1</graphql-java.version>
20+
<graphql-java.version>26.1</graphql-java.version>
2121
<reactive-streams.version>1.0.4</reactive-streams.version>
2222

2323
<maven.compiler.source>${java.version}</maven.compiler.source>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ internal abstract class DictionaryTypeResolver(
2222
}
2323

2424
override fun getType(env: TypeResolutionEnvironment): GraphQLObjectType? {
25-
val clazz = env.getObject<Any>().javaClass
25+
val clazz = env.getObject<Any>()!!.javaClass
2626
val name = getTypeDefinition(clazz)?.name ?: clazz.simpleName
2727
return env.schema.getObjectType(name) ?: throw TypeResolverError(getError(name))
2828
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ data class SchemaObjects(
1414
val codeRegistryBuilder: GraphQLCodeRegistry.Builder,
1515
val description: String?
1616
) {
17+
// TODO change dictionary to Set<GraphQLNamedType> in the next major version and remove this cast
18+
@Suppress("UNCHECKED_CAST")
19+
private fun namedDictionary(): Set<GraphQLNamedType> = dictionary as Set<GraphQLNamedType>
20+
1721
/**
1822
* Makes a GraphQLSchema with query, mutation and subscription.
1923
*/
@@ -23,7 +27,7 @@ data class SchemaObjects(
2327
.query(query)
2428
.mutation(mutation)
2529
.subscription(subscription)
26-
.additionalTypes(dictionary)
30+
.additionalTypes(namedDictionary())
2731
.additionalDirectives(directives)
2832
.codeRegistry(codeRegistryBuilder.build())
2933
.build()
@@ -35,6 +39,6 @@ data class SchemaObjects(
3539
fun toReadOnlySchema(): GraphQLSchema = GraphQLSchema.newSchema()
3640
.description(description)
3741
.query(query)
38-
.additionalTypes(dictionary)
42+
.additionalTypes(namedDictionary())
3943
.build()
4044
}

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package graphql.kickstart.tools
22

3+
import graphql.Directives
34
import graphql.introspection.Introspection
45
import graphql.introspection.Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION
56
import graphql.kickstart.tools.directive.DirectiveWiringHelper
@@ -8,7 +9,6 @@ import graphql.kickstart.tools.util.getExtendedFieldDefinitions
89
import graphql.kickstart.tools.util.unwrap
910
import graphql.language.*
1011
import graphql.schema.*
11-
import graphql.schema.idl.DirectiveInfo
1212
import graphql.schema.idl.RuntimeWiring
1313
import graphql.schema.idl.ScalarInfo
1414
import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility
@@ -351,7 +351,7 @@ class SchemaParser internal constructor(
351351
private fun buildAppliedDirectives(directives: List<Directive>): Array<GraphQLAppliedDirective> {
352352
return directives.map { directive ->
353353
val graphQLDirective = schemaDirectives.find { d -> d.name == directive.name }
354-
?: DirectiveInfo.GRAPHQL_SPECIFICATION_DIRECTIVE_MAP[directive.name]
354+
?: BUILT_IN_DIRECTIVES[directive.name]
355355
?: throw SchemaError("Found applied directive ${directive.name} without corresponding directive definition.")
356356
val graphQLArguments = graphQLDirective.arguments.associateBy { it.name }
357357

@@ -374,6 +374,22 @@ class SchemaParser internal constructor(
374374
)
375375
}
376376
}
377+
.apply {
378+
// a bare @deprecated has no "reason" argument, which makes SchemaPrinter throw a NPE.
379+
// copy the default from the directive definition (for the built-in one: "No longer supported").
380+
if (directive.name == Directives.DeprecatedDirective.name && directive.arguments.none { it.name == "reason" }) {
381+
val reasonArgument = graphQLArguments["reason"]
382+
if (reasonArgument != null && reasonArgument.hasSetDefaultValue()) {
383+
argument(GraphQLAppliedDirectiveArgument.newArgument()
384+
.name(reasonArgument.name)
385+
.type(reasonArgument.type)
386+
.description(reasonArgument.description)
387+
.inputValueWithState(reasonArgument.argumentDefaultValue)
388+
.build()
389+
)
390+
}
391+
}
392+
}
377393
.build()
378394
}.toTypedArray()
379395
}
@@ -391,7 +407,7 @@ class SchemaParser internal constructor(
391407
if (repeatable || !names.contains(directive.name)) {
392408
names.add(directive.name)
393409
val graphQLDirective = this.schemaDirectives.find { d -> d.name == directive.name }
394-
?: DirectiveInfo.GRAPHQL_SPECIFICATION_DIRECTIVE_MAP[directive.name]
410+
?: BUILT_IN_DIRECTIVES[directive.name]
395411
?: throw SchemaError("Found applied directive ${directive.name} without corresponding directive definition.")
396412
val graphQLArguments = graphQLDirective.arguments.associateBy { it.name }
397413
output.add(
@@ -521,4 +537,16 @@ class SchemaError(message: String, cause: Throwable? = null) : RuntimeException(
521537

522538
val GRAPHQL_SCALARS = ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS.associateBy { it.name }
523539

540+
// Built from the individual constants rather than Directives.BUILT_IN_DIRECTIVES_MAP (added in graphql-java 26)
541+
// so that the library keeps working with graphql-java 25. TODO replace with Directives.BUILT_IN_DIRECTIVES_MAP once we deploy a major version
542+
private val BUILT_IN_DIRECTIVES = listOf(
543+
Directives.IncludeDirective,
544+
Directives.SkipDirective,
545+
Directives.DeprecatedDirective,
546+
Directives.SpecifiedByDirective,
547+
Directives.OneOfDirective,
548+
Directives.DeferDirective,
549+
Directives.ExperimentalDisableErrorPropagationDirective
550+
).associateBy { it.name }
551+
524552
const val DEFAULT_DEPRECATION_MESSAGE = "No longer supported"

‎src/main/kotlin/graphql/kickstart/tools/relay/RelayConnectionFactory.kt‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package graphql.kickstart.tools.relay
22

3+
import graphql.kickstart.tools.SchemaError
34
import graphql.kickstart.tools.TypeDefinitionFactory
45
import graphql.language.*
56

@@ -54,7 +55,7 @@ class RelayConnectionFactory : TypeDefinitionFactory {
5455
.fieldDefinition(FieldDefinition("pageInfo", TypeName("PageInfo")))
5556
.build()
5657

57-
private fun createEdgeDefinition(connectionType: String, nodeType: String?): ObjectTypeDefinition =
58+
private fun createEdgeDefinition(connectionType: String, nodeType: String): ObjectTypeDefinition =
5859
ObjectTypeDefinition.newObjectTypeDefinition()
5960
.name(connectionType + "Edge")
6061
.fieldDefinition(FieldDefinition("cursor", TypeName("String")))
@@ -70,9 +71,9 @@ class RelayConnectionFactory : TypeDefinitionFactory {
7071
.fieldDefinition(FieldDefinition("endCursor", TypeName("String")))
7172
.build()
7273

73-
private fun Directive.forTypeName(): String? {
74-
return (this.getArgument("for").value as StringValue).value
75-
}
74+
private fun Directive.forTypeName(): String =
75+
(getArgument("for")?.value as? StringValue)?.value
76+
?: throw SchemaError("@connection directive is missing the required 'for' argument")
7677

7778
private fun Directive.withField(field: FieldDefinition): DirectiveWithField {
7879
return DirectiveWithField(field, this.name, this.arguments, this.sourceLocation, this.comments)
@@ -92,9 +93,9 @@ class RelayConnectionFactory : TypeDefinitionFactory {
9293
fun getTypeName(): String {
9394
val type = field.type
9495
if (type is NonNullType) {
95-
return (type.type as TypeName).name
96+
return (type.type as TypeName).name!!
9697
}
97-
return (field.type as TypeName).name
98+
return (field.type as TypeName).name!!
9899
}
99100
}
100101
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ internal class MethodFieldResolver(
141141
is ListType -> List::class.java.isAssignableFrom(this.genericType.getRawClass(genericParameterType))
142142
&& isConcreteScalarType(environment, type.type, this.genericType.unwrapGenericType(genericParameterType))
143143

144-
is TypeName -> environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) && type.name != "ID" }
144+
is TypeName -> environment.graphQLSchema?.getType(type.name!!)?.let { isScalar(it) && type.name != "ID" }
145145
?: false
146146

147147
is NonNullType -> isConcreteScalarType(environment, type.type, genericParameterType)

‎src/main/kotlin/graphql/kickstart/tools/util/Utils.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ internal val Class<*>.declaredNonProxyMethods: List<JavaMethod>
5252

5353
internal fun getDocumentation(node: AbstractNode<*>, options: SchemaParserOptions): String? =
5454
when {
55-
node is AbstractDescribedNode<*> && node.description != null -> node.description.content
55+
node is AbstractDescribedNode<*> && node.description != null -> node.description?.content
5656
!options.useCommentsForDescriptions -> null
5757
node.comments.isNullOrEmpty() -> null
5858
else -> node.comments.asSequence()

‎src/test/kotlin/graphql/kickstart/tools/DeepGenericsHierarchyTest.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ class PlaceTest {
1818
val executionInput = ExecutionInput.newExecutionInput().query(query).build()
1919
val result = graphql.execute(executionInput)
2020

21-
assert(result.getData<Map<String, List<*>>>()["places1"]?.size == 3)
22-
assert(result.getData<Map<String, List<*>>>()["places2"]?.size == 2)
21+
assert(result.getData<Map<String, List<*>>>()!!["places1"]?.size == 3)
22+
assert(result.getData<Map<String, List<*>>>()!!["places2"]?.size == 2)
2323
}
2424
}
2525

‎src/test/kotlin/graphql/kickstart/tools/DeprecatedDirectiveTest.kt‎

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import graphql.schema.DataFetchingEnvironment
66
import graphql.schema.GraphQLEnumType
77
import graphql.schema.GraphQLInputObjectType
88
import graphql.schema.GraphQLObjectType
9+
import graphql.schema.idl.SchemaPrinter
910
import org.junit.Test
1011

1112
class DeprecatedDirectiveTest {
@@ -111,7 +112,7 @@ class DeprecatedDirectiveTest {
111112
val userTypeEnum = schema.getType("UserType") as GraphQLEnumType
112113
val droidValue = userTypeEnum.getValue("DROID")
113114

114-
assert(droidValue.isDeprecated)
115+
assert(droidValue!!.isDeprecated)
115116
assertEquals(droidValue.deprecationReason, "No longer supported")
116117
}
117118

@@ -151,7 +152,7 @@ class DeprecatedDirectiveTest {
151152
val userTypeEnum = schema.getType("UserType") as GraphQLEnumType
152153
val droidValue = userTypeEnum.getValue("DROID")
153154

154-
assert(droidValue.isDeprecated)
155+
assert(droidValue!!.isDeprecated)
155156
assertEquals(droidValue.deprecationReason, "This value is no longer used")
156157
}
157158

@@ -250,7 +251,7 @@ class DeprecatedDirectiveTest {
250251
.makeExecutableSchema()
251252

252253
val directive = schema.getDirective("uppercase")
253-
val argument = directive.getArgument("firstCharacterOnly")
254+
val argument = directive!!.getArgument("firstCharacterOnly")
254255

255256
assert(argument.isDeprecated)
256257
assertEquals(argument.deprecationReason, "No longer supported")
@@ -285,7 +286,7 @@ class DeprecatedDirectiveTest {
285286
.makeExecutableSchema()
286287

287288
val directive = schema.getDirective("uppercase")
288-
val argument = directive.getArgument("firstCharacterOnly")
289+
val argument = directive!!.getArgument("firstCharacterOnly")
289290

290291
assert(argument.isDeprecated)
291292
assertEquals(argument.deprecationReason, "Do not use this thing")
@@ -365,6 +366,48 @@ class DeprecatedDirectiveTest {
365366
assertEquals(firstField.deprecationReason, "Please do not use this field")
366367
}
367368

369+
@Test
370+
fun `bare @deprecated gets the default reason and can be printed`() {
371+
val schema = SchemaParser.newParser()
372+
.schemaString(
373+
"""
374+
type Query {
375+
users(first: Int @deprecated): UserConnection
376+
}
377+
378+
type UserConnection {
379+
edges: [UserEdge!]!
380+
}
381+
382+
type UserEdge {
383+
node: User!
384+
}
385+
386+
enum UserType {
387+
JEDI
388+
BASIC
389+
DROID @deprecated
390+
}
391+
392+
type User {
393+
id: ID!
394+
name: String @deprecated
395+
type: UserType @deprecated(reason: "custom")
396+
}
397+
""")
398+
.resolvers(UsersQueryResolver())
399+
.build()
400+
.makeExecutableSchema()
401+
402+
val name = (schema.getType("User") as GraphQLObjectType).getField("name")
403+
assertEquals("No longer supported", name.getAppliedDirective("deprecated").getArgument("reason")!!.getValue<String>())
404+
405+
val printed = SchemaPrinter().print(schema)
406+
assert(printed.contains("name: String @deprecated(reason : \"No longer supported\")"))
407+
assert(printed.contains("type: UserType @deprecated(reason : \"custom\")"))
408+
assert(printed.contains("DROID @deprecated(reason : \"No longer supported\")"))
409+
}
410+
368411
private enum class UserType {
369412
JEDI,
370413
BASIC,

‎src/test/kotlin/graphql/kickstart/tools/DirectiveTest.kt‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,44 @@ class DirectiveTest {
275275
.build()
276276
}
277277

278+
@Test
279+
fun `should resolve built-in directives`() {
280+
val schema = SchemaParser.newParser()
281+
.schemaString(
282+
"""
283+
input BookFilter @oneOf {
284+
id: Int
285+
name: String
286+
}
287+
288+
type Book {
289+
id: Int!
290+
name: String! @deprecated
291+
}
292+
293+
type Query {
294+
book(filter: BookFilter!): Book
295+
}
296+
""")
297+
.resolvers(BookQueryResolver())
298+
.build()
299+
.makeExecutableSchema()
300+
301+
val filter = schema.getType("BookFilter") as GraphQLInputObjectType
302+
assert(filter.isOneOf)
303+
assertNotNull(filter.getAppliedDirective("oneOf"))
304+
assert((schema.getType("Book") as GraphQLObjectType).getField("name").isDeprecated)
305+
}
306+
307+
private class BookQueryResolver : GraphQLQueryResolver {
308+
fun book(filter: BookFilter): Book? = null
309+
}
310+
311+
private data class BookFilter(
312+
val id: Int?,
313+
val name: String?
314+
)
315+
278316
private class QueryResolver : GraphQLQueryResolver {
279317
fun books(): List<Book> {
280318
return listOf(Book(42L, "Test Book"))

0 commit comments

Comments
 (0)