1
1
package graphql.kickstart.tools
2
2
3
- import graphql.Scalars
4
3
import graphql.introspection.Introspection
5
4
import graphql.introspection.Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION
6
5
import graphql.kickstart.tools.directive.DirectiveWiringHelper
@@ -9,6 +8,7 @@ import graphql.kickstart.tools.util.getExtendedFieldDefinitions
9
8
import graphql.kickstart.tools.util.unwrap
10
9
import graphql.language.*
11
10
import graphql.schema.*
11
+ import graphql.schema.idl.DirectiveInfo
12
12
import graphql.schema.idl.RuntimeWiring
13
13
import graphql.schema.idl.ScalarInfo
14
14
import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility
@@ -60,6 +60,8 @@ class SchemaParser internal constructor(
60
60
private val codeRegistryBuilder = GraphQLCodeRegistry .newCodeRegistry()
61
61
private val directiveWiringHelper = DirectiveWiringHelper (options, runtimeWiring, codeRegistryBuilder, directiveDefinitions)
62
62
63
+ private lateinit var schemaDirectives : Set <GraphQLDirective >
64
+
63
65
/* *
64
66
* Parses the given schema with respect to the given dictionary and returns GraphQL objects.
65
67
*/
@@ -72,6 +74,7 @@ class SchemaParser internal constructor(
72
74
73
75
// Create GraphQL objects
74
76
val inputObjects: MutableList <GraphQLInputObjectType > = mutableListOf ()
77
+ createDirectives(inputObjects)
75
78
inputObjectDefinitions.forEach {
76
79
if (inputObjects.none { io -> io.name == it.name }) {
77
80
inputObjects.add(createInputObject(it, inputObjects, mutableSetOf ()))
@@ -82,8 +85,6 @@ class SchemaParser internal constructor(
82
85
val unions = unionDefinitions.map { createUnionObject(it, objects) }
83
86
val enums = enumDefinitions.map { createEnumObject(it) }
84
87
85
- val directives = directiveDefinitions.map { createDirective(it, inputObjects) }.toSet()
86
-
87
88
// Assign type resolver to interfaces now that we know all of the object types
88
89
interfaces.forEach { codeRegistryBuilder.typeResolver(it, InterfaceTypeResolver (dictionary.inverse(), it)) }
89
90
unions.forEach { codeRegistryBuilder.typeResolver(it, UnionTypeResolver (dictionary.inverse(), it)) }
@@ -103,7 +104,7 @@ class SchemaParser internal constructor(
103
104
val additionalObjects = objects.filter { o -> o != query && o != subscription && o != mutation }
104
105
105
106
val types = (additionalObjects.toSet() as Set <GraphQLType >) + inputObjects + enums + interfaces + unions
106
- return SchemaObjects (query, mutation, subscription, types, directives , codeRegistryBuilder, rootInfo.getDescription())
107
+ return SchemaObjects (query, mutation, subscription, types, schemaDirectives , codeRegistryBuilder, rootInfo.getDescription())
107
108
}
108
109
109
110
/* *
@@ -300,44 +301,75 @@ class SchemaParser internal constructor(
300
301
.name(definition.name)
301
302
.definition(definition)
302
303
.description(getDocumentation(definition, options))
303
- .type(determineInputType(definition.type, inputObjects, setOf ()))
304
+ .type(determineInputType(definition.type, inputObjects, mutableSetOf ()))
304
305
.apply { getDeprecated(definition.directives)?.let { deprecate(it) } }
305
306
.apply { definition.defaultValue?.let { defaultValueLiteral(it) } }
306
307
.withAppliedDirectives(* buildAppliedDirectives(definition.directives))
307
308
.withDirectives(* buildDirectives(definition.directives, Introspection .DirectiveLocation .ARGUMENT_DEFINITION ))
308
309
.build()
309
310
}
310
311
311
- private fun createDirective (definition : DirectiveDefinition , inputObjects : List <GraphQLInputObjectType >): GraphQLDirective {
312
- val locations = definition.directiveLocations.map { Introspection .DirectiveLocation .valueOf(it.name) }.toTypedArray()
312
+ private fun createDirectives (inputObjects : MutableList <GraphQLInputObjectType >) {
313
+ schemaDirectives = directiveDefinitions.map { definition ->
314
+ val locations = definition.directiveLocations.map { Introspection .DirectiveLocation .valueOf(it.name) }.toTypedArray()
315
+
316
+ GraphQLDirective .newDirective()
317
+ .name(definition.name)
318
+ .description(getDocumentation(definition, options))
319
+ .definition(definition)
320
+ .comparatorRegistry(runtimeWiring.comparatorRegistry)
321
+ .validLocations(* locations)
322
+ .repeatable(definition.isRepeatable)
323
+ .apply {
324
+ definition.inputValueDefinitions.forEach { argumentDefinition ->
325
+ argument(createDirectiveArgument(argumentDefinition, inputObjects))
326
+ }
327
+ }
328
+ .build()
329
+ }.toSet()
330
+ // because the arguments can have directives too, we attach them only after the directives themselves are created
331
+ schemaDirectives = schemaDirectives.map { d ->
332
+ val arguments = d.arguments.map { a -> a.transform {
333
+ it.withAppliedDirectives(* buildAppliedDirectives(a.definition!! .directives))
334
+ .withDirectives(* buildDirectives(a.definition!! .directives, Introspection .DirectiveLocation .OBJECT ))
335
+ } }
336
+ d.transform { it.replaceArguments(arguments) }
337
+ }.toSet()
338
+ }
313
339
314
- return GraphQLDirective .newDirective()
340
+ private fun createDirectiveArgument (definition : InputValueDefinition , inputObjects : List <GraphQLInputObjectType >): GraphQLArgument {
341
+ return GraphQLArgument .newArgument()
315
342
.name(definition.name)
316
- .description(getDocumentation(definition, options))
317
343
.definition(definition)
318
- .comparatorRegistry(runtimeWiring.comparatorRegistry)
319
- .validLocations(* locations)
320
- .repeatable(definition.isRepeatable)
321
- .apply {
322
- definition.inputValueDefinitions.forEach { argumentDefinition ->
323
- argument(createArgument(argumentDefinition, inputObjects))
324
- }
325
- }
344
+ .description(getDocumentation(definition, options))
345
+ .type(determineInputType(definition.type, inputObjects, mutableSetOf ()))
346
+ .apply { getDeprecated(definition.directives)?.let { deprecate(it) } }
347
+ .apply { definition.defaultValue?.let { defaultValueLiteral(it) } }
326
348
.build()
327
349
}
328
350
329
351
private fun buildAppliedDirectives (directives : List <Directive >): Array <GraphQLAppliedDirective > {
330
- return directives.map {
352
+ return directives.map { directive ->
353
+ val graphQLDirective = schemaDirectives.find { d -> d.name == directive.name }
354
+ ? : DirectiveInfo .GRAPHQL_SPECIFICATION_DIRECTIVE_MAP [directive.name]
355
+ ? : throw SchemaError (" Found applied directive ${directive.name} without corresponding directive definition." )
356
+ val graphQLArguments = graphQLDirective.arguments.associateBy { it.name }
357
+
331
358
GraphQLAppliedDirective .newDirective()
332
- .name(it.name)
333
- .description(getDocumentation(it, options))
359
+ .name(directive.name)
360
+ .description(getDocumentation(directive, options))
361
+ .definition(directive)
334
362
.comparatorRegistry(runtimeWiring.comparatorRegistry)
335
363
.apply {
336
- it.arguments.forEach { arg ->
364
+ directive.arguments.forEach { arg ->
365
+ val graphQLArgument = graphQLArguments[arg.name]
366
+ ? : throw SchemaError (" Found an unexpected directive argument ${directive.name} #${arg.name} ." )
337
367
argument(GraphQLAppliedDirectiveArgument .newArgument()
338
368
.name(arg.name)
339
- .type(buildDirectiveInputType(arg.value))
369
+ // TODO instead of guessing the type from its value, lookup the directive definition
370
+ .type(graphQLArgument.type)
340
371
.valueLiteral(arg.value)
372
+ .description(graphQLArgument.description)
341
373
.build()
342
374
)
343
375
}
@@ -358,6 +390,10 @@ class SchemaParser internal constructor(
358
390
val repeatable = directiveDefinitions.find { it.name.equals(directive.name) }?.isRepeatable ? : false
359
391
if (repeatable || ! names.contains(directive.name)) {
360
392
names.add(directive.name)
393
+ val graphQLDirective = this .schemaDirectives.find { d -> d.name == directive.name }
394
+ ? : DirectiveInfo .GRAPHQL_SPECIFICATION_DIRECTIVE_MAP [directive.name]
395
+ ? : throw SchemaError (" Found applied directive ${directive.name} without corresponding directive definition." )
396
+ val graphQLArguments = graphQLDirective.arguments.associateBy { it.name }
361
397
output.add(
362
398
GraphQLDirective .newDirective()
363
399
.name(directive.name)
@@ -367,9 +403,11 @@ class SchemaParser internal constructor(
367
403
.repeatable(repeatable)
368
404
.apply {
369
405
directive.arguments.forEach { arg ->
406
+ val graphQLArgument = graphQLArguments[arg.name]
407
+ ? : throw SchemaError (" Found an unexpected directive argument ${directive.name} #${arg.name} ." )
370
408
argument(GraphQLArgument .newArgument()
371
409
.name(arg.name)
372
- .type(buildDirectiveInputType(arg.value) )
410
+ .type(graphQLArgument.type )
373
411
// TODO remove this once directives are fully replaced with applied directives
374
412
.valueLiteral(arg.value)
375
413
.build())
@@ -383,46 +421,6 @@ class SchemaParser internal constructor(
383
421
return output.toTypedArray()
384
422
}
385
423
386
- private fun buildDirectiveInputType (value : Value <* >): GraphQLInputType ? {
387
- return when (value) {
388
- is NullValue -> Scalars .GraphQLString
389
- is FloatValue -> Scalars .GraphQLFloat
390
- is StringValue -> Scalars .GraphQLString
391
- is IntValue -> Scalars .GraphQLInt
392
- is BooleanValue -> Scalars .GraphQLBoolean
393
- is ArrayValue -> GraphQLList .list(buildDirectiveInputType(getArrayValueWrappedType(value)))
394
- // TODO to implement this we'll need to "observe" directive's input types + match them here based on their fields(?)
395
- else -> throw SchemaError (" Directive values of type '${value::class .simpleName} ' are not supported yet." )
396
- }
397
- }
398
-
399
- private fun getArrayValueWrappedType (value : ArrayValue ): Value <* > {
400
- // empty array [] is equivalent to [null]
401
- if (value.values.isEmpty()) {
402
- return NullValue .newNullValue().build()
403
- }
404
-
405
- // get rid of null values
406
- val nonNullValueList = value.values.filter { v -> v !is NullValue }
407
-
408
- // [null, null, ...] unwrapped is null
409
- if (nonNullValueList.isEmpty()) {
410
- return NullValue .newNullValue().build()
411
- }
412
-
413
- // make sure the array isn't polymorphic
414
- val distinctTypes = nonNullValueList
415
- .map { it::class .java }
416
- .distinct()
417
-
418
- if (distinctTypes.size > 1 ) {
419
- throw SchemaError (" Arrays containing multiple types of values are not supported yet." )
420
- }
421
-
422
- // peek at first value, value exists and is assured to be non-null
423
- return nonNullValueList[0 ]
424
- }
425
-
426
424
private fun determineOutputType (typeDefinition : Type <* >, inputObjects : List <GraphQLInputObjectType >) =
427
425
determineType(GraphQLOutputType ::class , typeDefinition, permittedTypesForObject, inputObjects) as GraphQLOutputType
428
426
@@ -455,13 +453,15 @@ class SchemaParser internal constructor(
455
453
else -> throw SchemaError (" Unknown type: $typeDefinition " )
456
454
}
457
455
458
- private fun determineInputType (typeDefinition : Type <* >, inputObjects : List <GraphQLInputObjectType >, referencingInputObjects : Set <String >) =
456
+ private fun determineInputType (typeDefinition : Type <* >, inputObjects : List <GraphQLInputObjectType >, referencingInputObjects : MutableSet <String >) =
459
457
determineInputType(GraphQLInputType ::class , typeDefinition, permittedTypesForInputObject, inputObjects, referencingInputObjects)
460
458
461
- private fun <T : Any > determineInputType (expectedType : KClass <T >,
462
- typeDefinition : Type <* >, allowedTypeReferences : Set <String >,
463
- inputObjects : List <GraphQLInputObjectType >,
464
- referencingInputObjects : Set <String >): GraphQLInputType =
459
+ private fun <T : Any > determineInputType (
460
+ expectedType : KClass <T >,
461
+ typeDefinition : Type <* >,
462
+ allowedTypeReferences : Set <String >,
463
+ inputObjects : List <GraphQLInputObjectType >,
464
+ referencingInputObjects : MutableSet <String >): GraphQLInputType =
465
465
when (typeDefinition) {
466
466
is ListType -> GraphQLList (determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
467
467
is NonNullType -> GraphQLNonNull (determineType(expectedType, typeDefinition.type, allowedTypeReferences, inputObjects))
@@ -489,7 +489,7 @@ class SchemaParser internal constructor(
489
489
if (referencingInputObject != null ) {
490
490
GraphQLTypeReference (referencingInputObject)
491
491
} else {
492
- val inputObject = createInputObject(filteredDefinitions[0 ], inputObjects, referencingInputObjects as MutableSet < String > )
492
+ val inputObject = createInputObject(filteredDefinitions[0 ], inputObjects, referencingInputObjects)
493
493
(inputObjects as MutableList ).add(inputObject)
494
494
inputObject
495
495
}
0 commit comments