Skip to content

Commit cb662e5

Browse files
committed
Move the unexpected-type error messages out of SchemaParser
Moves unexpectedTypeError, unreachedTypeHint, findUsage and fieldUsages to TypeReferenceErrors.kt. determineType stays in SchemaParser, because for input types it builds missing input objects through createInputObject. Edits to compile: - TypeReferenceErrors' header, with definitions, unusedDefinitions and typeExtensions. - private dropped on unexpectedTypeError. - SchemaParser holds a typeReferenceErrors, and determineType throws through it. - Imports.
1 parent 2975b20 commit cb662e5

2 files changed

Lines changed: 71 additions & 57 deletions

File tree

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

Lines changed: 2 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class SchemaParser internal constructor(
7070
unionDefinitions.map { it.name }).toSet()
7171
private val permittedTypesForInputObject: Set<String> =
7272
(inputObjectDefinitions.map { it.name } + enumDefinitions.map { it.name }).toSet()
73+
private val typeReferenceErrors = TypeReferenceErrors(definitions, unusedDefinitions, typeExtensions)
7374

7475
private val codeRegistryBuilder = GraphQLCodeRegistry.newCodeRegistry()
7576
private val directiveWiringHelper = DirectiveWiringHelper(runtimeWiring, codeRegistryBuilder)
@@ -628,7 +629,7 @@ class SchemaParser internal constructor(
628629
scalarType
629630
} else {
630631
if (!allowedTypeReferences.contains(typeDefinition.name)) {
631-
throw unexpectedTypeError(typeDefinition, expectedType)
632+
throw typeReferenceErrors.unexpectedTypeError(typeDefinition, expectedType)
632633
}
633634
namedType(typeDefinition)
634635
}
@@ -648,62 +649,6 @@ class SchemaParser internal constructor(
648649
return createInputObject(definition, inputObjects, inputObjectsInProgress).also { (inputObjects as MutableList).add(it) }
649650
}
650651

