@@ -73,8 +73,8 @@ class SchemaParser internal constructor(
7373 private val codeRegistryBuilder = GraphQLCodeRegistry .newCodeRegistry()
7474 private val directiveWiringHelper = DirectiveWiringHelper (runtimeWiring, codeRegistryBuilder)
7575
76- private lateinit var schemaDirectives : SchemaDirectives
77- private lateinit var scalars : MutableMap <String , GraphQLScalarType >
76+ // by name, in the order they're declared
77+ private lateinit var schemaDirectives : MutableMap <String , GraphQLDirective >
7878
7979 /* *
8080 * Parses the given schema with respect to the given dictionary and returns GraphQL objects.
@@ -91,22 +91,15 @@ class SchemaParser internal constructor(
9191
9292 // Create GraphQL objects
9393 val inputObjects: MutableList <GraphQLInputObjectType > = mutableListOf ()
94- scalars = mutableMapOf ()
95- createDirectives(inputObjects)
94+ createDirectives ()
95+ val scalars = scalarDefinitions.values.map { createScalar(it) }
9696 inputObjectDefinitions.forEach {
9797 if (inputObjects.none { io -> io.name == it.name }) {
9898 inputObjects.add(createInputObject(it, inputObjects, mutableSetOf ()))
9999 }
100100 }
101101 val interfaces = interfaceDefinitions.map { createInterfaceObject(it, inputObjects) }
102102 val objects = objectDefinitions.map { createObject(it, interfaces, inputObjects) }
103- if (options.unmappedInputFields != UnmappedInputFields .IGNORE ) {
104- val checker = UnmappedInputFieldChecker (inputObjects.associateBy { it.name }, options.unmappedInputFields)
105- objectDefinitions.forEach { type ->
106- fieldResolversByType[type].orEmpty().values.filterIsInstance<MethodFieldResolver >().forEach { checker.check(type.name, it) }
107- }
108- checker.report()
109- }
110103 val unions = unionDefinitions.map { createUnionObject(it, objects) }
111104 val enums = enumDefinitions.map { createEnumObject(it) }
112105
@@ -128,11 +121,37 @@ class SchemaParser internal constructor(
128121
129122 val additionalObjects = objects.filter { o -> o != query && o != subscription && o != mutation }
130123
131- val types = (additionalObjects.toSet() as Set <GraphQLType >) + inputObjects + enums + interfaces + unions
124+ val types = (additionalObjects.toSet() as Set <GraphQLType >) + inputObjects + enums + interfaces + unions + scalars
132125 // Directives applied to the schema itself, e.g. `extend schema @link(...)`. Only GraphQLAppliedDirectives are built here, not
133126 // the deprecated GraphQLDirective ones: graphql-java doesn't resolve type references in those, so enum arguments would fail.
134127 val schemaAppliedDirectives = buildAppliedDirectives(rootInfo.getDirectives(), SCHEMA ).toList()
135- return SchemaObjects (query, mutation, subscription, types, schemaDirectives.all, codeRegistryBuilder, rootInfo.getDescription(), schemaAppliedDirectives)
128+ val schemaObjects = SchemaObjects (query, mutation, subscription, types, schemaDirectives.values.toSet(), codeRegistryBuilder, rootInfo.getDescription(), schemaAppliedDirectives)
129+ // in the order the types were created
130+ val wiredObjects = if (directiveWiringHelper.isWiringRequired) wire(schemaObjects, scalars + inputObjects + interfaces + objects + unions + enums) else schemaObjects
131+ if (options.unmappedInputFields != UnmappedInputFields .IGNORE ) {
132+ val wiredInputObjects = wiredObjects.dictionary.filterIsInstance<GraphQLInputObjectType >().associateBy { it.name }
133+ val checker = UnmappedInputFieldChecker (wiredInputObjects, options.unmappedInputFields)
134+ objectDefinitions.forEach { type ->
135+ fieldResolversByType[type].orEmpty().values.filterIsInstance<MethodFieldResolver >().forEach { checker.check(type.name, it) }
136+ }
137+ checker.report()
138+ }
139+ return wiredObjects
140+ }
141+
142+ /* *
143+ * Runs the directive wirings on the types once the schema is assembled, so they see resolved types, and swaps in the types they change.
144+ */
145+ private fun wire (schemaObjects : SchemaObjects , types : List <GraphQLNamedType >): SchemaObjects {
146+ // building the schema resolves the type references in these types
147+ val wired = directiveWiringHelper.wireTypes(schemaObjects.toSchema(), types)
148+ if (wired.isEmpty()) return schemaObjects
149+ return schemaObjects.copy(
150+ query = wired[schemaObjects.query.name] as GraphQLObjectType ? ? : schemaObjects.query,
151+ mutation = schemaObjects.mutation?.let { wired[it.name] as GraphQLObjectType ? ? : it },
152+ subscription = schemaObjects.subscription?.let { wired[it.name] as GraphQLObjectType ? ? : it },
153+ dictionary = schemaObjects.dictionary.mapTo(linkedSetOf()) { wired[(it as GraphQLNamedType ).name] ? : it }
154+ )
136155 }
137156
138157 /* *
@@ -182,7 +201,7 @@ class SchemaParser internal constructor(
182201 }
183202 }
184203
185- return directiveWiringHelper.wireObject( builder.build() )
204+ return builder.build()
186205 }
187206
188207 private fun createInputObject (definition : InputObjectTypeDefinition , inputObjects : List <GraphQLInputObjectType >,
@@ -218,7 +237,7 @@ class SchemaParser internal constructor(
218237 }
219238 }
220239
221- return directiveWiringHelper.wireInputObject( builder.build() )
240+ return builder.build()
222241 }
223242
224243 private fun createEnumObject (definition : EnumTypeDefinition ): GraphQLEnumType {
@@ -263,20 +282,7 @@ class SchemaParser internal constructor(
263282 }
264283 }
265284
266- return directiveWiringHelper.wireEnum(builder.build())
267- }
268-
269- /* *
270- * The scalar with this name, or null if there's none. A custom scalar is built the first time it's used, so that everything
271- * using it holds the same instance. The directives it applies are built first, so their definitions don't keep a type
272- * reference to it. The directives applied to scalars can still hold one to a scalar that's being built, which graphql-java
273- * replaces when it builds the schema.
274- */
275- private fun scalarType (name : String? ): GraphQLScalarType ? {
276- val definition = scalarDefinitions[name] ? : return GRAPHQL_SCALARS [name]
277- scalars[definition.name]?.let { return it }
278- definition.withExtensions(typeExtensions).flatMap { it.directives }.forEach { schemaDirectives.find(it.name) }
279- return scalars.getOrPut(definition.name) { createScalar(definition) }
285+ return builder.build()
280286 }
281287
282288 private fun createScalar (definition : ScalarTypeDefinition ): GraphQLScalarType {
@@ -301,7 +307,7 @@ class SchemaParser internal constructor(
301307 .withAppliedDirectives(* appliedDirectives)
302308 .withDirectives(* legacyDirectives)
303309
304- return directiveWiringHelper.wireScalar( builder.build() )
310+ return builder.build()
305311 }
306312
307313 private fun createInterfaceObject (interfaceDefinition : InterfaceTypeDefinition , inputObjects : List <GraphQLInputObjectType >): GraphQLInterfaceType {
@@ -328,7 +334,7 @@ class SchemaParser internal constructor(
328334 }
329335 }
330336
331- return directiveWiringHelper.wireInterFace( builder.build() )
337+ return builder.build()
332338 }
333339
334340 private fun createUnionObject (definition : UnionTypeDefinition , types : List <GraphQLObjectType >): GraphQLUnionType {
@@ -346,7 +352,7 @@ class SchemaParser internal constructor(
346352 getLeafUnionObjects(definition, types).forEach { possibleType(it) }
347353 }
348354
349- return directiveWiringHelper.wireUnion( builder.build() )
355+ return builder.build()
350356 }
351357
352358 private fun getLeafUnionObjects (definition : UnionTypeDefinition , types : List <GraphQLObjectType >): List <GraphQLObjectType > {
@@ -395,7 +401,7 @@ class SchemaParser internal constructor(
395401 .description(getDocumentation(fieldDefinition, options))
396402 .definition(fieldDefinition)
397403 .apply { getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
398- .type(determineOutputType(fieldDefinition.type, inputObjects ))
404+ .type(determineOutputType(fieldDefinition.type))
399405 .withAppliedDirectives(* buildAppliedDirectives(fieldDefinition.directives, FIELD_DEFINITION ))
400406 .withDirectives(* buildDirectives(fieldDefinition.directives, FIELD_DEFINITION ))
401407 .apply {
@@ -418,11 +424,10 @@ class SchemaParser internal constructor(
418424 .build()
419425 }
420426
421- private fun createDirectives (inputObjects : MutableList <GraphQLInputObjectType >) {
422- schemaDirectives = SchemaDirectives (inputObjects)
423- directiveDefinitions.forEach { schemaDirectives.find(it.name) }
427+ private fun createDirectives () {
428+ schemaDirectives = directiveDefinitions.associateTo(linkedMapOf()) { it.name to createDirective(it) }
424429 // because the arguments can have directives too, we attach them only after the directives themselves are created
425- schemaDirectives.update { d ->
430+ schemaDirectives.replaceAll { _, d ->
426431 val arguments = d.arguments.map { a -> a.transform {
427432 it.withAppliedDirectives(* buildAppliedDirectives(a.definition!! .directives, ARGUMENT_DEFINITION ))
428433 .withDirectives(* buildDirectives(a.definition!! .directives, ARGUMENT_DEFINITION ))
@@ -431,33 +436,7 @@ class SchemaParser internal constructor(
431436 }
432437 }
433438
434- /* *
435- * The directive definitions. The input objects used by their arguments need the directives applied to them, so each directive
436- * is first built with type references to the types it can't build yet, then rebuilt with the built types the first time it's
437- * looked up. Only a directive applied within its own argument types gets its first build there.
438- */
439- private inner class SchemaDirectives (private val inputObjects : MutableList <GraphQLInputObjectType >) {
440- // by name, in the order they're declared
441- private val byName = directiveDefinitions.associateTo(linkedMapOf()) { definition ->
442- definition.name to createDirective(definition, ::typeReferenceOf)
443- }
444- private val unfinished = directiveDefinitions.associateByTo(mutableMapOf ()) { it.name }
445- // shared, so that an input object that's still being built isn't built again for another directive
446- private val referencingInputObjects = mutableSetOf<String >()
447-
448- val all: Set <GraphQLDirective > get() = byName.values.toSet()
449-
450- fun find (name : String ): GraphQLDirective ? {
451- unfinished.remove(name)?.let { definition ->
452- byName[name] = createDirective(definition) { determineInputType(it, inputObjects, referencingInputObjects) }
453- }
454- return byName[name]
455- }
456-
457- fun update (transform : (GraphQLDirective ) -> GraphQLDirective ) = byName.replaceAll { _, directive -> transform(directive) }
458- }
459-
460- private fun createDirective (definition : DirectiveDefinition , determineArgumentType : (Type <* >) -> GraphQLInputType ): GraphQLDirective {
439+ private fun createDirective (definition : DirectiveDefinition ): GraphQLDirective {
461440 val locations = definition.directiveLocations.map { Introspection .DirectiveLocation .valueOf(it.name) }.toTypedArray()
462441
463442 return GraphQLDirective .newDirective()
@@ -469,18 +448,19 @@ class SchemaParser internal constructor(
469448 .repeatable(definition.isRepeatable)
470449 .apply {
471450 definition.inputValueDefinitions.forEach { argumentDefinition ->
472- argument(createDirectiveArgument(argumentDefinition, determineArgumentType ))
451+ argument(createDirectiveArgument(argumentDefinition))
473452 }
474453 }
475454 .build()
476455 }
477456
478- private fun createDirectiveArgument (definition : InputValueDefinition , determineArgumentType : ( Type < * >) -> GraphQLInputType ): GraphQLArgument {
457+ private fun createDirectiveArgument (definition : InputValueDefinition ): GraphQLArgument {
479458 return GraphQLArgument .newArgument()
480459 .name(definition.name)
481460 .definition(definition)
482461 .description(getDocumentation(definition, options))
483- .type(determineArgumentType(definition.type))
462+ // type references, so that an input object used by a directive applied to it doesn't contain itself
463+ .type(determineType(GraphQLInputType ::class , definition.type, permittedTypesForInputObject) as GraphQLInputType )
484464 .apply { getDeprecated(definition.directives)?.let { deprecate(it) } }
485465 .apply { definition.defaultValue?.let { defaultValueLiteral(it) } }
486466 .build()
@@ -550,7 +530,7 @@ class SchemaParser internal constructor(
550530 }
551531 }
552532
553- private fun findDirective (name : String ): GraphQLDirective ? = schemaDirectives.find( name) ? : BUILT_IN_DIRECTIVES [name]
533+ private fun findDirective (name : String ): GraphQLDirective ? = schemaDirectives[ name] ? : BUILT_IN_DIRECTIVES [name]
554534
555535 private fun buildUndeclaredAppliedDirective (directive : Directive ): GraphQLAppliedDirective {
556536 return GraphQLAppliedDirective .newDirective()
@@ -642,33 +622,22 @@ class SchemaParser internal constructor(
642622 private fun getSpecifiedByUrl (directives : List <Directive >): String? =
643623 (directives.find { it.name == Directives .SpecifiedByDirective .name }?.getArgument(" url" )?.value as ? StringValue )?.value
644624
645- // a directive's first build, see SchemaDirectives: no directive exists yet, so only scalars that apply none can be built
646- private fun typeReferenceOf (type : Type <* >): GraphQLInputType = when (type) {
647- is ListType -> GraphQLList (typeReferenceOf(type.type))
648- is NonNullType -> GraphQLNonNull (typeReferenceOf(type.type))
649- is TypeName -> {
650- val appliesDirectives = scalarDefinitions[type.name]?.withExtensions(typeExtensions)?.any { it.directives.isNotEmpty() } == true
651- (if (appliesDirectives) null else scalarType(type.name)) ? : GraphQLTypeReference (type.name)
652- }
653- else -> throw SchemaError (" Unknown type: $type " )
654- }
625+ // custom scalars are in the dictionary, so they're referenced by name like the other types
626+ private fun scalarType (name : String? ): GraphQLInputType ? =
627+ scalarDefinitions[name]?.let { GraphQLTypeReference (it.name) } ? : GRAPHQL_SCALARS [name]
655628
656- private fun determineOutputType (typeDefinition : Type <* >, inputObjects : List < GraphQLInputObjectType > ) =
657- determineType(GraphQLOutputType ::class , typeDefinition, permittedTypesForObject, inputObjects ) as GraphQLOutputType
629+ private fun determineOutputType (typeDefinition : Type <* >) =
630+ determineType(GraphQLOutputType ::class , typeDefinition, permittedTypesForObject) as GraphQLOutputType
658631
632+ // named types other than spec scalars are type references
659633 private fun <T : Any > determineType (
660634 expectedType : KClass <T >,
661635 typeDefinition : Type <* >,
662- allowedTypeReferences : Set <String >,
663- inputObjects : List <GraphQLInputObjectType >
636+ allowedTypeReferences : Set <String >
664637 ): GraphQLType =
665638 when (typeDefinition) {
666- is ListType -> GraphQLList (determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
667- is NonNullType -> GraphQLNonNull (determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
668- is InputObjectTypeDefinition -> {
669- log.info(" Create input object" )
670- createInputObject(typeDefinition, inputObjects, mutableSetOf ())
671- }
639+ is ListType -> GraphQLList (determineType(expectedType, typeDefinition.type, allowedTypeReferences))
640+ is NonNullType -> GraphQLNonNull (determineType(expectedType, typeDefinition.type, allowedTypeReferences))
672641 is TypeName -> {
673642 val scalarType = scalarType(typeDefinition.name)
674643 if (scalarType != null ) {
@@ -677,7 +646,7 @@ class SchemaParser internal constructor(
677646 if (! allowedTypeReferences.contains(typeDefinition.name)) {
678647 throw unexpectedTypeError(typeDefinition, expectedType)
679648 }
680- inputObjects.find { it.name == typeDefinition.name } ? : GraphQLTypeReference (typeDefinition.name)
649+ GraphQLTypeReference (typeDefinition.name)
681650 }
682651 }
683652 else -> throw SchemaError (" Unknown type: $typeDefinition " )
0 commit comments