@@ -11,6 +11,7 @@ import graphql.introspection.Introspection.DirectiveLocation.INPUT_FIELD_DEFINIT
1111import graphql.introspection.Introspection.DirectiveLocation.INPUT_OBJECT
1212import graphql.introspection.Introspection.DirectiveLocation.INTERFACE
1313import graphql.introspection.Introspection.DirectiveLocation.OBJECT
14+ import graphql.introspection.Introspection.DirectiveLocation.SCALAR
1415import graphql.introspection.Introspection.DirectiveLocation.SCHEMA
1516import graphql.introspection.Introspection.DirectiveLocation.UNION
1617import graphql.kickstart.tools.directive.DirectiveWiringHelper
@@ -60,6 +61,7 @@ class SchemaParser internal constructor(
6061 private val directiveDefinitions = definitions.filterIsInstance<DirectiveDefinition >()
6162
6263 private val unionDefinitions = definitions.filterIsInstance<UnionTypeDefinition >()
64+ private val scalarDefinitions = definitions.filterIsInstance<ScalarTypeDefinition >().filter { it.name in customScalars }.associateBy { it.name }
6365
6466 private val permittedTypesForObject: Set <String > = (objectDefinitions.map { it.name } +
6567 enumDefinitions.map { it.name } +
@@ -72,6 +74,7 @@ class SchemaParser internal constructor(
7274 private val directiveWiringHelper = DirectiveWiringHelper (runtimeWiring, codeRegistryBuilder)
7375
7476 private lateinit var schemaDirectives: SchemaDirectives
77+ private lateinit var scalars: MutableMap <String , GraphQLScalarType >
7578
7679 /* *
7780 * Parses the given schema with respect to the given dictionary and returns GraphQL objects.
@@ -88,6 +91,7 @@ class SchemaParser internal constructor(
8891
8992 // Create GraphQL objects
9093 val inputObjects: MutableList <GraphQLInputObjectType > = mutableListOf ()
94+ scalars = mutableMapOf ()
9195 createDirectives(inputObjects)
9296 inputObjectDefinitions.forEach {
9397 if (inputObjects.none { io -> io.name == it.name }) {
@@ -262,6 +266,44 @@ class SchemaParser internal constructor(
262266 return directiveWiringHelper.wireEnum(builder.build())
263267 }
264268
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) }
280+ }
281+
282+ private fun createScalar (definition : ScalarTypeDefinition ): GraphQLScalarType {
283+ val name = definition.name
284+ val provided = customScalars.getValue(name)
285+ val extensions = typeExtensions.extensionsOf<ScalarTypeExtensionDefinition >(name)
286+ val directives = directivesOf(definition, extensions)
287+ val (appliedDirectives, legacyDirectives) = try {
288+ buildAppliedDirectives(directives, SCALAR ) to buildDirectives(directives, SCALAR )
289+ } catch (e: SchemaError ) {
290+ // TODO throw instead in 15.0.0 (#893), like for the other types
291+ log.warn(" Scalar '$name ' is built without its directives from the schema: ${e.message} " )
292+ emptyArray<GraphQLAppliedDirective >() to emptyArray<GraphQLDirective >()
293+ }
294+ val builder = GraphQLScalarType .newScalar(provided)
295+ .definition(definition)
296+ .extensionDefinitions(extensions)
297+ .description(getDocumentation(definition, options) ? : provided.description)
298+ .specifiedByUrl(getSpecifiedByUrl(directives) ? : provided.specifiedByUrl)
299+ // like graphql-java, only the schema's directives are applied
300+ .clearDirectives()
301+ .withAppliedDirectives(* appliedDirectives)
302+ .withDirectives(* legacyDirectives)
303+
304+ return directiveWiringHelper.wireScalar(builder.build())
305+ }
306+
265307 private fun createInterfaceObject (interfaceDefinition : InterfaceTypeDefinition , inputObjects : List <GraphQLInputObjectType >): GraphQLInterfaceType {
266308 val extensions = typeExtensions.extensionsOf<InterfaceTypeExtensionDefinition >(interfaceDefinition.name)
267309 val directives = directivesOf(interfaceDefinition, extensions)
@@ -391,13 +433,13 @@ class SchemaParser internal constructor(
391433
392434 /* *
393435 * 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.
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.
396438 */
397439 private inner class SchemaDirectives (private val inputObjects : MutableList <GraphQLInputObjectType >) {
398440 // by name, in the order they're declared
399441 private val byName = directiveDefinitions.associateTo(linkedMapOf()) { definition ->
400- definition.name to createDirective(definition) { determineType( GraphQLInputType :: class , it, permittedTypesForInputObject, inputObjects) as GraphQLInputType }
442+ definition.name to createDirective(definition, ::typeReferenceOf)
401443 }
402444 private val unfinished = directiveDefinitions.associateByTo(mutableMapOf ()) { it.name }
403445 // shared, so that an input object that's still being built isn't built again for another directive
@@ -597,6 +639,20 @@ class SchemaParser internal constructor(
597639 private fun missingArgumentsWithDefault (directive : Directive , graphQLDirective : GraphQLDirective ): List <GraphQLArgument > =
598640 graphQLDirective.arguments.filter { it.hasSetDefaultValue() && directive.getArgument(it.name) == null }
599641
642+ private fun getSpecifiedByUrl (directives : List <Directive >): String? =
643+ (directives.find { it.name == Directives .SpecifiedByDirective .name }?.getArgument(" url" )?.value as ? StringValue )?.value
644+
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+ }
655+
600656 private fun determineOutputType (typeDefinition : Type <* >, inputObjects : List <GraphQLInputObjectType >) =
601657 determineType(GraphQLOutputType ::class , typeDefinition, permittedTypesForObject, inputObjects) as GraphQLOutputType
602658
@@ -614,8 +670,7 @@ class SchemaParser internal constructor(
614670 createInputObject(typeDefinition, inputObjects, mutableSetOf ())
615671 }
616672 is TypeName -> {
617- val scalarType = customScalars[typeDefinition.name]
618- ? : GRAPHQL_SCALARS [typeDefinition.name]
673+ val scalarType = scalarType(typeDefinition.name)
619674 if (scalarType != null ) {
620675 scalarType
621676 } else {
@@ -645,8 +700,7 @@ class SchemaParser internal constructor(
645700 createInputObject(typeDefinition, inputObjects, referencingInputObjects)
646701 }
647702 is TypeName -> {
648- val scalarType = customScalars[typeDefinition.name]
649- ? : GRAPHQL_SCALARS [typeDefinition.name]
703+ val scalarType = scalarType(typeDefinition.name)
650704 if (scalarType != null ) {
651705 scalarType
652706 } else {
0 commit comments