@@ -71,7 +71,8 @@ class SchemaParser internal constructor(
7171 private val codeRegistryBuilder = GraphQLCodeRegistry .newCodeRegistry()
7272 private val directiveWiringHelper = DirectiveWiringHelper (runtimeWiring, codeRegistryBuilder)
7373
74- private lateinit var schemaDirectives: SchemaDirectives
74+ // by name, in the order they're declared
75+ private lateinit var schemaDirectives: MutableMap <String , GraphQLDirective >
7576
7677 /* *
7778 * Parses the given schema with respect to the given dictionary and returns GraphQL objects.
@@ -88,21 +89,14 @@ class SchemaParser internal constructor(
8889
8990 // Create GraphQL objects
9091 val inputObjects: MutableList <GraphQLInputObjectType > = mutableListOf ()
91- createDirectives(inputObjects )
92+ createDirectives()
9293 inputObjectDefinitions.forEach {
9394 if (inputObjects.none { io -> io.name == it.name }) {
9495 inputObjects.add(createInputObject(it, inputObjects, mutableSetOf ()))
9596 }
9697 }
9798 val interfaces = interfaceDefinitions.map { createInterfaceObject(it, inputObjects) }
9899 val objects = objectDefinitions.map { createObject(it, interfaces, inputObjects) }
99- if (options.unmappedInputFields != UnmappedInputFields .IGNORE ) {
100- val checker = UnmappedInputFieldChecker (inputObjects.associateBy { it.name }, options.unmappedInputFields)
101- objectDefinitions.forEach { type ->
102- fieldResolversByType[type].orEmpty().values.filterIsInstance<MethodFieldResolver >().forEach { checker.check(type.name, it) }
103- }
104- checker.report()
105- }
106100 val unions = unionDefinitions.map { createUnionObject(it, objects) }
107101 val enums = enumDefinitions.map { createEnumObject(it) }
108102
@@ -128,7 +122,33 @@ class SchemaParser internal constructor(
128122 // Directives applied to the schema itself, e.g. `extend schema @link(...)`. Only GraphQLAppliedDirectives are built here, not
129123 // the deprecated GraphQLDirective ones: graphql-java doesn't resolve type references in those, so enum arguments would fail.
130124 val schemaAppliedDirectives = buildAppliedDirectives(rootInfo.getDirectives(), SCHEMA ).toList()
131- return SchemaObjects (query, mutation, subscription, types, schemaDirectives.all, codeRegistryBuilder, rootInfo.getDescription(), schemaAppliedDirectives)
125+ val schemaObjects = SchemaObjects (query, mutation, subscription, types, schemaDirectives.values.toSet(), codeRegistryBuilder, rootInfo.getDescription(), schemaAppliedDirectives)
126+ // in the order the types were created
127+ val wiredObjects = if (directiveWiringHelper.isWiringRequired) wire(schemaObjects, inputObjects + interfaces + objects + unions + enums) else schemaObjects
128+ if (options.unmappedInputFields != UnmappedInputFields .IGNORE ) {
129+ val wiredInputObjects = wiredObjects.dictionary.filterIsInstance<GraphQLInputObjectType >().associateBy { it.name }
130+ val checker = UnmappedInputFieldChecker (wiredInputObjects, options.unmappedInputFields)
131+ objectDefinitions.forEach { type ->
132+ fieldResolversByType[type].orEmpty().values.filterIsInstance<MethodFieldResolver >().forEach { checker.check(type.name, it) }
133+ }
134+ checker.report()
135+ }
136+ return wiredObjects
137+ }
138+
139+ /* *
140+ * Runs the directive wirings on the types once the schema is assembled, so they see resolved types, and swaps in the types they change.
141+ */
142+ private fun wire (schemaObjects : SchemaObjects , types : List <GraphQLNamedType >): SchemaObjects {
143+ // building the schema resolves the type references in these types
144+ val wired = directiveWiringHelper.wireTypes(schemaObjects.toSchema(), types)
145+ if (wired.isEmpty()) return schemaObjects
146+ return schemaObjects.copy(
147+ query = wired[schemaObjects.query.name] as GraphQLObjectType ? ? : schemaObjects.query,
148+ mutation = schemaObjects.mutation?.let { wired[it.name] as GraphQLObjectType ? ? : it },
149+ subscription = schemaObjects.subscription?.let { wired[it.name] as GraphQLObjectType ? ? : it },
150+ dictionary = schemaObjects.dictionary.mapTo(linkedSetOf()) { wired[(it as GraphQLNamedType ).name] ? : it }
151+ )
132152 }
133153
134154 /* *
@@ -178,7 +198,7 @@ class SchemaParser internal constructor(
178198 }
179199 }
180200
181- return directiveWiringHelper.wireObject( builder.build() )
201+ return builder.build()
182202 }
183203
184204 private fun createInputObject (definition : InputObjectTypeDefinition , inputObjects : List <GraphQLInputObjectType >,
@@ -214,7 +234,7 @@ class SchemaParser internal constructor(
214234 }
215235 }
216236
217- return directiveWiringHelper.wireInputObject( builder.build() )
237+ return builder.build()
218238 }
219239
220240 private fun createEnumObject (definition : EnumTypeDefinition ): GraphQLEnumType {
@@ -259,7 +279,7 @@ class SchemaParser internal constructor(
259279 }
260280 }
261281
262- return directiveWiringHelper.wireEnum( builder.build() )
282+ return builder.build()
263283 }
264284
265285 private fun createInterfaceObject (interfaceDefinition : InterfaceTypeDefinition , inputObjects : List <GraphQLInputObjectType >): GraphQLInterfaceType {
@@ -286,7 +306,7 @@ class SchemaParser internal constructor(
286306 }
287307 }
288308
289- return directiveWiringHelper.wireInterFace( builder.build() )
309+ return builder.build()
290310 }
291311
292312 private fun createUnionObject (definition : UnionTypeDefinition , types : List <GraphQLObjectType >): GraphQLUnionType {
@@ -304,7 +324,7 @@ class SchemaParser internal constructor(
304324 getLeafUnionObjects(definition, types).forEach { possibleType(it) }
305325 }
306326
307- return directiveWiringHelper.wireUnion( builder.build() )
327+ return builder.build()
308328 }
309329
310330 private fun getLeafUnionObjects (definition : UnionTypeDefinition , types : List <GraphQLObjectType >): List <GraphQLObjectType > {
@@ -353,7 +373,7 @@ class SchemaParser internal constructor(
353373 .description(getDocumentation(fieldDefinition, options))
354374 .definition(fieldDefinition)
355375 .apply { getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
356- .type(determineOutputType(fieldDefinition.type, inputObjects ))
376+ .type(determineOutputType(fieldDefinition.type))
357377 .withAppliedDirectives(* buildAppliedDirectives(fieldDefinition.directives, FIELD_DEFINITION ))
358378 .withDirectives(* buildDirectives(fieldDefinition.directives, FIELD_DEFINITION ))
359379 .apply {
@@ -376,11 +396,10 @@ class SchemaParser internal constructor(
376396 .build()
377397 }
378398
379- private fun createDirectives (inputObjects : MutableList <GraphQLInputObjectType >) {
380- schemaDirectives = SchemaDirectives (inputObjects)
381- directiveDefinitions.forEach { schemaDirectives.find(it.name) }
399+ private fun createDirectives () {
400+ schemaDirectives = directiveDefinitions.associateTo(linkedMapOf()) { it.name to createDirective(it) }
382401 // because the arguments can have directives too, we attach them only after the directives themselves are created
383- schemaDirectives.update { d ->
402+ schemaDirectives.replaceAll { _, d ->
384403 val arguments = d.arguments.map { a -> a.transform {
385404 it.withAppliedDirectives(* buildAppliedDirectives(a.definition!! .directives, ARGUMENT_DEFINITION ))
386405 .withDirectives(* buildDirectives(a.definition!! .directives, ARGUMENT_DEFINITION ))
@@ -389,33 +408,7 @@ class SchemaParser internal constructor(
389408 }
390409 }
391410
392- /* *
393- * The directive definitions. The input objects used by their arguments need the directives applied to them, so each directive
394- * is first built with type references to input objects, then rebuilt with the input objects the first time it's looked up.
395- * Only a directive applied within its own argument types gets its first build there.
396- */
397- private inner class SchemaDirectives (private val inputObjects : MutableList <GraphQLInputObjectType >) {
398- // by name, in the order they're declared
399- private val byName = directiveDefinitions.associateTo(linkedMapOf()) { definition ->
400- definition.name to createDirective(definition) { determineType(GraphQLInputType ::class , it, permittedTypesForInputObject, inputObjects) as GraphQLInputType }
401- }
402- private val unfinished = directiveDefinitions.associateByTo(mutableMapOf ()) { it.name }
403- // shared, so that an input object that's still being built isn't built again for another directive
404- private val referencingInputObjects = mutableSetOf<String >()
405-
406- val all: Set <GraphQLDirective > get() = byName.values.toSet()
407-
408- fun find (name : String ): GraphQLDirective ? {
409- unfinished.remove(name)?.let { definition ->
410- byName[name] = createDirective(definition) { determineInputType(it, inputObjects, referencingInputObjects) }
411- }
412- return byName[name]
413- }
414-
415- fun update (transform : (GraphQLDirective ) -> GraphQLDirective ) = byName.replaceAll { _, directive -> transform(directive) }
416- }
417-
418- private fun createDirective (definition : DirectiveDefinition , determineArgumentType : (Type <* >) -> GraphQLInputType ): GraphQLDirective {
411+ private fun createDirective (definition : DirectiveDefinition ): GraphQLDirective {
419412 val locations = definition.directiveLocations.map { Introspection .DirectiveLocation .valueOf(it.name) }.toTypedArray()
420413
421414 return GraphQLDirective .newDirective()
@@ -427,18 +420,19 @@ class SchemaParser internal constructor(
427420 .repeatable(definition.isRepeatable)
428421 .apply {
429422 definition.inputValueDefinitions.forEach { argumentDefinition ->
430- argument(createDirectiveArgument(argumentDefinition, determineArgumentType ))
423+ argument(createDirectiveArgument(argumentDefinition))
431424 }
432425 }
433426 .build()
434427 }
435428
436- private fun createDirectiveArgument (definition : InputValueDefinition , determineArgumentType : ( Type < * >) -> GraphQLInputType ): GraphQLArgument {
429+ private fun createDirectiveArgument (definition : InputValueDefinition ): GraphQLArgument {
437430 return GraphQLArgument .newArgument()
438431 .name(definition.name)
439432 .definition(definition)
440433 .description(getDocumentation(definition, options))
441- .type(determineArgumentType(definition.type))
434+ // type references, so that an input object used by a directive applied to it doesn't contain itself
435+ .type(determineType(GraphQLInputType ::class , definition.type, permittedTypesForInputObject) as GraphQLInputType )
442436 .apply { getDeprecated(definition.directives)?.let { deprecate(it) } }
443437 .apply { definition.defaultValue?.let { defaultValueLiteral(it) } }
444438 .build()
@@ -508,7 +502,7 @@ class SchemaParser internal constructor(
508502 }
509503 }
510504
511- private fun findDirective (name : String ): GraphQLDirective ? = schemaDirectives.find( name) ? : BUILT_IN_DIRECTIVES [name]
505+ private fun findDirective (name : String ): GraphQLDirective ? = schemaDirectives[ name] ? : BUILT_IN_DIRECTIVES [name]
512506
513507 private fun buildUndeclaredAppliedDirective (directive : Directive ): GraphQLAppliedDirective {
514508 return GraphQLAppliedDirective .newDirective()
@@ -597,22 +591,18 @@ class SchemaParser internal constructor(
597591 private fun missingArgumentsWithDefault (directive : Directive , graphQLDirective : GraphQLDirective ): List <GraphQLArgument > =
598592 graphQLDirective.arguments.filter { it.hasSetDefaultValue() && directive.getArgument(it.name) == null }
599593
600- private fun determineOutputType (typeDefinition : Type <* >, inputObjects : List < GraphQLInputObjectType > ) =
601- determineType(GraphQLOutputType ::class , typeDefinition, permittedTypesForObject, inputObjects ) as GraphQLOutputType
594+ private fun determineOutputType (typeDefinition : Type <* >) =
595+ determineType(GraphQLOutputType ::class , typeDefinition, permittedTypesForObject) as GraphQLOutputType
602596
597+ // named types other than scalars are type references
603598 private fun <T : Any > determineType (
604599 expectedType : KClass <T >,
605600 typeDefinition : Type <* >,
606- allowedTypeReferences : Set <String >,
607- inputObjects : List <GraphQLInputObjectType >
601+ allowedTypeReferences : Set <String >
608602 ): GraphQLType =
609603 when (typeDefinition) {
610- is ListType -> GraphQLList (determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
611- is NonNullType -> GraphQLNonNull (determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
612- is InputObjectTypeDefinition -> {
613- log.info(" Create input object" )
614- createInputObject(typeDefinition, inputObjects, mutableSetOf ())
615- }
604+ is ListType -> GraphQLList (determineType(expectedType, typeDefinition.type, allowedTypeReferences))
605+ is NonNullType -> GraphQLNonNull (determineType(expectedType, typeDefinition.type, allowedTypeReferences))
616606 is TypeName -> {
617607 val scalarType = customScalars[typeDefinition.name]
618608 ? : GRAPHQL_SCALARS [typeDefinition.name]
@@ -622,7 +612,7 @@ class SchemaParser internal constructor(
622612 if (! allowedTypeReferences.contains(typeDefinition.name)) {
623613 throw unexpectedTypeError(typeDefinition, expectedType)
624614 }
625- inputObjects.find { it.name == typeDefinition.name } ? : GraphQLTypeReference (typeDefinition.name)
615+ GraphQLTypeReference (typeDefinition.name)
626616 }
627617 }
628618 else -> throw SchemaError (" Unknown type: $typeDefinition " )
0 commit comments