@@ -62,7 +62,7 @@ class SchemaParser internal constructor(
6262 private val codeRegistryBuilder = GraphQLCodeRegistry .newCodeRegistry()
6363 private val directiveWiringHelper = DirectiveWiringHelper (runtimeWiring, codeRegistryBuilder)
6464
65- private lateinit var schemaDirectives : Set < GraphQLDirective >
65+ private lateinit var schemaDirectives: SchemaDirectives
6666
6767 /* *
6868 * Parses the given schema with respect to the given dictionary and returns GraphQL objects.
@@ -112,7 +112,7 @@ class SchemaParser internal constructor(
112112 // Directives applied to the schema itself, e.g. `extend schema @link(...)`. Only GraphQLAppliedDirectives are built here, not
113113 // the deprecated GraphQLDirective ones: graphql-java doesn't resolve type references in those, so enum arguments would fail.
114114 val schemaAppliedDirectives = buildAppliedDirectives(rootInfo.getDirectives()).toList()
115- return SchemaObjects (query, mutation, subscription, types, schemaDirectives, codeRegistryBuilder, rootInfo.getDescription(), schemaAppliedDirectives)
115+ return SchemaObjects (query, mutation, subscription, types, schemaDirectives.all , codeRegistryBuilder, rootInfo.getDescription(), schemaAppliedDirectives)
116116 }
117117
118118 /* *
@@ -187,7 +187,7 @@ class SchemaParser internal constructor(
187187 .apply { getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
188188 .type(determineInputType(fieldDefinition.type, inputObjects, referencingInputObjects))
189189 .withAppliedDirectives(* buildAppliedDirectives(fieldDefinition.directives))
190- .withDirectives(* buildDirectives(definition .directives, INPUT_FIELD_DEFINITION ))
190+ .withDirectives(* buildDirectives(fieldDefinition .directives, INPUT_FIELD_DEFINITION ))
191191 .build()
192192 )
193193 }
@@ -323,47 +323,76 @@ class SchemaParser internal constructor(
323323 }
324324
325325 private fun createDirectives (inputObjects : MutableList <GraphQLInputObjectType >) {
326- schemaDirectives = directiveDefinitions.map { definition ->
327- val locations = definition.directiveLocations.map { Introspection .DirectiveLocation .valueOf(it.name) }.toTypedArray()
328-
329- GraphQLDirective .newDirective()
330- .name(definition.name)
331- .description(getDocumentation(definition, options))
332- .definition(definition)
333- .comparatorRegistry(runtimeWiring.comparatorRegistry)
334- .validLocations(* locations)
335- .repeatable(definition.isRepeatable)
336- .apply {
337- definition.inputValueDefinitions.forEach { argumentDefinition ->
338- argument(createDirectiveArgument(argumentDefinition, inputObjects))
339- }
340- }
341- .build()
342- }.toSet()
326+ schemaDirectives = SchemaDirectives (inputObjects)
327+ directiveDefinitions.forEach { schemaDirectives.find(it.name) }
343328 // because the arguments can have directives too, we attach them only after the directives themselves are created
344- schemaDirectives = schemaDirectives.map { d ->
329+ schemaDirectives.update { d ->
345330 val arguments = d.arguments.map { a -> a.transform {
346331 it.withAppliedDirectives(* buildAppliedDirectives(a.definition!! .directives))
347332 .withDirectives(* buildDirectives(a.definition!! .directives, Introspection .DirectiveLocation .OBJECT ))
348333 } }
349334 d.transform { it.replaceArguments(arguments) }
350- }.toSet()
335+ }
351336 }
352337
353- private fun createDirectiveArgument (definition : InputValueDefinition , inputObjects : List <GraphQLInputObjectType >): GraphQLArgument {
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+
364+ private fun createDirective (definition : DirectiveDefinition , determineArgumentType : (Type <* >) -> GraphQLInputType ): GraphQLDirective {
365+ val locations = definition.directiveLocations.map { Introspection .DirectiveLocation .valueOf(it.name) }.toTypedArray()
366+
367+ return GraphQLDirective .newDirective()
368+ .name(definition.name)
369+ .description(getDocumentation(definition, options))
370+ .definition(definition)
371+ .comparatorRegistry(runtimeWiring.comparatorRegistry)
372+ .validLocations(* locations)
373+ .repeatable(definition.isRepeatable)
374+ .apply {
375+ definition.inputValueDefinitions.forEach { argumentDefinition ->
376+ argument(createDirectiveArgument(argumentDefinition, determineArgumentType))
377+ }
378+ }
379+ .build()
380+ }
381+
382+ private fun createDirectiveArgument (definition : InputValueDefinition , determineArgumentType : (Type <* >) -> GraphQLInputType ): GraphQLArgument {
354383 return GraphQLArgument .newArgument()
355384 .name(definition.name)
356385 .definition(definition)
357386 .description(getDocumentation(definition, options))
358- .type(determineInputType (definition.type, inputObjects, mutableSetOf () ))
387+ .type(determineArgumentType (definition.type))
359388 .apply { getDeprecated(definition.directives)?.let { deprecate(it) } }
360389 .apply { definition.defaultValue?.let { defaultValueLiteral(it) } }
361390 .build()
362391 }
363392
364393 private fun buildAppliedDirectives (directives : List <Directive >): Array <GraphQLAppliedDirective > {
365394 return directives.map { directive ->
366- val graphQLDirective = schemaDirectives.find { d -> d.name == directive.name }
395+ val graphQLDirective = schemaDirectives.find( directive.name)
367396 ? : BUILT_IN_DIRECTIVES [directive.name]
368397 ? : if (options.allowUndeclaredDirectives) return @map buildUndeclaredAppliedDirective(directive)
369398 else throw SchemaError (" Found applied directive ${directive.name} without corresponding directive definition." )
@@ -448,7 +477,7 @@ class SchemaParser internal constructor(
448477 val repeatable = directiveDefinitions.find { it.name.equals(directive.name) }?.isRepeatable ? : false
449478 if (repeatable || ! names.contains(directive.name)) {
450479 names.add(directive.name)
451- val graphQLDirective = this . schemaDirectives.find { d -> d.name == directive.name }
480+ val graphQLDirective = schemaDirectives.find( directive.name)
452481 ? : BUILT_IN_DIRECTIVES [directive.name]
453482 // graphql-java rejects legacy directives without a definition, so undeclared ones are only kept as applied directives
454483 ? : if (options.allowUndeclaredDirectives) continue
@@ -534,8 +563,8 @@ class SchemaParser internal constructor(
534563 inputObjects : List <GraphQLInputObjectType >,
535564 referencingInputObjects : MutableSet <String >): GraphQLInputType =
536565 when (typeDefinition) {
537- is ListType -> GraphQLList (determineType (expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
538- is NonNullType -> GraphQLNonNull (determineType (expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
566+ is ListType -> GraphQLList (determineInputType (expectedType, typeDefinition.type, allowedTypeReferences, inputObjects, referencingInputObjects ))
567+ is NonNullType -> GraphQLNonNull (determineInputType (expectedType, typeDefinition.type, allowedTypeReferences, inputObjects, referencingInputObjects ))
539568 is InputObjectTypeDefinition -> {
540569 log.info(" Create input object" )
541570 createInputObject(typeDefinition, inputObjects, referencingInputObjects)
0 commit comments