Skip to content

Commit 75a9362

Browse files
oryan-blockclaude
andcommitted
Keep the directive definitions in one object
SchemaDirectives holds the directives by name, the ones not rebuilt with their input objects yet, and the input objects being built for them. find() rebuilds a directive the first time it's looked up. This replaces the finishDirective function that createDirectives had to set and reset, and a new object per parse means nothing carries over between parses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 4d59fdc commit 75a9362

1 file changed

Lines changed: 33 additions & 30 deletions

File tree

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

Lines changed: 33 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)