Skip to content

Commit e4ed1bd

Browse files
authored
Merge pull request #763 from graphql-java-kickstart/664-scan-directive-scalar-arguments
Scan directives while parsing schema
2 parents 949cf04 + 54f21e2 commit e4ed1bd

File tree

6 files changed

+142
-115
lines changed

6 files changed

+142
-115
lines changed

src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt

+33-16
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ internal class SchemaClassScanner(
103103
} while (scanQueue())
104104
}
105105

106+
handleDirectives()
107+
106108
return validateAndCreateResult(rootTypeHolder)
107109
}
108110

@@ -131,6 +133,30 @@ internal class SchemaClassScanner(
131133
scanResolverInfoForPotentialMatches(rootType.type, rootType.resolverInfo)
132134
}
133135

136+
137+
private fun handleDirectives() {
138+
for (directive in directiveDefinitions) {
139+
for (input in directive.inputValueDefinitions) {
140+
handleDirectiveInput(input.type)
141+
}
142+
}
143+
}
144+
145+
private fun handleDirectiveInput(inputType: Type<*>) {
146+
val inputTypeName = (inputType.unwrap() as TypeName).name
147+
val typeDefinition = ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS[inputTypeName]
148+
?: definitionsByName[inputTypeName]
149+
?: error("No ${TypeDefinition::class.java.simpleName} for type name $inputTypeName")
150+
when (typeDefinition) {
151+
is ScalarTypeDefinition -> handleFoundScalarType(typeDefinition)
152+
is InputObjectTypeDefinition -> {
153+
for (input in typeDefinition.inputValueDefinitions) {
154+
handleDirectiveInput(input.type)
155+
}
156+
}
157+
}
158+
}
159+
134160
private fun validateAndCreateResult(rootTypeHolder: RootTypesHolder): ScannedSchemaObjects {
135161
initialDictionary
136162
.filter { !it.value.accessed }
@@ -280,16 +306,9 @@ internal class SchemaClassScanner(
280306
}
281307
}
282308

