Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<kotlin.version>2.3.10</kotlin.version>
<kotlin-coroutines.version>1.10.2</kotlin-coroutines.version>
<jackson.version>2.22.3</jackson.version>
<graphql-java.version>25.1</graphql-java.version>
<graphql-java.version>26.1</graphql-java.version>
<reactive-streams.version>1.0.4</reactive-streams.version>

<maven.compiler.source>${java.version}</maven.compiler.source>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ internal abstract class DictionaryTypeResolver(
}

override fun getType(env: TypeResolutionEnvironment): GraphQLObjectType? {
val clazz = env.getObject<Any>().javaClass
val clazz = env.getObject<Any>()!!.javaClass
val name = getTypeDefinition(clazz)?.name ?: clazz.simpleName
return env.schema.getObjectType(name) ?: throw TypeResolverError(getError(name))
}
Expand Down
8 changes: 6 additions & 2 deletions src/main/kotlin/graphql/kickstart/tools/SchemaObjects.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
val codeRegistryBuilder: GraphQLCodeRegistry.Builder,
val description: String?
) {
// TODO change dictionary to Set<GraphQLNamedType> in the next major version and remove this cast

Check warning on line 17 in src/main/kotlin/graphql/kickstart/tools/SchemaObjects.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=graphql-java-kickstart_graphql-java-tools&issues=AaECUsc5xj2Jd9yy7DQA&open=AaECUsc5xj2Jd9yy7DQA&pullRequest=818
@Suppress("UNCHECKED_CAST")
private fun namedDictionary(): Set<GraphQLNamedType> = dictionary as Set<GraphQLNamedType>

/**
* Makes a GraphQLSchema with query, mutation and subscription.
*/
Expand All @@ -23,7 +27,7 @@
.query(query)
.mutation(mutation)
.subscription(subscription)
.additionalTypes(dictionary)
.additionalTypes(namedDictionary())
.additionalDirectives(directives)
.codeRegistry(codeRegistryBuilder.build())
.build()
Expand All @@ -35,6 +39,6 @@
fun toReadOnlySchema(): GraphQLSchema = GraphQLSchema.newSchema()
.description(description)
.query(query)
.additionalTypes(dictionary)
.additionalTypes(namedDictionary())
.build()
}
34 changes: 31 additions & 3 deletions src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package graphql.kickstart.tools