651-
private fun unexpectedTypeError(typeName: TypeName, expectedType: KClass<*>): SchemaError {
652-
val isInput = expectedType == GraphQLInputType::class
653-
val definition = unusedDefinitions.find { it.name == typeName.name }
654-
?: definitions.filterIsInstance<TypeDefinition<*>>().find { it.name == typeName.name }
655-
val outputOnlyKind = when (definition) {
656-
is ObjectTypeDefinition -> "an object type"
657-
is InterfaceTypeDefinition -> "an interface"
658-
is UnionTypeDefinition -> "a union"
659-
else -> null
660-
}
661-
662-
val problem = when {
663-
definition == null -> "is not defined in the schema."
664-
isInput && outputOnlyKind != null ->
665-
"is $outputOnlyKind, which can't be used as an input type. Input types are input objects, enums and scalars."
666-
!isInput && definition is InputObjectTypeDefinition ->
667-
"is an input object type, which can't be used as an output type. Output types are object types, interfaces, unions, enums and scalars."
668-
definition in unusedDefinitions ->
669-
"is defined in the schema, but was not reached while scanning the resolvers and the classes they use, so it wasn't built. " +
670-
unreachedTypeHint(definition)
671-
else -> "can't be used as ${if (isInput) "an input" else "an output"} type."
672-
}
673-
val usage = findUsage(typeName)?.let { " used by $it" }.orEmpty()
674-
return SchemaError("Type '${typeName.name}'$usage${typeName.sourceLocation.describe()} $problem")
675-
}
676-
677-
// what makes the scanner reach the type depends on its kind, see SchemaClassScanner.canIncludeUnusedType
678-
private fun unreachedTypeHint(definition: TypeDefinition<*>): String = when (definition) {
679-
is InterfaceTypeDefinition, is UnionTypeDefinition ->
680-
"Return it from a resolver method or one of those classes, or set SchemaParserOptions.includeUnusedTypes."
681-
is EnumTypeDefinition ->
682-
"Use it in a resolver method or one of those classes, or set SchemaParserOptions.includeUnusedTypes and pass its enum class in the parser's dictionary."
683-
is InputObjectTypeDefinition ->
684-
"Use it in an argument of a resolver method, directly or through another input type. SchemaParserOptions.includeUnusedTypes doesn't include input types."
685-
is ScalarTypeDefinition ->
686-
"Use it in a resolver method or one of those classes. SchemaParserOptions.includeUnusedTypes doesn't include scalars."
687-
else ->
688-
"Return it from a resolver method or one of those classes, or set SchemaParserOptions.includeUnusedTypes and pass a class for it in the parser's dictionary."
689-
}
690-
691-
// determineType only gets the type reference, so the definition using it is looked up for the error message
692-
private fun findUsage(typeName: TypeName): String? = (definitions + typeExtensions.values.flatten()).asSequence().flatMap { definition ->
693-
when (definition) {
694-
is ObjectTypeDefinition -> fieldUsages(definition.name, definition.fieldDefinitions)
695-
is InterfaceTypeDefinition -> fieldUsages(definition.name, definition.fieldDefinitions)
696-
is InputObjectTypeDefinition -> definition.inputValueDefinitions.map { "input field '${definition.name}.${it.name}'" to it.type }
697-
is DirectiveDefinition -> definition.inputValueDefinitions.map { "argument '${it.name}' of directive '@${definition.name}'" to it.type }
698-
else -> emptyList()
699-
}
700-
}.find { (_, type) -> type.unwrap() === typeName }?.first
701-
702-
private fun fieldUsages(typeName: String, fields: List<FieldDefinition>): List<Pair<String, Type<*>>> = fields.flatMap { field ->
703-
listOf("field '$typeName.${field.name}'" to field.type) +
704-
field.inputValueDefinitions.map { "argument '${it.name}' of field '$typeName.${field.name}'" to it.type }
705-
}
706-
707652
/**
708653
* Returns an optional [String] describing a deprecated field/enum.
709654
* If a deprecation directive was defined using the @deprecated directive,
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package graphql.kickstart.tools
2+
3+
import graphql.kickstart.tools.util.describe
4+
import graphql.kickstart.tools.util.unwrap
5+
import graphql.language.*
6+
import graphql.schema.GraphQLInputType
7+
import kotlin.reflect.KClass
8+
9+
internal class TypeReferenceErrors(
10+
private val definitions: Set<SDLNamedDefinition<*>>,
11+
private val unusedDefinitions: Set<TypeDefinition<*>>,
12+
private val typeExtensions: TypeExtensions,
13+
) {
14+
fun unexpectedTypeError(typeName: TypeName, expectedType: KClass<*>): SchemaError {
15+
val isInput = expectedType == GraphQLInputType::class
16+
val definition = unusedDefinitions.find { it.name == typeName.name }
17+
?: definitions.filterIsInstance<TypeDefinition<*>>().find { it.name == typeName.name }
18+
val outputOnlyKind = when (definition) {
19+
is ObjectTypeDefinition -> "an object type"
20+
is InterfaceTypeDefinition -> "an interface"
21+
is UnionTypeDefinition -> "a union"
22+
else -> null
23+
}
24+
25+
val problem = when {
26+
definition == null -> "is not defined in the schema."
27+
isInput && outputOnlyKind != null ->
28+
"is $outputOnlyKind, which can't be used as an input type. Input types are input objects, enums and scalars."
29+
!isInput && definition is InputObjectTypeDefinition ->
30+
"is an input object type, which can't be used as an output type. Output types are object types, interfaces, unions, enums and scalars."
31+
definition in unusedDefinitions ->
32+
"is defined in the schema, but was not reached while scanning the resolvers and the classes they use, so it wasn't built. " +
33+
unreachedTypeHint(definition)
34+
else -> "can't be used as ${if (isInput) "an input" else "an output"} type."
35+
}
36+
val usage = findUsage(typeName)?.let { " used by $it" }.orEmpty()
37+
return SchemaError("Type '${typeName.name}'$usage${typeName.sourceLocation.describe()} $problem")
38+
}
39+
40+
// what makes the scanner reach the type depends on its kind, see SchemaClassScanner.canIncludeUnusedType
41+
private fun unreachedTypeHint(definition: TypeDefinition<*>): String = when (definition) {
42+
is InterfaceTypeDefinition, is UnionTypeDefinition ->
43+
"Return it from a resolver method or one of those classes, or set SchemaParserOptions.includeUnusedTypes."
44+
is EnumTypeDefinition ->
45+
"Use it in a resolver method or one of those classes, or set SchemaParserOptions.includeUnusedTypes and pass its enum class in the parser's dictionary."
46+
is InputObjectTypeDefinition ->
47+
"Use it in an argument of a resolver method, directly or through another input type. SchemaParserOptions.includeUnusedTypes doesn't include input types."
48+
is ScalarTypeDefinition ->
49+
"Use it in a resolver method or one of those classes. SchemaParserOptions.includeUnusedTypes doesn't include scalars."
50+
else ->
51+
"Return it from a resolver method or one of those classes, or set SchemaParserOptions.includeUnusedTypes and pass a class for it in the parser's dictionary."
52+
}
53+
54+
// determineType only gets the type reference, so the definition using it is looked up for the error message
55+
private fun findUsage(typeName: TypeName): String? = (definitions + typeExtensions.values.flatten()).asSequence().flatMap { definition ->
56+
when (definition) {
57+
is ObjectTypeDefinition -> fieldUsages(definition.name, definition.fieldDefinitions)
58+
is InterfaceTypeDefinition -> fieldUsages(definition.name, definition.fieldDefinitions)
59+
is InputObjectTypeDefinition -> definition.inputValueDefinitions.map { "input field '${definition.name}.${it.name}'" to it.type }
60+
is DirectiveDefinition -> definition.inputValueDefinitions.map { "argument '${it.name}' of directive '@${definition.name}'" to it.type }
61+
else -> emptyList()
62+
}
63+
}.find { (_, type) -> type.unwrap() === typeName }?.first
64+
65+
private fun fieldUsages(typeName: String, fields: List<FieldDefinition>): List<Pair<String, Type<*>>> = fields.flatMap { field ->
66+
listOf("field '$typeName.${field.name}'" to field.type) +
67+
field.inputValueDefinitions.map { "argument '${it.name}' of field '$typeName.${field.name}'" to it.type }
68+
}
69+
}

0 commit comments

Comments
 (0)