283-
private fun handleFoundType(match: TypeClassMatcher.Match) {
284-
when (match) {
285-
is TypeClassMatcher.ScalarMatch -> {
286-
handleFoundScalarType(match.type)
287-
}
288-
289-
is TypeClassMatcher.ValidMatch -> {
290-
handleFoundType(match.type, match.javaType, match.reference)
291-
}
292-
}
309+
private fun handleFoundType(match: TypeClassMatcher.Match) = when (match) {
310+
is TypeClassMatcher.ScalarMatch -> handleFoundScalarType(match.type)
311+
is TypeClassMatcher.ValidMatch -> handleFoundType(match.type, match.javaType, match.reference)
293312
}
294313

295314
private fun handleFoundScalarType(type: ScalarTypeDefinition) {
@@ -392,12 +411,10 @@ internal class SchemaClassScanner(
392411
val filteredMethods = methods.filter {
393412
it.name == name || it.name == "get${name.replaceFirstChar(Char::titlecase)}"
394413
}.sortedBy { it.name.length }
395-
return filteredMethods.find {
396-
!it.isSynthetic
397-
}?.genericReturnType ?: filteredMethods.firstOrNull(
398-
)?.genericReturnType ?: clazz.fields.find {
399-
it.name == name
400-
}?.genericType
414+
415+
return filteredMethods.find { !it.isSynthetic }?.genericReturnType
416+
?: filteredMethods.firstOrNull()?.genericReturnType
417+
?: clazz.fields.find { it.name == name }?.genericType
401418
}
402419

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

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

+72-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package graphql.kickstart.tools
22

3+
import graphql.Scalars
34
import graphql.introspection.Introspection
45
import graphql.introspection.Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION
56
import graphql.kickstart.tools.directive.DirectiveWiringHelper
@@ -335,7 +336,7 @@ class SchemaParser internal constructor(
335336
it.arguments.forEach { arg ->
336337
argument(GraphQLAppliedDirectiveArgument.newArgument()
337338
.name(arg.name)
338-
.type(directiveWiringHelper.buildDirectiveInputType(arg.value))
339+
.type(buildDirectiveInputType(arg.value))
339340
.valueLiteral(arg.value)
340341
.build()
341342
)
@@ -350,7 +351,76 @@ class SchemaParser internal constructor(
350351
directives: List<Directive>,
351352
directiveLocation: Introspection.DirectiveLocation
352353
): Array<GraphQLDirective> {
353-
return directiveWiringHelper.buildDirectives(directives, directiveLocation).toTypedArray()
354+
val names = mutableSetOf<String>()
355+
val output = mutableListOf<GraphQLDirective>()
356+
357+
for (directive in directives) {
358+
val repeatable = directiveDefinitions.find { it.name.equals(directive.name) }?.isRepeatable ?: false
359+
if (repeatable || !names.contains(directive.name)) {
360+
names.add(directive.name)
361+
output.add(
362+
GraphQLDirective.newDirective()
363+
.name(directive.name)
364+
.description(getDocumentation(directive, options))
365+
.comparatorRegistry(runtimeWiring.comparatorRegistry)
366+
.validLocation(directiveLocation)
367+
.repeatable(repeatable)
368+
.apply {
369+
directive.arguments.forEach { arg ->
370+
argument(GraphQLArgument.newArgument()
371+
.name(arg.name)
372+
.type(buildDirectiveInputType(arg.value))
373+
// TODO remove this once directives are fully replaced with applied directives
374+
.valueLiteral(arg.value)
375+
.build())
376+
}
377+
}
378+
.build()
379+
)
380+
}
381+
}
382+
383+
return output.toTypedArray()
384+
}
385+
386+
private fun buildDirectiveInputType(value: Value<*>): GraphQLInputType? {
387+
return when (value) {
388+
is NullValue -> Scalars.GraphQLString
389+
is FloatValue -> Scalars.GraphQLFloat
390+
is StringValue -> Scalars.GraphQLString
391+
is IntValue -> Scalars.GraphQLInt
392+
is BooleanValue -> Scalars.GraphQLBoolean
393+
is ArrayValue -> GraphQLList.list(buildDirectiveInputType(getArrayValueWrappedType(value)))
394+
// TODO to implement this we'll need to "observe" directive's input types + match them here based on their fields(?)
395+
else -> throw SchemaError("Directive values of type '${value::class.simpleName}' are not supported yet.")
396+
}
397+
}
398+
399+
private fun getArrayValueWrappedType(value: ArrayValue): Value<*> {
400+
// empty array [] is equivalent to [null]
401+
if (value.values.isEmpty()) {
402+
return NullValue.newNullValue().build()
403+
}
404+
405+
// get rid of null values
406+
val nonNullValueList = value.values.filter { v -> v !is NullValue }
407+
408+
// [null, null, ...] unwrapped is null
409+
if (nonNullValueList.isEmpty()) {
410+
return NullValue.newNullValue().build()
411+
}
412+
413+
// make sure the array isn't polymorphic
414+
val distinctTypes = nonNullValueList
415+
.map { it::class.java }
416+
.distinct()
417+
418+
if (distinctTypes.size > 1) {
419+
throw SchemaError("Arrays containing multiple types of values are not supported yet.")
420+
}
421+
422+
// peek at first value, value exists and is assured to be non-null
423+
return nonNullValueList[0]
354424
}
355425

356426
private fun determineOutputType(typeDefinition: Type<*>, inputObjects: List<GraphQLInputObjectType>) =

src/main/kotlin/graphql/kickstart/tools/TypeClassMatcher.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
9393

9494
private fun isListType(realType: ParameterizedType, potentialMatch: PotentialMatch) = isListType(realType, potentialMatch.generic)
9595

96-
internal interface Match
96+
internal sealed interface Match
9797

9898
internal data class ScalarMatch(val type: ScalarTypeDefinition) : Match
9999

src/main/kotlin/graphql/kickstart/tools/directive/DirectiveWiringHelper.kt

+15-89
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
package graphql.kickstart.tools.directive
22

3-
import graphql.Scalars
43
import graphql.introspection.Introspection
54
import graphql.introspection.Introspection.DirectiveLocation.*
6-
import graphql.kickstart.tools.SchemaError
75
import graphql.kickstart.tools.SchemaParserOptions
86
import graphql.kickstart.tools.directive.SchemaDirectiveWiringEnvironmentImpl.Parameters
9-
import graphql.kickstart.tools.util.getDocumentation
10-
import graphql.language.*
7+
import graphql.language.DirectiveDefinition
8+
import graphql.language.NamedNode
9+
import graphql.language.NodeParentTree
1110
import graphql.schema.*
1211
import graphql.schema.idl.RuntimeWiring
1312
import graphql.schema.idl.SchemaDirectiveWiring
@@ -75,23 +74,20 @@ class DirectiveWiringHelper(
7574
}
7675

7776
private fun <T : GraphQLDirectiveContainer> wireDirectives(wrapper: WiringWrapper<T>): T {
78-
val directivesContainer = wrapper.graphQlType.definition as DirectivesContainer<*>
79-
val directives = buildDirectives(directivesContainer.directives, wrapper.directiveLocation)
80-
val directivesByName = directives.associateBy { it.name }
8177
var output = wrapper.graphQlType
8278
// first the specific named directives
8379
wrapper.graphQlType.appliedDirectives.forEach { appliedDirective ->
84-
val env = buildEnvironment(wrapper, directives, directivesByName[appliedDirective.name], appliedDirective)
80+
val env = buildEnvironment(wrapper, appliedDirective)
8581
val wiring = runtimeWiring.registeredDirectiveWiring[appliedDirective.name]
8682
wiring?.let { output = wrapper.invoker(it, env) }
8783
}
8884
// now call any statically added to the runtime
8985
runtimeWiring.directiveWiring.forEach { staticWiring ->
90-
val env = buildEnvironment(wrapper, directives, null, null)
86+
val env = buildEnvironment(wrapper)
9187
output = wrapper.invoker(staticWiring, env)
9288
}
9389
// wiring factory is last (if present)
94-
val env = buildEnvironment(wrapper, directives, null, null)
90+
val env = buildEnvironment(wrapper)
9591
if (runtimeWiring.wiringFactory.providesSchemaDirectiveWiring(env)) {
9692
val factoryWiring = runtimeWiring.wiringFactory.getSchemaDirectiveWiring(env)
9793
output = wrapper.invoker(factoryWiring, env)
@@ -100,102 +96,32 @@ class DirectiveWiringHelper(
10096
return output
10197
}
10298

103-
fun buildDirectives(directives: List<Directive>, directiveLocation: Introspection.DirectiveLocation): List<GraphQLDirective> {
104-
val names = mutableSetOf<String>()
105-
val output = mutableListOf<GraphQLDirective>()
106-
107-
for (directive in directives) {
108-
val repeatable = directiveDefinitions.find { it.name.equals(directive.name) }?.isRepeatable ?: false
109-
if (repeatable || !names.contains(directive.name)) {
110-
names.add(directive.name)
111-
output.add(
112-
GraphQLDirective.newDirective()
113-
.name(directive.name)
114-
.description(getDocumentation(directive, options))
115-
.comparatorRegistry(runtimeWiring.comparatorRegistry)
116-
.validLocation(directiveLocation)
117-
.repeatable(repeatable)
118-
.apply {
119-
directive.arguments.forEach { arg ->
120-
argument(GraphQLArgument.newArgument()
121-
.name(arg.name)
122-
.type(buildDirectiveInputType(arg.value))
123-
// TODO remove this once directives are fully replaced with applied directives
124-
.valueLiteral(arg.value)
125-
.build())
126-
}
127-
}
128-
.build()
129-
)
130-
}
131-
}
132-
133-
return output
134-
}
135-
136-
private fun <T : GraphQLDirectiveContainer> buildEnvironment(wrapper: WiringWrapper<T>, directives: List<GraphQLDirective>, directive: GraphQLDirective?, appliedDirective: GraphQLAppliedDirective?): SchemaDirectiveWiringEnvironmentImpl<T> {
99+
private fun <T : GraphQLDirectiveContainer> buildEnvironment(wrapper: WiringWrapper<T>, appliedDirective: GraphQLAppliedDirective? = null): SchemaDirectiveWiringEnvironmentImpl<T> {
100+
val type = wrapper.graphQlType
101+
val directive = appliedDirective?.let { d -> type.directives.find { it.name == d.name } }
137102
val nodeParentTree = buildAstTree(*listOfNotNull(
138103
wrapper.fieldsContainer?.definition,
139104
wrapper.inputFieldsContainer?.definition,
140105
wrapper.enumType?.definition,
141106
wrapper.fieldDefinition?.definition,
142-
wrapper.graphQlType.definition
107+
type.definition
143108
).filterIsInstance<NamedNode<*>>()
144109
.toTypedArray())
145110
val elementParentTree = buildRuntimeTree(*listOfNotNull(
146111
wrapper.fieldsContainer,
147112
wrapper.inputFieldsContainer,
148113
wrapper.enumType,
149114
wrapper.fieldDefinition,
150-
wrapper.graphQlType
115+
type
151116
).toTypedArray())
152-
val params = when (wrapper.graphQlType) {
153-
is GraphQLFieldDefinition -> schemaDirectiveParameters.newParams(wrapper.graphQlType, wrapper.fieldsContainer, nodeParentTree, elementParentTree)
117+
val params = when (type) {
118+
is GraphQLFieldDefinition -> schemaDirectiveParameters.newParams(type, wrapper.fieldsContainer, nodeParentTree, elementParentTree)
154119
is GraphQLArgument -> schemaDirectiveParameters.newParams(wrapper.fieldDefinition, wrapper.fieldsContainer, nodeParentTree, elementParentTree)
155120
// object or interface
156-
is GraphQLFieldsContainer -> schemaDirectiveParameters.newParams(wrapper.graphQlType, nodeParentTree, elementParentTree)
121+
is GraphQLFieldsContainer -> schemaDirectiveParameters.newParams(type, nodeParentTree, elementParentTree)
157122
else -> schemaDirectiveParameters.newParams(nodeParentTree, elementParentTree)
158123
}
159-
return SchemaDirectiveWiringEnvironmentImpl(wrapper.graphQlType, directives, wrapper.graphQlType.appliedDirectives, directive, appliedDirective, params)
160-
}
161-
162-
fun buildDirectiveInputType(value: Value<*>): GraphQLInputType? {
163-
return when (value) {
164-
is NullValue -> Scalars.GraphQLString
165-
is FloatValue -> Scalars.GraphQLFloat
166-
is StringValue -> Scalars.GraphQLString
167-
is IntValue -> Scalars.GraphQLInt
168-
is BooleanValue -> Scalars.GraphQLBoolean
169-
is ArrayValue -> GraphQLList.list(buildDirectiveInputType(getArrayValueWrappedType(value)))
170-
else -> throw SchemaError("Directive values of type '${value::class.simpleName}' are not supported yet.")
171-
}
172-
}
173-
174-
private fun getArrayValueWrappedType(value: ArrayValue): Value<*> {
175-
// empty array [] is equivalent to [null]
176-
if (value.values.isEmpty()) {
177-
return NullValue.newNullValue().build()
178-
}
179-
180-
// get rid of null values
181-
val nonNullValueList = value.values.filter { v -> v !is NullValue }
182-
183-
// [null, null, ...] unwrapped is null
184-
if (nonNullValueList.isEmpty()) {
185-
return NullValue.newNullValue().build()
186-
}
187-
188-
// make sure the array isn't polymorphic
189-
val distinctTypes = nonNullValueList
190-
.map { it::class.java }
191-
.distinct()
192-
193-
if (distinctTypes.size > 1) {
194-
throw SchemaError("Arrays containing multiple types of values are not supported yet.")
195-
}
196-
197-
// peek at first value, value exists and is assured to be non-null
198-
return nonNullValueList[0]
124+
return SchemaDirectiveWiringEnvironmentImpl(type, type.directives, type.appliedDirectives, directive, appliedDirective, params)
199125
}
200126

201127
private fun buildAstTree(vararg nodes: NamedNode<*>): NodeParentTree<NamedNode<*>> {

src/test/kotlin/graphql/kickstart/tools/InaccessibleFieldResolverTest.kt

-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import graphql.ExceptionWhileDataFetching
44
import graphql.GraphQL
55
import graphql.execution.AsyncExecutionStrategy
66
import graphql.schema.GraphQLSchema
7-
import org.junit.Ignore
87
import org.junit.Test
98
import java.util.*
109

@@ -16,7 +15,6 @@ import java.util.*
1615
class InaccessibleFieldResolverTest {
1716

1817
@Test
19-
@Ignore // TODO enable test after upgrading to 17
2018
fun `private field from closed module is not accessible`() {
2119
val schema: GraphQLSchema = SchemaParser.newParser()
2220
.schemaString(

0 commit comments

Comments
 (0)