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
20 changes: 20 additions & 0 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,23 @@ jobs:
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven
run: mvn --batch-mode --update-snapshots verify

# The same jar has to work with the oldest supported graphql-java, so build and test against it too
graphql-java-25:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Setup java
uses: actions/setup-java@v6
with:
java-version: '11'
distribution: 'temurin'
- name: Cache Maven packages
uses: actions/cache@v6
with:
path: ~/.m2
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-m2
- name: Build with Maven
run: mvn --batch-mode --update-snapshots verify -Dgraphql-java.version=25.1
9 changes: 7 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,11 @@
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<configuration>
<args>
<arg>-Werror</arg>
</args>
</configuration>
<executions>
<execution>
<id>compile</id>
Expand All @@ -286,8 +291,8 @@
<artifactId>maven-compiler-plugin</artifactId>
<version>3.16.0</version>
<configuration>
<source>11</source>
<target>11</target>
<release>11</release>
<failOnWarning>true</failOnWarning>
</configuration>
</plugin>

Expand Down
10 changes: 5 additions & 5 deletions src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ internal class SchemaClassScanner(
dictionary
.filter {
it.value.javaType != null
&& it.value.typeClass() != java.lang.Object::class.java
&& !java.util.Map::class.java.isAssignableFrom(it.value.typeClass())
&& it.value.typeClass() != Any::class.java
&& !Map::class.java.isAssignableFrom(it.value.typeClass())
&& it.key !is InputObjectTypeDefinition
&& it.key !is UnionTypeDefinition
}.mapValuesTo(it) { it.value.javaType }
Expand Down Expand Up @@ -355,7 +355,7 @@ internal class SchemaClassScanner(
* Scan a new object for types that haven't been mapped yet.
*/
private fun scanQueueItemForPotentialMatches(item: QueueItem) {
val resolverInfo = if (item.clazz == Object::class.java) {
val resolverInfo = if (item.clazz == Any::class.java) {
getResolverInfoFromTypeDictionary(item.type.name)
?: throw SchemaClassScannerError("The GraphQL schema type '${item.type.name}' maps to a field of type java.lang.Object however there is no matching entry for this type in the type dictionary. You may need to add this type to the dictionary before building the schema.")
} else {
Expand Down Expand Up @@ -391,7 +391,7 @@ internal class SchemaClassScanner(
}

private fun isResolverForSupertype(resolverInfo: NormalResolverInfo, dataClass: Class<*>) =
resolverInfo.dataClassType != Object::class.java && resolverInfo.dataClassType.isAssignableFrom(dataClass)
resolverInfo.dataClassType != Any::class.java && resolverInfo.dataClassType.isAssignableFrom(dataClass)

private fun scanResolverInfoForPotentialMatches(type: ObjectTypeDefinition, resolverInfo: ResolverInfo) {
type.getExtendedFieldDefinitions(extensionDefinitions).forEach { field ->
Expand Down Expand Up @@ -428,7 +428,7 @@ internal class SchemaClassScanner(
if (options.preferGraphQLResolver && realEntry.hasResolverRef()) {
log.warn("The real entry ${realEntry.joinReferences()} is a GraphQLResolver so ignoring this one ${javaType.unwrap()} $reference")
} else {
if (java.util.Map::class.java.isAssignableFrom(javaType.unwrap())) {
if (Map::class.java.isAssignableFrom(javaType.unwrap())) {
throw SchemaClassScannerError("Two different property map classes used for type ${type.name}:\n${realEntry.joinReferences()}\n\n- ${javaType}:\n| ${reference.getDescription()}")
}
throw SchemaClassScannerError("Two different classes used for type ${type.name}:\n${realEntry.joinReferences()}\n\n- ${javaType.typeName}:\n| ${reference.getDescription()}")
Expand Down
10 changes: 7 additions & 3 deletions src/main/kotlin/graphql/kickstart/tools/SchemaParser.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import graphql.schema.*
import graphql.schema.idl.RuntimeWiring
import graphql.schema.idl.ScalarInfo
import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility
import graphql.schema.visibility.BlockedFields
import org.slf4j.LoggerFactory
import kotlin.reflect.KClass

Expand Down Expand Up @@ -69,7 +69,10 @@
*/
fun parseSchemaObjects(): SchemaObjects {
if (!options.introspectionEnabled) {
codeRegistryBuilder.fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY)
// What the deprecated NoIntrospectionGraphqlFieldVisibility does. Its suggested replacement,
// Introspection.enabledJvmWide, would also turn introspection off for every other schema in the JVM.
// TODO deprecate introspectionEnabled and remove this in 15.0.0 (#858)

Check warning on line 74 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=AaFGasbJlC4K5Sa_1bxC&open=AaFGasbJlC4K5Sa_1bxC&pullRequest=849
codeRegistryBuilder.fieldVisibility(BlockedFields.newBlock().addPattern("__.*").build())
}
// this overrides the above introspection enabled setting obviously... todo: add documentation
options.fieldVisibility?.let { codeRegistryBuilder.fieldVisibility(it) }
Expand Down Expand Up @@ -433,6 +436,7 @@
}

// TODO remove this once directives are fully replaced with applied directives
@Suppress("DEPRECATION") // legacy directive argument values
private fun buildDirectives(
directives: List<Directive>,
directiveLocation: Introspection.DirectiveLocation
Expand Down Expand Up @@ -535,7 +539,7 @@
is NonNullType -> GraphQLNonNull(determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
is InputObjectTypeDefinition -> {
log.info("Create input object")
createInputObject(typeDefinition, inputObjects, referencingInputObjects as MutableSet<String>)
createInputObject(typeDefinition, inputObjects, referencingInputObjects)
}
is TypeName -> {
val scalarType = customScalars[typeDefinition.name]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import graphql.schema.DataFetcher
import graphql.schema.DataFetchingEnvironment
import graphql.schema.visibility.GraphqlFieldVisibility
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.reactive.publish
import org.reactivestreams.Publisher
Expand Down Expand Up @@ -45,7 +44,6 @@ data class SchemaParserOptions internal constructor(
fun newOptions() = Builder()

@JvmStatic
@ExperimentalCoroutinesApi
fun defaultOptions() = Builder().build()
}

Expand Down Expand Up @@ -164,7 +162,6 @@ data class SchemaParserOptions internal constructor(
this.allowUndeclaredDirectives = allowUndeclaredDirectives
}

@ExperimentalCoroutinesApi
fun build(): SchemaParserOptions {
val coroutineContextProvider = coroutineContextProvider
?: DefaultCoroutineContextProvider(Dispatchers.Default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
realType is Class<*> && realType.isArray ->
match(potentialMatch, graphQLType.type, realType.componentType)
// e.g. a property map value: the element type is unknown, so match it as Object too
realType == Object::class.java ->
realType == Any::class.java ->
match(potentialMatch, graphQLType.type, realType)
else ->
throw error(potentialMatch, "Java class is not a List or generic type information was lost: $realType")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
}
}

if (java.util.Map::class.java.isAssignableFrom(search.type.unwrap())) {
if (Map::class.java.isAssignableFrom(search.type.unwrap())) {
return MapFieldResolver(field, search, options, search.type.unwrap())
}

Expand Down Expand Up @@ -128,7 +128,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
.filter { !Modifier.isPrivate(it.modifiers) }
// discard any methods that are coming off the root of the class hierarchy
// to avoid issues with duplicate method declarations
.filter { it.declaringClass != Object::class.java }
.filter { it.declaringClass != Any::class.java }
// subscription resolvers must return a publisher
.filter { !search.isSubscription || resolverMethodReturnsPublisher(it) }
.toList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ internal class MapFieldResolver(
val resolvedType = TypeResolver().resolve(mapClass)
val typeParameters = resolvedType.typeParametersFor(Map::class.java)

return typeParameters.elementAtOrElse(1) { Object::class.java }
return typeParameters.elementAtOrElse(1) { Any::class.java }
}

override fun createDataFetcher(): DataFetcher<*> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ internal class MethodFieldResolver(
is ListType -> List::class.java.isAssignableFrom(this.typeResolver.getRawClass(genericParameterType))
&& isConcreteScalarType(environment, type.type, this.typeResolver.resolve(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: 2 additions & 0 deletions src/main/kotlin/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
requires kotlinx.coroutines.reactive;

requires com.graphqljava;
// graphql-java's nullability annotations, only needed at compile time
requires static org.jspecify;

requires com.fasterxml.jackson.core;
requires com.fasterxml.jackson.kotlin;
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<Any>() as Publisher<ExecutionResult>
val data = checkNotNull(result.getData<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<Any>() as Publisher<ExecutionResult>
val data = checkNotNull(result.getData<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<Any>() as Publisher<ExecutionResult>
val data = checkNotNull(result.getData<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<Any>() as Publisher<ExecutionResult>
val data = checkNotNull(result.getData<Publisher<ExecutionResult>>())
val subscriber = TestEnvironment().newManualSubscriber(data)

val subscriberResult = subscriber.requestNextElement() as ExecutionResultImpl
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,9 @@ import graphql.relay.Connection
import graphql.relay.DefaultConnection
import graphql.relay.DefaultPageInfo
import graphql.schema.DataFetchingEnvironment
import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.junit.Test
import java.util.*

@OptIn(ExperimentalCoroutinesApi::class)
class FieldResolverScannerTest {

private val options = defaultOptions()
Expand Down
60 changes: 60 additions & 0 deletions src/test/kotlin/graphql/kickstart/tools/IntrospectionTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package graphql.kickstart.tools

import graphql.GraphQL
import graphql.validation.ValidationError
import graphql.validation.ValidationErrorType
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test

class IntrospectionTest {

private fun graphQL(introspectionEnabled: Boolean): GraphQL {
val schema = SchemaParser.newParser()
.schemaString(
"""
type Query {
name: String!
}
""")
.resolvers(object : GraphQLQueryResolver {
fun name() = "tools"
})
.options(SchemaParserOptions.newOptions().introspectionEnabled(introspectionEnabled).build())
.build()
.makeExecutableSchema()
return GraphQL.newGraphQL(schema).build()
}

@Test
fun `introspection is enabled by default`() {
val data = assertNoGraphQlErrors(graphQL(introspectionEnabled = true)) {
"{ __schema { queryType { name } } }"
}

assertEquals(mapOf("queryType" to mapOf("name" to "Query")), data["__schema"])
}

@Test
fun `disabling introspection hides the fields of introspection types`() {
val gql = graphQL(introspectionEnabled = false)

listOf("{ __schema { queryType { name } } }", "{ __type(name: \"Query\") { name } }").forEach { query ->
val result = gql.execute(query)

assertNull(result.getData())
assertEquals(
listOf(ValidationErrorType.FieldUndefined),
result.errors.map { (it as ValidationError).validationErrorType })
}
}

@Test
fun `disabling introspection keeps __typename`() {
val data = assertNoGraphQlErrors(graphQL(introspectionEnabled = false)) {
"{ name __typename }"
}

assertEquals(mapOf("name" to "tools", "__typename" to "Query"), data)
}
}
Loading
Loading