@@ -62,10 +62,7 @@ class SchemaParser internal constructor(
6262 private val codeRegistryBuilder = GraphQLCodeRegistry .newCodeRegistry()
6363 private val directiveWiringHelper = DirectiveWiringHelper (runtimeWiring, codeRegistryBuilder)
6464
65- // by name, in the order they're declared
66- private val schemaDirectives = mutableMapOf<String , GraphQLDirective >()
67- // set while createDirectives runs, see there
68- private var finishDirective: (String ) -> Unit = {}
65+ private lateinit var schemaDirectives: SchemaDirectives
6966
7067 /* *
7168 * Parses the given schema with respect to the given dictionary and returns GraphQL objects.
@@ -115,7 +112,7 @@ class SchemaParser internal constructor(
115112 // Directives applied to the schema itself, e.g. `extend schema @link(...)`. Only GraphQLAppliedDirectives are built here, not
116113 // the deprecated GraphQLDirective ones: graphql-java doesn't resolve type references in those, so enum arguments would fail.
117114 val schemaAppliedDirectives = buildAppliedDirectives(rootInfo.getDirectives()).toList()
118- return SchemaObjects (query, mutation, subscription, types, schemaDirectives.values.toSet() , codeRegistryBuilder, rootInfo.getDescription(), schemaAppliedDirectives)
115+ return SchemaObjects (query, mutation, subscription, types, schemaDirectives.all , codeRegistryBuilder, rootInfo.getDescription(), schemaAppliedDirectives)
119116 }
120117
121118 /* *
@@ -326,25 +323,10 @@ class SchemaParser internal constructor(
326323 }
327324
328325 private fun createDirectives (inputObjects : MutableList <GraphQLInputObjectType >) {
329- // The input objects used by directive arguments are built here, and they need the directives applied to them. So every
330- // directive is first built with type references to input objects, then rebuilt with the input objects the first time it's
331- // looked up. Only a directive applied within its own argument types gets its first build there.
332- directiveDefinitions.forEach { definition ->
333- schemaDirectives[definition.name] =
334- createDirective(definition) { determineType(GraphQLInputType ::class , it, permittedTypesForInputObject, inputObjects) as GraphQLInputType }
335- }
336- val unfinishedDefinitions = directiveDefinitions.associateByTo(mutableMapOf ()) { it.name }
337- // shared, so that an input object that's still being built isn't built again for another directive
338- val referencingInputObjects = mutableSetOf<String >()
339- finishDirective = { name ->
340- unfinishedDefinitions.remove(name)?.let { definition ->
341- schemaDirectives[name] = createDirective(definition) { determineInputType(it, inputObjects, referencingInputObjects) }
342- }
343- }
344- directiveDefinitions.forEach { finishDirective(it.name) }
345- finishDirective = {}
326+ schemaDirectives = SchemaDirectives (inputObjects)
327+ directiveDefinitions.forEach { schemaDirectives.find(it.name) }
346328 // because the arguments can have directives too, we attach them only after the directives themselves are created
347- schemaDirectives.replaceAll { _, d ->
329+ schemaDirectives.update { d ->
348330 val arguments = d.arguments.map { a -> a.transform {
349331 it.withAppliedDirectives(* buildAppliedDirectives(a.definition!! .directives))
350332 .withDirectives(* buildDirectives(a.definition!! .directives, Introspection .DirectiveLocation .OBJECT ))
@@ -353,6 +335,32 @@ class SchemaParser internal constructor(
353335 }
354336 }
355337
338+ /* *
339+ * The directive definitions. The input objects used by their arguments need the directives applied to them, so each directive
340+ * is first built with type references to input objects, then rebuilt with the input objects the first time it's looked up.
341+ * Only a directive applied within its own argument types gets its first build there.
342+ */
343+ private inner class SchemaDirectives (private val inputObjects : MutableList <GraphQLInputObjectType >) {
344+ // by name, in the order they're declared
345+ private val byName = directiveDefinitions.associateTo(linkedMapOf()) { definition ->
346+ definition.name to createDirective(definition) { determineType(GraphQLInputType ::class , it, permittedTypesForInputObject, inputObjects) as GraphQLInputType }
347+ }
348+ private val unfinished = directiveDefinitions.associateByTo(mutableMapOf ()) { it.name }
349+ // shared, so that an input object that's still being built isn't built again for another directive
350+ private val referencingInputObjects = mutableSetOf<String >()
351+
352+ val all: Set <GraphQLDirective > get() = byName.values.toSet()
353+
354+ fun find (name : String ): GraphQLDirective ? {
355+ unfinished.remove(name)?.let { definition ->
356+ byName[name] = createDirective(definition) { determineInputType(it, inputObjects, referencingInputObjects) }
357+ }
358+ return byName[name]
359+ }
360+
361+ fun update (transform : (GraphQLDirective ) -> GraphQLDirective ) = byName.replaceAll { _, directive -> transform(directive) }
362+ }
363+
356364 private fun createDirective (definition : DirectiveDefinition , determineArgumentType : (Type <* >) -> GraphQLInputType ): GraphQLDirective {
357365 val locations = definition.directiveLocations.map { Introspection .DirectiveLocation .valueOf(it.name) }.toTypedArray()
358366
@@ -371,11 +379,6 @@ class SchemaParser internal constructor(
371379 .build()
372380 }
373381
374- private fun findDirective (name : String ): GraphQLDirective ? {
375- finishDirective(name)
376- return schemaDirectives[name]
377- }
378-
379382 private fun createDirectiveArgument (definition : InputValueDefinition , determineArgumentType : (Type <* >) -> GraphQLInputType ): GraphQLArgument {
380383 return GraphQLArgument .newArgument()
381384 .name(definition.name)
@@ -389,7 +392,7 @@ class SchemaParser internal constructor(
389392
390393 private fun buildAppliedDirectives (directives : List <Directive >): Array <GraphQLAppliedDirective > {
391394 return directives.map { directive ->
392- val graphQLDirective = findDirective (directive.name)
395+ val graphQLDirective = schemaDirectives.find (directive.name)
393396 ? : BUILT_IN_DIRECTIVES [directive.name]
394397 ? : if (options.allowUndeclaredDirectives) return @map buildUndeclaredAppliedDirective(directive)
395398 else throw SchemaError (" Found applied directive ${directive.name} without corresponding directive definition." )
@@ -474,7 +477,7 @@ class SchemaParser internal constructor(
474477 val repeatable = directiveDefinitions.find { it.name.equals(directive.name) }?.isRepeatable ? : false
475478 if (repeatable || ! names.contains(directive.name)) {
476479 names.add(directive.name)
477- val graphQLDirective = findDirective (directive.name)
480+ val graphQLDirective = schemaDirectives.find (directive.name)
478481 ? : BUILT_IN_DIRECTIVES [directive.name]
479482 // graphql-java rejects legacy directives without a definition, so undeclared ones are only kept as applied directives
480483 ? : if (options.allowUndeclaredDirectives) continue
0 commit comments