11package graphql.kickstart.tools
22
3+ import graphql.Scalars
34import graphql.introspection.Introspection
45import graphql.kickstart.tools.directive.SchemaGeneratorDirectiveHelper
6+ import graphql.kickstart.tools.util.getDocumentation
57import graphql.kickstart.tools.util.getExtendedFieldDefinitions
68import graphql.kickstart.tools.util.unwrap
79import graphql.language.*
@@ -11,7 +13,6 @@ import graphql.schema.idl.ScalarInfo
1113import graphql.schema.idl.SchemaGeneratorHelper
1214import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility
1315import org.slf4j.LoggerFactory
14- import java.util.*
1516import kotlin.reflect.KClass
1617
1718/* *
@@ -24,17 +25,11 @@ class SchemaParser internal constructor(
2425 private val options : SchemaParserOptions ,
2526 private val runtimeWiring : RuntimeWiring
2627) {
27- companion object {
28- val log = LoggerFactory .getLogger(SchemaClassScanner ::class .java)!!
29- const val DEFAULT_DEPRECATION_MESSAGE = " No longer supported"
28+ private val log = LoggerFactory .getLogger(javaClass)
3029
30+ companion object {
3131 @JvmStatic
3232 fun newParser () = SchemaParserBuilder ()
33-
34- internal fun getDocumentation (node : AbstractNode <* >): String? = node.comments?.asSequence()
35- ?.filter { ! it.content.startsWith(" #" ) }
36- ?.joinToString(" \n " ) { it.content.trimEnd() }
37- ?.trimIndent()
3833 }
3934
4035 private val dictionary = scanResult.dictionary
@@ -129,7 +124,7 @@ class SchemaParser internal constructor(
129124 .definition(objectDefinition)
130125 .description(if (objectDefinition.description != null ) objectDefinition.description.content else getDocumentation(objectDefinition))
131126
132- builder.withDirectives(* buildDirectives(objectDefinition.directives, setOf (), Introspection .DirectiveLocation .OBJECT ))
127+ builder.withDirectives(* buildDirectives(objectDefinition.directives, Introspection .DirectiveLocation .OBJECT ))
133128
134129 objectDefinition.implements.forEach { implementsDefinition ->
135130 val interfaceName = (implementsDefinition as TypeName ).name
@@ -160,19 +155,6 @@ class SchemaParser internal constructor(
160155 return schemaGeneratorDirectiveHelper.onObject(objectType, directiveHelperParameters)
161156 }
162157
163- private fun buildDirectives (directives : List <Directive >, directiveDefinitions : Set <GraphQLDirective >, directiveLocation : Introspection .DirectiveLocation ): Array <GraphQLDirective > {
164- val names = HashSet <String >()
165-
166- val output = ArrayList <GraphQLDirective >()
167- for (directive in directives) {
168- if (! names.contains(directive.name)) {
169- names.add(directive.name)
170- output.add(schemaGeneratorHelper.buildDirective(directive, directiveDefinitions, directiveLocation, runtimeWiring.comparatorRegistry))
171- }
172- }
173- return output.toTypedArray()
174- }
175-
176158 private fun createInputObject (definition : InputObjectTypeDefinition , inputObjects : List <GraphQLInputObjectType >,
177159 referencingInputObjects : MutableSet <String >): GraphQLInputObjectType {
178160 val extensionDefinitions = inputExtensionDefinitions.filter { it.name == definition.name }
@@ -183,19 +165,19 @@ class SchemaParser internal constructor(
183165 .extensionDefinitions(extensionDefinitions)
184166 .description(if (definition.description != null ) definition.description.content else getDocumentation(definition))
185167
186- builder.withDirectives(* buildDirectives(definition.directives, setOf (), Introspection .DirectiveLocation .INPUT_OBJECT ))
168+ builder.withDirectives(* buildDirectives(definition.directives, Introspection .DirectiveLocation .INPUT_OBJECT ))
187169
188170 referencingInputObjects.add(definition.name)
189171
190172 (extensionDefinitions + definition).forEach {
191173 it.inputValueDefinitions.forEach { inputDefinition ->
192174 val fieldBuilder = GraphQLInputObjectField .newInputObjectField()
193- .name(inputDefinition.name)
194- .definition(inputDefinition)
195- .description(if (inputDefinition.description != null ) inputDefinition.description.content else getDocumentation(inputDefinition))
196- .defaultValue(buildDefaultValue(inputDefinition.defaultValue))
197- .type(determineInputType(inputDefinition.type, inputObjects, referencingInputObjects))
198- .withDirectives(* buildDirectives(inputDefinition.directives, setOf (), Introspection .DirectiveLocation .INPUT_FIELD_DEFINITION ))
175+ .name(inputDefinition.name)
176+ .definition(inputDefinition)
177+ .description(if (inputDefinition.description != null ) inputDefinition.description.content else getDocumentation(inputDefinition))
178+ .defaultValue(buildDefaultValue(inputDefinition.defaultValue))
179+ .type(determineInputType(inputDefinition.type, inputObjects, referencingInputObjects))
180+ .withDirectives(* buildDirectives(inputDefinition.directives, setOf (), Introspection .DirectiveLocation .INPUT_FIELD_DEFINITION ))
199181 builder.field(fieldBuilder.build())
200182 }
201183 }
@@ -214,14 +196,14 @@ class SchemaParser internal constructor(
214196 .definition(definition)
215197 .description(if (definition.description != null ) definition.description.content else getDocumentation(definition))
216198
217- builder.withDirectives(* buildDirectives(definition.directives, setOf (), Introspection .DirectiveLocation .ENUM ))
199+ builder.withDirectives(* buildDirectives(definition.directives, Introspection .DirectiveLocation .ENUM ))
218200
219201 definition.enumValueDefinitions.forEach { enumDefinition ->
220202 val enumName = enumDefinition.name
221203 val enumValue = type.unwrap().enumConstants.find { (it as Enum <* >).name == enumName }
222204 ? : throw SchemaError (" Expected value for name '$enumName ' in enum '${type.unwrap().simpleName} ' but found none!" )
223205
224- val enumValueDirectives = buildDirectives(enumDefinition.directives, setOf (), Introspection .DirectiveLocation .ENUM_VALUE )
206+ val enumValueDirectives = buildDirectives(enumDefinition.directives, Introspection .DirectiveLocation .ENUM_VALUE )
225207 getDeprecated(enumDefinition.directives).let {
226208 val enumValueDefinition = GraphQLEnumValueDefinition .newEnumValueDefinition()
227209 .name(enumName)
@@ -246,7 +228,7 @@ class SchemaParser internal constructor(
246228 .definition(interfaceDefinition)
247229 .description(if (interfaceDefinition.description != null ) interfaceDefinition.description.content else getDocumentation(interfaceDefinition))
248230
249- builder.withDirectives(* buildDirectives(interfaceDefinition.directives, setOf (), Introspection .DirectiveLocation .INTERFACE ))
231+ builder.withDirectives(* buildDirectives(interfaceDefinition.directives, Introspection .DirectiveLocation .INTERFACE ))
250232
251233 interfaceDefinition.fieldDefinitions.forEach { fieldDefinition ->
252234 builder.field { field -> createField(field, fieldDefinition, inputObjects) }
@@ -262,7 +244,7 @@ class SchemaParser internal constructor(
262244 .definition(definition)
263245 .description(if (definition.description != null ) definition.description.content else getDocumentation(definition))
264246
265- builder.withDirectives(* buildDirectives(definition.directives, setOf (), Introspection .DirectiveLocation .UNION ))
247+ builder.withDirectives(* buildDirectives(definition.directives, Introspection .DirectiveLocation .UNION ))
266248
267249 getLeafUnionObjects(definition, types).forEach { builder.possibleType(it) }
268250 return schemaGeneratorDirectiveHelper.onUnion(builder.build(), schemaDirectiveParameters)
@@ -289,25 +271,95 @@ class SchemaParser internal constructor(
289271 }
290272
291273 private fun createField (field : GraphQLFieldDefinition .Builder , fieldDefinition : FieldDefinition , inputObjects : List <GraphQLInputObjectType >): GraphQLFieldDefinition .Builder {
292- field.name(fieldDefinition.name)
293- field.description(if (fieldDefinition.description != null ) fieldDefinition.description.content else getDocumentation(fieldDefinition))
294- field.definition(fieldDefinition)
295- getDeprecated(fieldDefinition.directives)?.let { field.deprecate(it) }
296- field.type(determineOutputType(fieldDefinition.type, inputObjects))
274+ field
275+ .name(fieldDefinition.name)
276+ .description(fieldDefinition.description?.content ? : getDocumentation(fieldDefinition))
277+ .definition(fieldDefinition)
278+ .apply { getDeprecated(fieldDefinition.directives)?.let { deprecate(it) } }
279+ .type(determineOutputType(fieldDefinition.type, inputObjects))
280+
297281 fieldDefinition.inputValueDefinitions.forEach { argumentDefinition ->
298282 val argumentBuilder = GraphQLArgument .newArgument()
299283 .name(argumentDefinition.name)
300284 .definition(argumentDefinition)
301285 .description(if (argumentDefinition.description != null ) argumentDefinition.description.content else getDocumentation(argumentDefinition))
302- .defaultValue(buildDefaultValue(argumentDefinition.defaultValue))
303286 .type(determineInputType(argumentDefinition.type, inputObjects, setOf ()))
304- .withDirectives(* buildDirectives(argumentDefinition.directives, setOf (), Introspection .DirectiveLocation .ARGUMENT_DEFINITION ))
287+ .apply { buildDefaultValue(argumentDefinition.defaultValue)?.let { defaultValue(it) } }
288+ .withDirectives(* buildDirectives(argumentDefinition.directives, Introspection .DirectiveLocation .ARGUMENT_DEFINITION ))
289+
305290 field.argument(argumentBuilder.build())
306291 }
307- field.withDirectives(* buildDirectives(fieldDefinition.directives, setOf (), Introspection .DirectiveLocation .FIELD_DEFINITION ))
292+ field.withDirectives(* buildDirectives(fieldDefinition.directives, Introspection .DirectiveLocation .FIELD_DEFINITION ))
293+
308294 return field
309295 }
310296
297+ private fun buildDirectives (directives : List <Directive >, directiveLocation : Introspection .DirectiveLocation ): Array <GraphQLDirective > {
298+ val names = mutableSetOf<String >()
299+
300+ val output = mutableListOf<GraphQLDirective >()
301+ for (directive in directives) {
302+ if (! names.contains(directive.name)) {
303+ names.add(directive.name)
304+ val graphQLDirective = GraphQLDirective .newDirective()
305+ .name(directive.name)
306+ .apply {
307+ directive.arguments.forEach { arg ->
308+ argument(GraphQLArgument .newArgument()
309+ .name(arg.name)
310+ .type(buildDirectiveInputType(arg.value))
311+ .build())
312+ }
313+ }
314+ .build()
315+
316+
317+ output.add(schemaGeneratorHelper.buildDirective(directive, setOf (graphQLDirective), directiveLocation, runtimeWiring.comparatorRegistry))
318+ }
319+ }
320+
321+ return output.toTypedArray()
322+ }
323+
324+ private fun buildDirectiveInputType (value : Value <* >): GraphQLInputType ? {
325+ when (value) {
326+ is NullValue -> return Scalars .GraphQLString
327+ is FloatValue -> return Scalars .GraphQLFloat
328+ is StringValue -> return Scalars .GraphQLString
329+ is IntValue -> return Scalars .GraphQLInt
330+ is BooleanValue -> return Scalars .GraphQLBoolean
331+ is ArrayValue -> return GraphQLList .list(buildDirectiveInputType(getArrayValueWrappedType(value)))
332+ else -> throw SchemaError (" Directive values of type '${value::class .simpleName} ' are not supported yet." )
333+ }
334+ }
335+
336+ private fun getArrayValueWrappedType (value : ArrayValue ): Value <* > {
337+ // empty array [] is equivalent to [null]
338+ if (value.values.isEmpty()) {
339+ return NullValue .newNullValue().build()
340+ }
341+
342+ // get rid of null values
343+ val nonNullValueList = value.values.filter { v -> v !is NullValue }
344+
345+ // [null, null, ...] unwrapped is null
346+ if (nonNullValueList.isEmpty()) {
347+ return NullValue .newNullValue().build()
348+ }
349+
350+ // make sure the array isn't polymorphic
351+ val distinctTypes = nonNullValueList
352+ .map { it::class .java }
353+ .distinct()
354+
355+ if (distinctTypes.size > 1 ) {
356+ throw SchemaError (" Arrays containing multiple types of values are not supported yet." )
357+ }
358+
359+ // peek at first value, value exists and is assured to be non-null
360+ return nonNullValueList[0 ]
361+ }
362+
311363 private fun buildDefaultValue (value : Value <* >? ): Any? {
312364 return when (value) {
313365 null -> null
@@ -335,7 +387,7 @@ class SchemaParser internal constructor(
335387 }
336388 is TypeName -> {
337389 val scalarType = customScalars[typeDefinition.name]
338- ? : graphQLScalars [typeDefinition.name]
390+ ? : GRAPHQL_SCALARS [typeDefinition.name]
339391 if (scalarType != null ) {
340392 scalarType
341393 } else {
@@ -365,7 +417,7 @@ class SchemaParser internal constructor(
365417 }
366418 is TypeName -> {
367419 val scalarType = customScalars[typeDefinition.name]
368- ? : graphQLScalars [typeDefinition.name]
420+ ? : GRAPHQL_SCALARS [typeDefinition.name]
369421 if (scalarType != null ) {
370422 scalarType
371423 } else {
@@ -417,4 +469,6 @@ class SchemaParser internal constructor(
417469
418470class SchemaError (message : String , cause : Throwable ? = null ) : RuntimeException(message, cause)
419471
420- val graphQLScalars = ScalarInfo .STANDARD_SCALARS .associateBy { it.name }
472+ val GRAPHQL_SCALARS = ScalarInfo .GRAPHQL_SPECIFICATION_SCALARS .associateBy { it.name }
473+
474+ const val DEFAULT_DEPRECATION_MESSAGE = " No longer supported"
0 commit comments