From 6b33b1d8f4d9d80d854607bacd635a90cdeac5d0 Mon Sep 17 00:00:00 2001 From: Michiel Oliemans Date: Tue, 18 Jun 2019 21:48:35 +0200 Subject: [PATCH] Fix serialization of default values for input object enums fix #276 --- .../coxautodev/graphql/tools/SchemaParser.kt | 5 +- .../graphql/tools/EnumDefaultValueSpec.groovy | 59 +++++++ .../tools/SchemaClassScannerSpec.groovy | 150 +++++++++--------- 3 files changed, 142 insertions(+), 72 deletions(-) create mode 100644 src/test/groovy/com/coxautodev/graphql/tools/EnumDefaultValueSpec.groovy diff --git a/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt b/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt index 6d7563e1..37a67c9e 100644 --- a/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt +++ b/src/main/kotlin/com/coxautodev/graphql/tools/SchemaParser.kt @@ -46,6 +46,7 @@ import graphql.schema.idl.DirectiveBehavior import graphql.schema.idl.RuntimeWiring import graphql.schema.idl.ScalarInfo import graphql.schema.idl.SchemaGeneratorHelper +import org.slf4j.LoggerFactory import java.util.* import kotlin.reflect.KClass @@ -57,6 +58,7 @@ import kotlin.reflect.KClass class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, private val options: SchemaParserOptions, private val runtimeWiring: RuntimeWiring) { companion object { + val log = LoggerFactory.getLogger(SchemaClassScanner::class.java)!! const val DEFAULT_DEPRECATION_MESSAGE = "No longer supported" @JvmStatic @@ -201,7 +203,7 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat .name(inputDefinition.name) .definition(inputDefinition) .description(if (inputDefinition.description != null) inputDefinition.description.content else getDocumentation(inputDefinition)) - .defaultValue(inputDefinition.defaultValue) + .defaultValue(buildDefaultValue(inputDefinition.defaultValue)) .type(determineInputType(inputDefinition.type)) .withDirectives(*buildDirectives(inputDefinition.directives, setOf(), Introspection.DirectiveLocation.INPUT_FIELD_DEFINITION)) builder.field(fieldBuilder.build()) @@ -226,6 +228,7 @@ class SchemaParser internal constructor(scanResult: ScannedSchemaObjects, privat val enumName = enumDefinition.name val enumValue = type.unwrap().enumConstants.find { (it as Enum<*>).name == enumName } ?: throw SchemaError("Expected value for name '$enumName' in enum '${type.unwrap().simpleName}' but found none!") + val enumValueDirectives = buildDirectives(enumDefinition.directives, setOf(), Introspection.DirectiveLocation.ENUM_VALUE) getDeprecated(enumDefinition.directives).let { val enumValueDefinition = GraphQLEnumValueDefinition.newEnumValueDefinition() diff --git a/src/test/groovy/com/coxautodev/graphql/tools/EnumDefaultValueSpec.groovy b/src/test/groovy/com/coxautodev/graphql/tools/EnumDefaultValueSpec.groovy new file mode 100644 index 00000000..86e571d4 --- /dev/null +++ b/src/test/groovy/com/coxautodev/graphql/tools/EnumDefaultValueSpec.groovy @@ -0,0 +1,59 @@ +package com.coxautodev.graphql.tools + +import graphql.GraphQL +import graphql.execution.AsyncExecutionStrategy +import graphql.schema.GraphQLSchema +import spock.lang.Specification + +class EnumDefaultValueSpec extends Specification { + + def "enumvalue is not passed down to graphql-java"() { + when: + GraphQLSchema schema = SchemaParser.newParser().schemaString('''\ + type Query { + test(input: MySortSpecifier): SortBy + } + input MySortSpecifier { + sortBy: SortBy = createdOn + value: Int = 10 + } + enum SortBy { + createdOn + updatedOn + } + ''').resolvers(new GraphQLQueryResolver() { + + SortBy test(MySortSpecifier input) { + return input.sortBy + } + + }) + .build() + .makeExecutableSchema() + GraphQL gql = GraphQL.newGraphQL(schema) + .queryExecutionStrategy(new AsyncExecutionStrategy()) + .build() + def data = Utils.assertNoGraphQlErrors(gql, [input: [value: 1]]) { + ''' + query test($input: MySortSpecifier) { + test(input: $input) + } + ''' + } + + then: + noExceptionThrown() + data.test == 'createdOn' + } + + static class MySortSpecifier { + SortBy sortBy + int value + } + + enum SortBy { + createdOn, + updatedOn + } + +} diff --git a/src/test/groovy/com/coxautodev/graphql/tools/SchemaClassScannerSpec.groovy b/src/test/groovy/com/coxautodev/graphql/tools/SchemaClassScannerSpec.groovy index b35f5b03..d737f9fc 100644 --- a/src/test/groovy/com/coxautodev/graphql/tools/SchemaClassScannerSpec.groovy +++ b/src/test/groovy/com/coxautodev/graphql/tools/SchemaClassScannerSpec.groovy @@ -19,14 +19,14 @@ class SchemaClassScannerSpec extends Specification { def "scanner handles futures and immediate return types"() { when: SchemaParser.newParser() - .resolvers(new FutureImmediateQuery()) - .schemaString(""" + .resolvers(new FutureImmediateQuery()) + .schemaString(""" type Query { future: Int! immediate: Int! } """) - .scan() + .scan() then: noExceptionThrown() } @@ -44,14 +44,14 @@ class SchemaClassScannerSpec extends Specification { def "scanner handles primitive and boxed return types"() { when: SchemaParser.newParser() - .resolvers(new PrimitiveBoxedQuery()) - .schemaString(""" + .resolvers(new PrimitiveBoxedQuery()) + .schemaString(""" type Query { primitive: Int! boxed: Int! } """) - .scan() + .scan() then: noExceptionThrown() } @@ -69,14 +69,14 @@ class SchemaClassScannerSpec extends Specification { def "scanner handles different scalars with same java class"() { when: SchemaParser.newParser() - .resolvers(new ScalarDuplicateQuery()) - .schemaString(""" + .resolvers(new ScalarDuplicateQuery()) + .schemaString(""" type Query { string: String! id: ID! } """) - .scan() + .scan() then: noExceptionThrown() @@ -84,14 +84,15 @@ class SchemaClassScannerSpec extends Specification { private class ScalarDuplicateQuery implements GraphQLQueryResolver { String string() { "" } + String id() { "" } } def "scanner handles interfaces referenced by objects that aren't explicitly used"() { when: ScannedSchemaObjects objects = SchemaParser.newParser() - .resolvers(new InterfaceMissingQuery()) - .schemaString(""" + .resolvers(new InterfaceMissingQuery()) + .schemaString(""" interface Interface { id: ID! } @@ -100,7 +101,7 @@ class SchemaClassScannerSpec extends Specification { id: ID! } """) - .scan() + .scan() then: objects.definitions.find { it instanceof InterfaceTypeDefinition } != null @@ -113,8 +114,8 @@ class SchemaClassScannerSpec extends Specification { def "scanner handles input types that reference other input types"() { when: ScannedSchemaObjects objects = SchemaParser.newParser() - .resolvers(new MultipleInputTypeQuery()) - .schemaString(""" + .resolvers(new MultipleInputTypeQuery()) + .schemaString(""" input FirstInput { id: String! second: SecondInput! @@ -131,7 +132,7 @@ class SchemaClassScannerSpec extends Specification { test(input: FirstInput): String! } """) - .scan() + .scan() then: objects.definitions.findAll { it instanceof InputObjectTypeDefinition }.size() == 3 @@ -144,6 +145,7 @@ class SchemaClassScannerSpec extends Specification { class FirstInput { String id + SecondInput second() { new SecondInput() } ThirdInput third } @@ -151,6 +153,7 @@ class SchemaClassScannerSpec extends Specification { class SecondInput { String id } + class ThirdInput { String id } @@ -159,24 +162,24 @@ class SchemaClassScannerSpec extends Specification { def "scanner allows multiple return types for custom scalars"() { when: ScannedSchemaObjects objects = SchemaParser.newParser() - .resolvers(new ScalarsWithMultipleTypes()) - .scalars(new GraphQLScalarType("UUID", "Test scalars with duplicate types", new Coercing() { - @Override - Object serialize(Object input) { - return null - } + .resolvers(new ScalarsWithMultipleTypes()) + .scalars(new GraphQLScalarType("UUID", "Test scalars with duplicate types", new Coercing() { + @Override + Object serialize(Object input) { + return null + } - @Override - Object parseValue(Object input) { - return null - } + @Override + Object parseValue(Object input) { + return null + } - @Override - Object parseLiteral(Object input) { - return null - } - })) - .schemaString(""" + @Override + Object parseLiteral(Object input) { + return null + } + })) + .schemaString(""" scalar UUID type Query { @@ -184,7 +187,7 @@ class SchemaClassScannerSpec extends Specification { second: UUID } """) - .scan() + .scan() then: objects.definitions.findAll { it instanceof ScalarTypeDefinition }.size() == 1 @@ -192,14 +195,15 @@ class SchemaClassScannerSpec extends Specification { class ScalarsWithMultipleTypes implements GraphQLQueryResolver { Integer first() { null } + String second() { null } } def "scanner handles multiple interfaces that are not used as field types"() { when: ScannedSchemaObjects objects = SchemaParser.newParser() - .resolvers(new MultipleInterfaces()) - .schemaString(""" + .resolvers(new MultipleInterfaces()) + .schemaString(""" type Query { query1: NamedResourceImpl query2: VersionedResourceImpl @@ -221,7 +225,7 @@ class SchemaClassScannerSpec extends Specification { version: Int! } """) - .scan() + .scan() then: objects.definitions.findAll { it instanceof InterfaceTypeDefinition }.size() == 2 @@ -229,11 +233,11 @@ class SchemaClassScannerSpec extends Specification { def "scanner handles interface implementation that is not used as field type"() { when: - ScannedSchemaObjects objects = SchemaParser.newParser() - // uncommenting the line below makes the test succeed - .dictionary(InterfaceImplementation.NamedResourceImpl.class) - .resolvers(new InterfaceImplementation()) - .schemaString(""" + ScannedSchemaObjects objects = SchemaParser.newParser() + // uncommenting the line below makes the test succeed + .dictionary(InterfaceImplementation.NamedResourceImpl.class) + .resolvers(new InterfaceImplementation()) + .schemaString(""" type Query { query1: NamedResource } @@ -246,10 +250,10 @@ class SchemaClassScannerSpec extends Specification { name: String! } """) - .scan() + .scan() then: - objects.definitions.findAll { it instanceof InterfaceTypeDefinition }.size() == 1 + objects.definitions.findAll { it instanceof InterfaceTypeDefinition }.size() == 1 } def "scanner handles custom scalars when matching input types"() { @@ -272,12 +276,13 @@ class SchemaClassScannerSpec extends Specification { }) ScannedSchemaObjects objects = SchemaParser.newParser() - .resolvers(new GraphQLQueryResolver() { - boolean hasRawScalar(Map rawScalar) { true } - boolean hasMapField(HasMapField mapField) { true } - }) - .scalars(customMap) - .schemaString(""" + .resolvers(new GraphQLQueryResolver() { + boolean hasRawScalar(Map rawScalar) { true } + + boolean hasMapField(HasMapField mapField) { true } + }) + .scalars(customMap) + .schemaString(""" type Query { hasRawScalar(customMap: customMap): Boolean hasMapField(mapField: HasMapField): Boolean @@ -289,7 +294,7 @@ class SchemaClassScannerSpec extends Specification { scalar customMap """) - .scan() + .scan() then: objects.definitions.findAll { it instanceof ScalarTypeDefinition }.size() == 2 // Boolean and customMap @@ -302,10 +307,10 @@ class SchemaClassScannerSpec extends Specification { def "scanner allows class to be used for object type and input object type"() { when: ScannedSchemaObjects objects = SchemaParser.newParser() - .resolvers(new GraphQLQueryResolver() { - Pojo test(Pojo pojo) { pojo } - }) - .schemaString(""" + .resolvers(new GraphQLQueryResolver() { + Pojo test(Pojo pojo) { pojo } + }) + .schemaString(""" type Query { test(inPojo: InPojo): OutPojo } @@ -318,7 +323,7 @@ class SchemaClassScannerSpec extends Specification { name: String } """) - .scan() + .scan() then: objects.definitions @@ -331,7 +336,7 @@ class SchemaClassScannerSpec extends Specification { def "scanner should handle nested types in input types"() { when: ScannedSchemaObjects objects = SchemaParser.newParser() - .schemaString(''' + .schemaString(''' schema { query: Query } @@ -352,9 +357,9 @@ class SchemaClassScannerSpec extends Specification { id: String } ''') - .resolvers(new NestedInterfaceTypeQuery()) - .dictionary(NestedInterfaceTypeQuery.Dog) - .scan() + .resolvers(new NestedInterfaceTypeQuery()) + .dictionary(NestedInterfaceTypeQuery.Dog) + .scan() then: objects.definitions.findAll { it instanceof ObjectTypeDefinition }.size() == 3 @@ -377,15 +382,16 @@ class SchemaClassScannerSpec extends Specification { def "scanner throws if @Batched is used on root resolver"() { when: SchemaParser.newParser() - .schemaString(''' + .schemaString(''' type Query { test: String } ''') - .resolvers(new GraphQLQueryResolver() { - @Batched List test() { null } - }) - .scan() + .resolvers(new GraphQLQueryResolver() { + @Batched + List test() { null } + }) + .scan() then: def e = thrown(ResolverError) @@ -395,7 +401,7 @@ class SchemaClassScannerSpec extends Specification { def "scanner throws if @Batched is used on data class"() { when: SchemaParser.newParser() - .schemaString(''' + .schemaString(''' type Query { test: DataClass } @@ -404,17 +410,19 @@ class SchemaClassScannerSpec extends Specification { test: String } ''') - .resolvers(new GraphQLQueryResolver() { - DataClass test() { null } + .resolvers(new GraphQLQueryResolver() { + DataClass test() { null } - class DataClass { - @Batched List test() { null } - } - }) - .scan() + class DataClass { + @Batched + List test() { null } + } + }) + .scan() then: def e = thrown(ResolverError) e.message.contains('The @Batched annotation is only allowed on non-root resolver methods') } + }