Skip to content

Reuse schema generator for tests #641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ fun toFederatedSchema(
subscriptions: List<TopLevelObject> = emptyList()
): GraphQLSchema {
val generator = FederatedSchemaGenerator(config)
return generator.generateSchema(queries, mutations, subscriptions)
return generator.use {
it.generateSchema(queries, mutations, subscriptions)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import graphql.schema.GraphQLCodeRegistry
import graphql.schema.GraphQLDirective
import graphql.schema.GraphQLSchema
import graphql.schema.GraphQLType
import java.io.Closeable
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
import kotlin.reflect.KType
Expand All @@ -39,7 +40,7 @@ import kotlin.reflect.full.createType
* This class maintains the state of the schema while generation is taking place. It is passed into the internal functions
* so they can use the cache and add additional types and directives into the schema as they parse the Kotlin code.
*/
open class SchemaGenerator(internal val config: SchemaGeneratorConfig) {
open class SchemaGenerator(internal val config: SchemaGeneratorConfig) : Closeable {

internal val additionalTypes = mutableSetOf<KType>()
internal val classScanner = ClassScanner(config.supportedPackages)
Expand All @@ -56,20 +57,17 @@ open class SchemaGenerator(internal val config: SchemaGeneratorConfig) {
subscriptions: List<TopLevelObject> = emptyList(),
additionalTypes: Set<KType> = emptySet()
): GraphQLSchema {

this.additionalTypes.addAll(additionalTypes)

val builder = GraphQLSchema.newSchema()
builder.query(generateQueries(this, queries))
builder.mutation(generateMutations(this, mutations))
builder.subscription(generateSubscriptions(this, subscriptions))
builder.additionalTypes(generateAdditionalTypes())
builder.additionalDirectives(directives.values.toSet())
builder.codeRegistry(codeRegistry.build())
val schema = config.hooks.willBuildSchema(builder).build()

classScanner.close()
Copy link
Contributor Author

@smyrick smyrick Mar 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be closed automatically when the schema generator is removed by GC

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'd still explicitly close it just in case

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since ScanResult implements Closeable we could use Kotlin use (https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.io/use.html)


return schema
return config.hooks.willBuildSchema(builder).build()
}

/**
Expand Down Expand Up @@ -100,4 +98,20 @@ open class SchemaGenerator(internal val config: SchemaGeneratorConfig) {

return graphqlTypes.toSet()
}

/**
* Clear the generator type cache, reflection scan, additional types,
* and the saved directives. You may want call this after you have
* called [generateSchema] and performed some other actions which is why
* we have a separate method to explicitly clear.
*
* If you use the built in [com.expediagroup.graphql.toSchema], we will handle
* clean up of resources for you.
*/
override fun close() {
classScanner.close()
cache.clear()
additionalTypes.clear()
directives.clear()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,11 @@ package com.expediagroup.graphql.generator.state
import io.github.classgraph.ClassGraph
import io.github.classgraph.ClassInfo
import io.github.classgraph.ClassInfoList
import java.io.Closeable
import kotlin.reflect.KClass
import kotlin.reflect.jvm.jvmName

internal class ClassScanner(supportedPackages: List<String>) {
internal class ClassScanner(supportedPackages: List<String>) : Closeable {

@Suppress("Detekt.SpreadOperator")
private val scanResult = ClassGraph()
Expand All @@ -39,9 +40,9 @@ internal class ClassScanner(supportedPackages: List<String>) {
.filterNot { it.isAbstract }
}

fun getClassesWithAnnotation(annotation: KClass<*>) = scanResult.getClassesWithAnnotation(annotation.jvmName).map { it.loadClass().kotlin }
override fun close() = scanResult.close()

fun close() = scanResult.close()
fun getClassesWithAnnotation(annotation: KClass<*>) = scanResult.getClassesWithAnnotation(annotation.jvmName).map { it.loadClass().kotlin }

@Suppress("Detekt.SwallowedException")
private fun getImplementingClasses(classInfo: ClassInfo) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,7 @@ fun toSchema(
subscriptions: List<TopLevelObject> = emptyList()
): GraphQLSchema {
val generator = SchemaGenerator(config)
return generator.generateSchema(queries, mutations, subscriptions)
return generator.use {
it.generateSchema(queries, mutations, subscriptions)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ package com.expediagroup.graphql.directives

import com.expediagroup.graphql.TopLevelObject
import com.expediagroup.graphql.annotations.GraphQLDirective
import com.expediagroup.graphql.generator.SchemaGenerator
import com.expediagroup.graphql.getTestSchemaConfigWithHooks
import com.expediagroup.graphql.hooks.SchemaGeneratorHooks
import com.expediagroup.graphql.testSchemaConfig
import com.expediagroup.graphql.toSchema
import com.expediagroup.graphql.testGenerator
import graphql.Scalars
import graphql.schema.GraphQLNonNull
import graphql.schema.GraphQLObjectType
Expand All @@ -34,7 +34,7 @@ class DirectiveTests {

@Test
fun `SchemaGenerator marks deprecated fields in the return objects`() {
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithDeprecatedFields())), config = testSchemaConfig)
val schema = testGenerator.generateSchema(queries = listOf(TopLevelObject(QueryWithDeprecatedFields())))
val topLevelQuery = schema.getObjectType("Query")
val query = topLevelQuery.getFieldDefinition("deprecatedFieldQuery")
val result = (query.type as? GraphQLNonNull)?.wrappedType as? GraphQLObjectType
Expand All @@ -46,7 +46,7 @@ class DirectiveTests {

@Test
fun `SchemaGenerator marks deprecated queries and documents replacement`() {
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithDeprecatedFields())), config = testSchemaConfig)
val schema = testGenerator.generateSchema(queries = listOf(TopLevelObject(QueryWithDeprecatedFields())))
val topLevelQuery = schema.getObjectType("Query")
val query = topLevelQuery.getFieldDefinition("deprecatedQueryWithReplacement")

Expand All @@ -56,7 +56,7 @@ class DirectiveTests {

@Test
fun `SchemaGenerator marks deprecated queries`() {
val schema = toSchema(queries = listOf(TopLevelObject(QueryWithDeprecatedFields())), config = testSchemaConfig)
val schema = testGenerator.generateSchema(queries = listOf(TopLevelObject(QueryWithDeprecatedFields())))
val topLevelQuery = schema.getObjectType("Query")
val query = topLevelQuery.getFieldDefinition("deprecatedQuery")
assertTrue(query.isDeprecated)
Expand All @@ -70,7 +70,8 @@ class DirectiveTests {
override val wiringFactory: KotlinDirectiveWiringFactory
get() = KotlinDirectiveWiringFactory(manualWiring = mapOf("dummyDirective" to wiring, "RightNameDirective" to wiring))
})
val schema = toSchema(queries = listOf(TopLevelObject(QueryObject())), config = config)
val generator = SchemaGenerator(config)
val schema = generator.generateSchema(queries = listOf(TopLevelObject(QueryObject())))

val query = schema.queryType.getFieldDefinition("query")
assertNotNull(query)
Expand All @@ -84,7 +85,8 @@ class DirectiveTests {
override val wiringFactory: KotlinDirectiveWiringFactory
get() = KotlinDirectiveWiringFactory(manualWiring = mapOf("dummyDirective" to wiring, "RightNameDirective" to wiring))
})
val schema = toSchema(queries = listOf(TopLevelObject(QueryObject())), config = config)
val generator = SchemaGenerator(config)
val schema = generator.generateSchema(queries = listOf(TopLevelObject(QueryObject())))

val directive = assertNotNull(
(schema.getType("Location") as? GraphQLObjectType)
Expand All @@ -95,50 +97,50 @@ class DirectiveTests {
assertEquals("arg", directive.arguments[0].name)
assertEquals(GraphQLNonNull(Scalars.GraphQLString), directive.arguments[0].type)
}
}

@GraphQLDirective(name = "RightNameDirective")
annotation class WrongNameDirective(val arg: String)
@GraphQLDirective(name = "RightNameDirective")
annotation class WrongNameDirective(val arg: String)

@GraphQLDirective
annotation class DummyDirective
@GraphQLDirective
annotation class DummyDirective

class Geography(
val id: Int?,
val type: GeoType,
val locations: List<Location>
) {
@Suppress("Detekt.FunctionOnlyReturningConstant")
fun somethingCool(): String = "Something cool"
}
class Geography(
val id: Int?,
val type: GeoType,
val locations: List<Location>
) {
@Suppress("Detekt.FunctionOnlyReturningConstant")
fun somethingCool(): String = "Something cool"
}

enum class GeoType {
CITY, STATE
}
enum class GeoType {
CITY, STATE
}

@WrongNameDirective(arg = "arenaming")
data class Location(val lat: Double, val lon: Double)
@WrongNameDirective(arg = "arenaming")
data class Location(val lat: Double, val lon: Double)

class QueryObject {
class QueryObject {

@DummyDirective
fun query(value: Int): Geography = Geography(value, GeoType.CITY, listOf())
}
@DummyDirective
fun query(value: Int): Geography = Geography(value, GeoType.CITY, listOf())
}

class QueryWithDeprecatedFields {
@Deprecated("this query is deprecated")
fun deprecatedQuery(something: String) = something
class QueryWithDeprecatedFields {
@Deprecated("this query is deprecated")
fun deprecatedQuery(something: String) = something

@Deprecated("this query is also deprecated", replaceWith = ReplaceWith("shinyNewQuery"))
fun deprecatedQueryWithReplacement(something: String) = something
@Deprecated("this query is also deprecated", replaceWith = ReplaceWith("shinyNewQuery"))
fun deprecatedQueryWithReplacement(something: String) = something

fun deprecatedFieldQuery(something: String) = ClassWithDeprecatedField(something, something.reversed())
fun deprecatedFieldQuery(something: String) = ClassWithDeprecatedField(something, something.reversed())

fun deprecatedArgumentQuery(input: ClassWithDeprecatedField) = input.something
}
fun deprecatedArgumentQuery(input: ClassWithDeprecatedField) = input.something
}

data class ClassWithDeprecatedField(
val something: String,
@Deprecated("this field is deprecated")
val deprecatedField: String
)
data class ClassWithDeprecatedField(
val something: String,
@Deprecated("this field is deprecated")
val deprecatedField: String
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package com.expediagroup.graphql.execution
import com.expediagroup.graphql.SchemaGeneratorConfig
import com.expediagroup.graphql.TopLevelObject
import com.expediagroup.graphql.extensions.deepName
import com.expediagroup.graphql.toSchema
import com.expediagroup.graphql.generator.SchemaGenerator
import graphql.GraphQL
import graphql.schema.DataFetcher
import graphql.schema.DataFetcherFactories
Expand All @@ -34,7 +34,8 @@ class CustomDataFetcherTests {
@Test
fun `Custom DataFetcher can be used on functions`() {
val config = SchemaGeneratorConfig(supportedPackages = listOf("com.expediagroup"), dataFetcherFactoryProvider = CustomDataFetcherFactoryProvider())
val schema = toSchema(queries = listOf(TopLevelObject(AnimalQuery())), config = config)
val generator = SchemaGenerator(config)
val schema = generator.generateSchema(queries = listOf(TopLevelObject(AnimalQuery())))

val animalType = schema.getObjectType("Animal")
assertEquals("AnimalDetails!", animalType.getFieldDefinition("details").type.deepName)
Expand All @@ -49,37 +50,37 @@ class CustomDataFetcherTests {
val details = data?.get("details") as? Map<*, *>
assertEquals(11, details?.get("specialId"))
}
}

class AnimalQuery {
fun findAnimal(): Animal = Animal(1, "cat")
}
class AnimalQuery {
fun findAnimal(): Animal = Animal(1, "cat")
}

@Suppress("DataClassShouldBeImmutable")
data class Animal(
val id: Int,
val type: String
) {
lateinit var details: AnimalDetails
}
@Suppress("DataClassShouldBeImmutable")
data class Animal(
val id: Int,
val type: String
) {
lateinit var details: AnimalDetails
}

data class AnimalDetails(val specialId: Int)
data class AnimalDetails(val specialId: Int)

class CustomDataFetcherFactoryProvider : SimpleKotlinDataFetcherFactoryProvider() {
class CustomDataFetcherFactoryProvider : SimpleKotlinDataFetcherFactoryProvider() {

override fun propertyDataFetcherFactory(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> =
if (kProperty.isLateinit) {
DataFetcherFactories.useDataFetcher(AnimalDetailsDataFetcher())
} else {
super.propertyDataFetcherFactory(kClass, kProperty)
}
}
override fun propertyDataFetcherFactory(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> =
if (kProperty.isLateinit) {
DataFetcherFactories.useDataFetcher(AnimalDetailsDataFetcher())
} else {
super.propertyDataFetcherFactory(kClass, kProperty)
}
}

class AnimalDetailsDataFetcher : DataFetcher<Any?> {
class AnimalDetailsDataFetcher : DataFetcher<Any?> {

override fun get(environment: DataFetchingEnvironment?): AnimalDetails {
val animal = environment?.getSource<Animal>()
val specialId = animal?.id?.plus(10) ?: 0
return animal.let { AnimalDetails(specialId) }
override fun get(environment: DataFetchingEnvironment?): AnimalDetails {
val animal = environment?.getSource<Animal>()
val specialId = animal?.id?.plus(10) ?: 0
return animal.let { AnimalDetails(specialId) }
}
}
}
Loading