diff --git a/detekt_baseline.xml b/detekt_baseline.xml index 6bd64ae30a..e72512724e 100644 --- a/detekt_baseline.xml +++ b/detekt_baseline.xml @@ -5,9 +5,6 @@ LargeClass:SchemaGenerator.kt$SchemaGenerator MethodOverloading:SchemaGeneratorTest.kt$SchemaGeneratorTest TooManyFunctions:SchemaGenerator.kt$SchemaGenerator - UnsafeCast:SchemaGeneratorAsyncTests.kt$SchemaGeneratorAsyncTests$schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDo").type as GraphQLNonNull - UnsafeCast:SchemaGeneratorAsyncTests.kt$SchemaGeneratorAsyncTests$schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDoSingle").type as GraphQLNonNull - UnsafeCast:SchemaGeneratorAsyncTests.kt$SchemaGeneratorAsyncTests$schema.getObjectType("TopLevelQuery").getFieldDefinition("maybe").type as GraphQLNonNull ComplexInterface:SchemaGeneratorHooks.kt$SchemaGeneratorHooks diff --git a/src/main/kotlin/com/expedia/graphql/schema/extensions/annotationExtensions.kt b/src/main/kotlin/com/expedia/graphql/schema/extensions/annotationExtensions.kt index 09296c5b3f..018006e483 100644 --- a/src/main/kotlin/com/expedia/graphql/schema/extensions/annotationExtensions.kt +++ b/src/main/kotlin/com/expedia/graphql/schema/extensions/annotationExtensions.kt @@ -15,7 +15,7 @@ import kotlin.reflect.full.findAnnotation import com.expedia.graphql.annotations.GraphQLDirective as DirectiveAnnotation internal fun KAnnotatedElement.graphQLDescription(): String? { - val directiveNames = listOfDirectives().map { it.normalizeDirectiveName() } + val directiveNames = listOfDirectives().map { normalizeDirectiveName(it) } val description = this.findAnnotation()?.value @@ -86,7 +86,7 @@ private fun DirectiveAnnotation.getGraphQLDirective(): GraphQLDirective { } @Suppress("Detekt.SpreadOperator") - builder.name(name.normalizeDirectiveName()) + builder.name(normalizeDirectiveName(name)) .validLocations(*this.locations) .description(this.description) @@ -95,10 +95,15 @@ private fun DirectiveAnnotation.getGraphQLDirective(): GraphQLDirective { val value = kFunction.call(kClass) @Suppress("Detekt.UnsafeCast") val type = defaultGraphQLScalars(kFunction.returnType) as GraphQLInputType - builder.argument(GraphQLArgument.newArgument().name(propertyName).value(value).type(type).build()) + val argument = GraphQLArgument.newArgument() + .name(propertyName) + .value(value) + .type(type) + .build() + builder.argument(argument) } return builder.build() } -private fun String.normalizeDirectiveName() = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, this) +private fun normalizeDirectiveName(string: String) = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, string) diff --git a/src/main/kotlin/com/expedia/graphql/schema/extensions/kClassExtensions.kt b/src/main/kotlin/com/expedia/graphql/schema/extensions/kClassExtensions.kt index c1bd281771..65981bfeab 100644 --- a/src/main/kotlin/com/expedia/graphql/schema/extensions/kClassExtensions.kt +++ b/src/main/kotlin/com/expedia/graphql/schema/extensions/kClassExtensions.kt @@ -2,17 +2,20 @@ package com.expedia.graphql.schema.extensions import com.expedia.graphql.schema.generator.functionFilters import com.expedia.graphql.schema.generator.propertyFilters +import com.expedia.graphql.schema.hooks.NoopSchemaGeneratorHooks import com.expedia.graphql.schema.hooks.SchemaGeneratorHooks import kotlin.reflect.KClass import kotlin.reflect.full.declaredMemberFunctions import kotlin.reflect.full.declaredMemberProperties -internal fun KClass<*>.getValidProperties(hooks: SchemaGeneratorHooks? = null) = this.declaredMemberProperties - .filter { hooks?.isValidProperty(it) ?: true } +private val noopHooks = NoopSchemaGeneratorHooks() + +internal fun KClass<*>.getValidProperties(hooks: SchemaGeneratorHooks = noopHooks) = this.declaredMemberProperties + .filter { hooks.isValidProperty(it) } .filter { prop -> propertyFilters.all { it.invoke(prop) } } -internal fun KClass<*>.getValidFunctions(hooks: SchemaGeneratorHooks? = null) = this.declaredMemberFunctions - .filter { hooks?.isValidFunction(it) ?: true } +internal fun KClass<*>.getValidFunctions(hooks: SchemaGeneratorHooks = noopHooks) = this.declaredMemberFunctions + .filter { hooks.isValidFunction(it) } .filter { func -> functionFilters.all { it.invoke(func) } } internal fun KClass<*>.canBeGraphQLInterface(): Boolean = this.java.isInterface diff --git a/src/main/kotlin/com/expedia/graphql/schema/generator/SchemaGenerator.kt b/src/main/kotlin/com/expedia/graphql/schema/generator/SchemaGenerator.kt index 8153336841..0154e97e1a 100644 --- a/src/main/kotlin/com/expedia/graphql/schema/generator/SchemaGenerator.kt +++ b/src/main/kotlin/com/expedia/graphql/schema/generator/SchemaGenerator.kt @@ -16,11 +16,11 @@ import com.expedia.graphql.schema.extensions.isGraphQLContext import com.expedia.graphql.schema.extensions.isGraphQLID import com.expedia.graphql.schema.extensions.throwIfUnathorizedInterface import com.expedia.graphql.schema.extensions.wrapInNonNull +import com.expedia.graphql.schema.generator.models.KGraphQLType import com.expedia.graphql.schema.generator.state.SchemaGeneratorState import com.expedia.graphql.schema.generator.types.defaultGraphQLScalars import com.expedia.graphql.schema.generator.types.enumType import com.expedia.graphql.schema.generator.types.getInputClassName -import com.expedia.graphql.schema.models.KGraphQLType import graphql.TypeResolutionEnvironment import graphql.schema.DataFetcher import graphql.schema.GraphQLArgument @@ -271,7 +271,7 @@ internal class SchemaGenerator( } private fun interfaceType(kClass: KClass<*>): GraphQLType { - return state.cache.buildIfNotUnderConstruction(kClass) { + return state.cache.buildIfNotUnderConstruction(kClass) { _ -> val builder = GraphQLInterfaceType.newInterface() builder.name(kClass.simpleName) @@ -287,23 +287,21 @@ internal class SchemaGenerator( val interfaceType = builder.build() val implementations = subTypeMapper.getSubTypesOf(kClass) - implementations - .filterNot { it.kotlin.isAbstract } - .forEach { - val objectType = objectType(it.kotlin, interfaceType) + implementations.forEach { + val objectType = objectType(it.kotlin, interfaceType) - if (objectType !is GraphQLTypeReference) { - state.additionalTypes.add(objectType) - } - state.cache.removeTypeUnderConstruction(it.kotlin) - } + if (objectType !is GraphQLTypeReference) { + state.additionalTypes.add(objectType) + } + state.cache.removeTypeUnderConstruction(it.kotlin) + } interfaceType } } private fun unionType(kClass: KClass<*>): GraphQLType { - return state.cache.buildIfNotUnderConstruction(kClass) { + return state.cache.buildIfNotUnderConstruction(kClass) { _ -> val builder = GraphQLUnionType.newUnionType() builder.name(kClass.simpleName) @@ -311,23 +309,21 @@ internal class SchemaGenerator( builder.typeResolver { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObject().javaClass.simpleName) } val implementations = subTypeMapper.getSubTypesOf(kClass) - implementations - .filterNot { it.kotlin.isAbstract } - .forEach { - val objectType = state.cache.get(TypesCacheKey(it.kotlin.createType(), false)) ?: objectType(it.kotlin) + implementations.forEach { + val objectType = state.cache.get(TypesCacheKey(it.kotlin.createType(), false)) ?: objectType(it.kotlin) - val key = TypesCacheKey(it.kotlin.createType(), false) + val key = TypesCacheKey(it.kotlin.createType(), false) - if (objectType is GraphQLTypeReference) { - builder.possibleType(objectType) - } else { - builder.possibleType(objectType as GraphQLObjectType) - } + if (objectType is GraphQLTypeReference) { + builder.possibleType(objectType) + } else { + builder.possibleType(objectType as GraphQLObjectType) + } - if (state.cache.doesNotContain(it.kotlin)) { - state.cache.put(key, KGraphQLType(it.kotlin, objectType)) - } - } + if (state.cache.doesNotContain(it.kotlin)) { + state.cache.put(key, KGraphQLType(it.kotlin, objectType)) + } + } builder.build() } diff --git a/src/main/kotlin/com/expedia/graphql/schema/generator/SubTypeMapper.kt b/src/main/kotlin/com/expedia/graphql/schema/generator/SubTypeMapper.kt index f245480a51..a398734a55 100644 --- a/src/main/kotlin/com/expedia/graphql/schema/generator/SubTypeMapper.kt +++ b/src/main/kotlin/com/expedia/graphql/schema/generator/SubTypeMapper.kt @@ -7,6 +7,7 @@ internal class SubTypeMapper(supportedPackages: List) { private val reflections = Reflections(supportedPackages) - fun getSubTypesOf(kclass: KClass<*>): MutableSet> = + fun getSubTypesOf(kclass: KClass<*>): List> = reflections.getSubTypesOf(Class.forName(kclass.javaObjectType.name)) + .filterNot { it.kotlin.isAbstract } } diff --git a/src/main/kotlin/com/expedia/graphql/schema/generator/TypesCache.kt b/src/main/kotlin/com/expedia/graphql/schema/generator/TypesCache.kt index 3c78fb11c2..be18891aec 100644 --- a/src/main/kotlin/com/expedia/graphql/schema/generator/TypesCache.kt +++ b/src/main/kotlin/com/expedia/graphql/schema/generator/TypesCache.kt @@ -5,7 +5,7 @@ import com.expedia.graphql.schema.exceptions.ConflictingTypesException import com.expedia.graphql.schema.exceptions.CouldNotGetJvmNameOfKTypeException import com.expedia.graphql.schema.exceptions.CouldNotGetNameOfEnumException import com.expedia.graphql.schema.exceptions.TypeNotSupportedException -import com.expedia.graphql.schema.models.KGraphQLType +import com.expedia.graphql.schema.generator.models.KGraphQLType import graphql.schema.GraphQLType import graphql.schema.GraphQLTypeReference import kotlin.reflect.KClass diff --git a/src/main/kotlin/com/expedia/graphql/schema/models/KGraphQLType.kt b/src/main/kotlin/com/expedia/graphql/schema/generator/models/KGraphQLType.kt similarity index 79% rename from src/main/kotlin/com/expedia/graphql/schema/models/KGraphQLType.kt rename to src/main/kotlin/com/expedia/graphql/schema/generator/models/KGraphQLType.kt index 2f648eb475..5935964720 100644 --- a/src/main/kotlin/com/expedia/graphql/schema/models/KGraphQLType.kt +++ b/src/main/kotlin/com/expedia/graphql/schema/generator/models/KGraphQLType.kt @@ -1,4 +1,4 @@ -package com.expedia.graphql.schema.models +package com.expedia.graphql.schema.generator.models import graphql.schema.GraphQLType import kotlin.reflect.KClass diff --git a/src/main/kotlin/com/expedia/graphql/toSchema.kt b/src/main/kotlin/com/expedia/graphql/toSchema.kt index 3fae7e4f48..698d1d0dc9 100644 --- a/src/main/kotlin/com/expedia/graphql/toSchema.kt +++ b/src/main/kotlin/com/expedia/graphql/toSchema.kt @@ -13,7 +13,7 @@ import graphql.schema.GraphQLSchema * @param config Schema generation configuration */ fun toSchema( - queries: List = emptyList(), + queries: List, mutations: List = emptyList(), config: SchemaGeneratorConfig ): GraphQLSchema { diff --git a/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorAsyncTests.kt b/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorAsyncTests.kt index dd02ec8968..9f02530924 100644 --- a/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorAsyncTests.kt +++ b/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorAsyncTests.kt @@ -29,7 +29,7 @@ class SchemaGeneratorAsyncTests { fun `SchemaGenerator strips type argument from CompletableFuture to support async servlet`() { val schema = toSchema(listOf(TopLevelObjectDef(AsyncQuery())), config = testSchemaConfig) val returnTypeName = - (schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDo").type as GraphQLNonNull).wrappedType.name + (schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDo").type as? GraphQLNonNull)?.wrappedType?.name assertEquals("Int", returnTypeName) } @@ -37,7 +37,7 @@ class SchemaGeneratorAsyncTests { fun `SchemaGenerator strips type argument from RxJava2 Observable`() { val schema = toSchema(listOf(TopLevelObjectDef(RxJava2Query())), config = configWithRxJavaMonads) val returnTypeName = - (schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDo").type as GraphQLNonNull).wrappedType.name + (schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDo").type as? GraphQLNonNull)?.wrappedType?.name assertEquals("Int", returnTypeName) } @@ -45,7 +45,7 @@ class SchemaGeneratorAsyncTests { fun `SchemaGenerator strips type argument from RxJava2 Single`() { val schema = toSchema(listOf(TopLevelObjectDef(RxJava2Query())), config = configWithRxJavaMonads) val returnTypeName = - (schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDoSingle").type as GraphQLNonNull).wrappedType.name + (schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDoSingle").type as? GraphQLNonNull)?.wrappedType?.name assertEquals("Int", returnTypeName) } @@ -53,7 +53,7 @@ class SchemaGeneratorAsyncTests { fun `SchemaGenerator strips type argument from RxJava2 Maybe`() { val schema = toSchema(listOf(TopLevelObjectDef(RxJava2Query())), config = configWithRxJavaMonads) val returnTypeName = - (schema.getObjectType("TopLevelQuery").getFieldDefinition("maybe").type as GraphQLNonNull).wrappedType.name + (schema.getObjectType("TopLevelQuery").getFieldDefinition("maybe").type as? GraphQLNonNull)?.wrappedType?.name assertEquals("Int", returnTypeName) } diff --git a/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorTest.kt b/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorTest.kt index 047b4a121e..ca11337fa0 100644 --- a/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorTest.kt +++ b/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorTest.kt @@ -23,7 +23,10 @@ import kotlin.test.assertFailsWith import kotlin.test.assertNotNull import kotlin.test.assertTrue -@Suppress("Detekt.UnusedPrivateMember", "Detekt.FunctionOnlyReturningConstant", "Detekt.LargeClass") +@Suppress("Detekt.UnusedPrivateMember", + "Detekt.FunctionOnlyReturningConstant", + "Detekt.LargeClass", + "Detekt.MethodOverloading") class SchemaGeneratorTest { @Test fun `SchemaGenerator generates a simple GraphQL schema`() { @@ -266,7 +269,7 @@ class SchemaGeneratorTest { @Test fun `SchemaGenerator supports Scalar GraphQLID for input types`() { - val schema = toSchema(mutations = listOf(TopLevelObjectDef(MutationWithId())), config = testSchemaConfig) + val schema = toSchema(queries = emptyList(), mutations = listOf(TopLevelObjectDef(MutationWithId())), config = testSchemaConfig) val furnitureType = schema.getObjectType("Furniture") val serialField = furnitureType.getFieldDefinition("serial").type as? GraphQLNonNull diff --git a/src/test/kotlin/com/expedia/graphql/schema/generator/SubTypeMapperTest.kt b/src/test/kotlin/com/expedia/graphql/schema/generator/SubTypeMapperTest.kt index f9281538f5..4201c88432 100644 --- a/src/test/kotlin/com/expedia/graphql/schema/generator/SubTypeMapperTest.kt +++ b/src/test/kotlin/com/expedia/graphql/schema/generator/SubTypeMapperTest.kt @@ -3,6 +3,7 @@ package com.expedia.graphql.schema.generator import org.junit.jupiter.api.Test import kotlin.test.assertEquals +@Suppress("Detekt.MethodOverloading") internal class SubTypeMapperTest { private interface MyInterface { @@ -17,21 +18,45 @@ internal class SubTypeMapperTest { override fun getValue() = 2 } + @Suppress("Detekt.UnnecessaryAbstractClass") + private abstract class MyAbstractClass { + abstract fun getValue(): Int + } + + private class ThirdClass : MyAbstractClass() { + override fun getValue() = 3 + } + + private abstract class FourthClass : MyAbstractClass() { + override fun getValue() = 3 + + abstract fun getSecondAbsctractValue(): Int + } + @Test fun `valid subtypes`() { val mapper = SubTypeMapper(listOf("com.expedia.graphql")) - val set = mapper.getSubTypesOf(MyInterface::class) + val list = mapper.getSubTypesOf(MyInterface::class) + + assertEquals(expected = 2, actual = list.size) + } + + @Test + fun `abstract subtypes`() { + + val mapper = SubTypeMapper(listOf("com.expedia.graphql")) + val list = mapper.getSubTypesOf(MyAbstractClass::class) - assertEquals(expected = 2, actual = set.size) + assertEquals(expected = 1, actual = list.size) } @Test fun `subtypes of non-supported packages`() { val mapper = SubTypeMapper(listOf("com.example")) - val set = mapper.getSubTypesOf(MyInterface::class) + val list = mapper.getSubTypesOf(MyInterface::class) - assertEquals(expected = 0, actual = set.size) + assertEquals(expected = 0, actual = list.size) } } diff --git a/src/test/kotlin/com/expedia/graphql/schema/generator/TypesCacheTest.kt b/src/test/kotlin/com/expedia/graphql/schema/generator/TypesCacheTest.kt index 0cfd0748f1..542442550f 100644 --- a/src/test/kotlin/com/expedia/graphql/schema/generator/TypesCacheTest.kt +++ b/src/test/kotlin/com/expedia/graphql/schema/generator/TypesCacheTest.kt @@ -1,6 +1,6 @@ package com.expedia.graphql.schema.generator -import com.expedia.graphql.schema.models.KGraphQLType +import com.expedia.graphql.schema.generator.models.KGraphQLType import graphql.schema.GraphQLType import org.junit.jupiter.api.Test import kotlin.reflect.full.starProjectedType diff --git a/src/test/kotlin/com/expedia/graphql/schema/models/KGraphQLTypeTest.kt b/src/test/kotlin/com/expedia/graphql/schema/generator/models/KGraphQLTypeTest.kt similarity index 90% rename from src/test/kotlin/com/expedia/graphql/schema/models/KGraphQLTypeTest.kt rename to src/test/kotlin/com/expedia/graphql/schema/generator/models/KGraphQLTypeTest.kt index c29febf27e..4300e9a3d1 100644 --- a/src/test/kotlin/com/expedia/graphql/schema/models/KGraphQLTypeTest.kt +++ b/src/test/kotlin/com/expedia/graphql/schema/generator/models/KGraphQLTypeTest.kt @@ -1,4 +1,4 @@ -package com.expedia.graphql.schema.models +package com.expedia.graphql.schema.generator.models import graphql.schema.GraphQLType import org.junit.jupiter.api.Test