import graphql.Directives
import graphql.introspection.Introspection
import graphql.introspection.Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION
import graphql.kickstart.tools.directive.DirectiveWiringHelper
Expand All @@ -8,7 +9,6 @@
import graphql.kickstart.tools.util.unwrap
import graphql.language.*
import graphql.schema.*
import graphql.schema.idl.DirectiveInfo
import graphql.schema.idl.RuntimeWiring
import graphql.schema.idl.ScalarInfo
import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility
Expand Down Expand Up @@ -351,7 +351,7 @@
private fun buildAppliedDirectives(directives: List<Directive>): Array<GraphQLAppliedDirective> {
return directives.map { directive ->
val graphQLDirective = schemaDirectives.find { d -> d.name == directive.name }
?: DirectiveInfo.GRAPHQL_SPECIFICATION_DIRECTIVE_MAP[directive.name]
?: BUILT_IN_DIRECTIVES[directive.name]
?: throw SchemaError("Found applied directive ${directive.name} without corresponding directive definition.")
val graphQLArguments = graphQLDirective.arguments.associateBy { it.name }

Expand All @@ -374,6 +374,22 @@
)
}
}
.apply {
// a bare @deprecated has no "reason" argument, which makes SchemaPrinter throw a NPE.
// copy the default from the directive definition (for the built-in one: "No longer supported").
if (directive.name == Directives.DeprecatedDirective.name && directive.arguments.none { it.name == "reason" }) {
val reasonArgument = graphQLArguments["reason"]
if (reasonArgument != null && reasonArgument.hasSetDefaultValue()) {
argument(GraphQLAppliedDirectiveArgument.newArgument()
.name(reasonArgument.name)
.type(reasonArgument.type)
.description(reasonArgument.description)
.inputValueWithState(reasonArgument.argumentDefaultValue)
.build()
)
}
}
}
.build()
}.toTypedArray()
}
Expand All @@ -391,7 +407,7 @@
if (repeatable || !names.contains(directive.name)) {
names.add(directive.name)
val graphQLDirective = this.schemaDirectives.find { d -> d.name == directive.name }
?: DirectiveInfo.GRAPHQL_SPECIFICATION_DIRECTIVE_MAP[directive.name]
?: BUILT_IN_DIRECTIVES[directive.name]
?: throw SchemaError("Found applied directive ${directive.name} without corresponding directive definition.")
val graphQLArguments = graphQLDirective.arguments.associateBy { it.name }
output.add(
Expand Down Expand Up @@ -521,4 +537,16 @@

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

// Built from the individual constants rather than Directives.BUILT_IN_DIRECTIVES_MAP (added in graphql-java 26)
// so that the library keeps working with graphql-java 25. TODO replace with Directives.BUILT_IN_DIRECTIVES_MAP once we deploy a major version

Check warning on line 541 in src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Complete the task associated to this TODO comment.

See more on https://sonarcloud.io/project/issues?id=graphql-java-kickstart_graphql-java-tools&issues=AaECUsgDxj2Jd9yy7DQB&open=AaECUsgDxj2Jd9yy7DQB&pullRequest=818
private val BUILT_IN_DIRECTIVES = listOf(
Directives.IncludeDirective,
Directives.SkipDirective,
Directives.DeprecatedDirective,
Directives.SpecifiedByDirective,
Directives.OneOfDirective,
Directives.DeferDirective,
Directives.ExperimentalDisableErrorPropagationDirective
).associateBy { it.name }

const val DEFAULT_DEPRECATION_MESSAGE = "No longer supported"
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package graphql.kickstart.tools.relay

import graphql.kickstart.tools.SchemaError
import graphql.kickstart.tools.TypeDefinitionFactory
import graphql.language.*

Expand Down Expand Up @@ -54,7 +55,7 @@ class RelayConnectionFactory : TypeDefinitionFactory {
.fieldDefinition(FieldDefinition("pageInfo", TypeName("PageInfo")))
.build()

private fun createEdgeDefinition(connectionType: String, nodeType: String?): ObjectTypeDefinition =
private fun createEdgeDefinition(connectionType: String, nodeType: String): ObjectTypeDefinition =
ObjectTypeDefinition.newObjectTypeDefinition()
.name(connectionType + "Edge")
.fieldDefinition(FieldDefinition("cursor", TypeName("String")))
Expand All @@ -70,9 +71,9 @@ class RelayConnectionFactory : TypeDefinitionFactory {
.fieldDefinition(FieldDefinition("endCursor", TypeName("String")))
.build()

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

private fun Directive.withField(field: FieldDefinition): DirectiveWithField {
return DirectiveWithField(field, this.name, this.arguments, this.sourceLocation, this.comments)
Expand All @@ -92,9 +93,9 @@ class RelayConnectionFactory : TypeDefinitionFactory {
fun getTypeName(): String {
val type = field.type
if (type is NonNullType) {
return (type.type as TypeName).name
return (type.type as TypeName).name!!
}
return (field.type as TypeName).name
return (field.type as TypeName).name!!
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ internal class MethodFieldResolver(
is ListType -> List::class.java.isAssignableFrom(this.genericType.getRawClass(genericParameterType))
&& isConcreteScalarType(environment, type.type, this.genericType.unwrapGenericType(genericParameterType))

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

is NonNullType -> isConcreteScalarType(environment, type.type, genericParameterType)
Expand Down
2 changes: 1 addition & 1 deletion src/main/kotlin/graphql/kickstart/tools/util/Utils.kt
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ internal val Class<*>.declaredNonProxyMethods: List<JavaMethod>

internal fun getDocumentation(node: AbstractNode<*>, options: SchemaParserOptions): String? =
when {
node is AbstractDescribedNode<*> && node.description != null -> node.description.content
node is AbstractDescribedNode<*> && node.description != null -> node.description?.content
!options.useCommentsForDescriptions -> null
node.comments.isNullOrEmpty() -> null
else -> node.comments.asSequence()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ class PlaceTest {
val executionInput = ExecutionInput.newExecutionInput().query(query).build()
val result = graphql.execute(executionInput)

assert(result.getData<Map<String, List<*>>>()["places1"]?.size == 3)
assert(result.getData<Map<String, List<*>>>()["places2"]?.size == 2)
assert(result.getData<Map<String, List<*>>>()!!["places1"]?.size == 3)
assert(result.getData<Map<String, List<*>>>()!!["places2"]?.size == 2)
}
}

Expand Down
51 changes: 47 additions & 4 deletions src/test/kotlin/graphql/kickstart/tools/DeprecatedDirectiveTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import graphql.schema.DataFetchingEnvironment
import graphql.schema.GraphQLEnumType
import graphql.schema.GraphQLInputObjectType
import graphql.schema.GraphQLObjectType
import graphql.schema.idl.SchemaPrinter
import org.junit.Test

class DeprecatedDirectiveTest {
Expand Down Expand Up @@ -111,7 +112,7 @@ class DeprecatedDirectiveTest {
val userTypeEnum = schema.getType("UserType") as GraphQLEnumType
val droidValue = userTypeEnum.getValue("DROID")

assert(droidValue.isDeprecated)
assert(droidValue!!.isDeprecated)
assertEquals(droidValue.deprecationReason, "No longer supported")
}

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

assert(droidValue.isDeprecated)
assert(droidValue!!.isDeprecated)
assertEquals(droidValue.deprecationReason, "This value is no longer used")
}

Expand Down Expand Up @@ -250,7 +251,7 @@ class DeprecatedDirectiveTest {
.makeExecutableSchema()

val directive = schema.getDirective("uppercase")
val argument = directive.getArgument("firstCharacterOnly")
val argument = directive!!.getArgument("firstCharacterOnly")

assert(argument.isDeprecated)
assertEquals(argument.deprecationReason, "No longer supported")
Expand Down Expand Up @@ -285,7 +286,7 @@ class DeprecatedDirectiveTest {
.makeExecutableSchema()

val directive = schema.getDirective("uppercase")
val argument = directive.getArgument("firstCharacterOnly")
val argument = directive!!.getArgument("firstCharacterOnly")

assert(argument.isDeprecated)
assertEquals(argument.deprecationReason, "Do not use this thing")
Expand Down Expand Up @@ -365,6 +366,48 @@ class DeprecatedDirectiveTest {
assertEquals(firstField.deprecationReason, "Please do not use this field")
}

@Test
fun `bare @deprecated gets the default reason and can be printed`() {
val schema = SchemaParser.newParser()
.schemaString(
"""
type Query {
users(first: Int @deprecated): UserConnection
}

type UserConnection {
edges: [UserEdge!]!
}

type UserEdge {
node: User!
}

enum UserType {
JEDI
BASIC
DROID @deprecated
}

type User {
id: ID!
name: String @deprecated
type: UserType @deprecated(reason: "custom")
}
""")
.resolvers(UsersQueryResolver())
.build()
.makeExecutableSchema()

val name = (schema.getType("User") as GraphQLObjectType).getField("name")
assertEquals("No longer supported", name.getAppliedDirective("deprecated").getArgument("reason")!!.getValue<String>())

val printed = SchemaPrinter().print(schema)
assert(printed.contains("name: String @deprecated(reason : \"No longer supported\")"))
assert(printed.contains("type: UserType @deprecated(reason : \"custom\")"))
assert(printed.contains("DROID @deprecated(reason : \"No longer supported\")"))
}

private enum class UserType {
JEDI,
BASIC,
Expand Down
38 changes: 38 additions & 0 deletions src/test/kotlin/graphql/kickstart/tools/DirectiveTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,44 @@ class DirectiveTest {
.build()
}

@Test
fun `should resolve built-in directives`() {
val schema = SchemaParser.newParser()
.schemaString(
"""
input BookFilter @oneOf {
id: Int
name: String
}

type Book {
id: Int!
name: String! @deprecated
}

type Query {
book(filter: BookFilter!): Book
}
""")
.resolvers(BookQueryResolver())
.build()
.makeExecutableSchema()

val filter = schema.getType("BookFilter") as GraphQLInputObjectType
assert(filter.isOneOf)
assertNotNull(filter.getAppliedDirective("oneOf"))
assert((schema.getType("Book") as GraphQLObjectType).getField("name").isDeprecated)
}

private class BookQueryResolver : GraphQLQueryResolver {
fun book(filter: BookFilter): Book? = null
}

private data class BookFilter(
val id: Int?,
val name: String?
)

private class QueryResolver : GraphQLQueryResolver {
fun books(): List<Book> {
return listOf(Book(42L, "Test Book"))
Expand Down
4 changes: 2 additions & 2 deletions src/test/kotlin/graphql/kickstart/tools/EndToEndSpecHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ class Subscription : GraphQLSubscriptionResolver {

fun onItemCreatedCoroutineChannel(env: DataFetchingEnvironment): ReceiveChannel<Item> {
val channel = Channel<Item>(1)
channel.trySend(env.graphQlContext["newItem"])
channel.trySend(env.graphQlContext.get<Item>("newItem")!!)
return channel
}

Expand All @@ -393,7 +393,7 @@ class Subscription : GraphQLSubscriptionResolver {
suspend fun onItemCreatedCoroutineChannelAndSuspendFunction(env: DataFetchingEnvironment): ReceiveChannel<Item> {
return coroutineScope {
val channel = Channel<Item>(1)
channel.trySend(env.graphQlContext["newItem"])
channel.trySend(env.graphQlContext.get<Item>("newItem")!!)
channel
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/test/kotlin/graphql/kickstart/tools/EndToEndTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ class EndToEndTest {
.graphQLContext(mapOf("newItem" to newItem))
.variables(mapOf()))

val data = result.getData() as Publisher<ExecutionResult>
val data = result.getData<Any>() as Publisher<ExecutionResult>
val latch = CountDownLatch(1)
data.subscribe(object : Subscriber<ExecutionResult> {
override fun onNext(item: ExecutionResult?) {
Expand Down Expand Up @@ -117,7 +117,7 @@ class EndToEndTest {
.graphQLContext(mapOf("newItem" to newItem))
.variables(mapOf()))

val data = result.getData() as Publisher<ExecutionResult>
val data = result.getData<Any>() as Publisher<ExecutionResult>
val latch = CountDownLatch(1)
data.subscribe(object : Subscriber<ExecutionResult> {
override fun onNext(item: ExecutionResult?) {
Expand Down Expand Up @@ -639,7 +639,7 @@ class EndToEndTest {
.graphQLContext(mapOf("newItem" to newItem))
.variables(mapOf()))

val data = result.getData() as Publisher<ExecutionResult>
val data = result.getData<Any>() as Publisher<ExecutionResult>
val subscriber = TestEnvironment().newManualSubscriber(data)

val subscriberResult = subscriber.requestNextElement() as ExecutionResultImpl
Expand Down Expand Up @@ -667,7 +667,7 @@ class EndToEndTest {
.graphQLContext(mapOf("newItem" to newItem))
.variables(mapOf()))

val data = result.getData() as Publisher<ExecutionResult>
val data = result.getData<Any>() as Publisher<ExecutionResult>
val subscriber = TestEnvironment().newManualSubscriber(data)

val subscriberResult = subscriber.requestNextElement() as ExecutionResultImpl
Expand Down
Loading
Loading