Skip to content

Commit 7bae973

Browse files
smyrickShane Myrick
and
Shane Myrick
authored
Add invalid packages exception (ExpediaGroup#793)
Co-authored-by: Shane Myrick <[email protected]>
1 parent f789415 commit 7bae973

File tree

7 files changed

+86
-19
lines changed

7 files changed

+86
-19
lines changed

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ kotlinJvmVersion = 1.8
1818
kotlinVersion = 1.3.72
1919
kotlinCoroutinesVersion = 1.3.7
2020

21-
classGraphVersion = 4.8.85
21+
classGraphVersion = 4.8.87
2222
graphQLJavaVersion = 15.0
2323
jacksonVersion = 2.11.0
2424
kotlinPoetVersion = 1.6.0
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2020 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.exceptions
18+
19+
/**
20+
* Thrown when the scanned packages contain no classes
21+
*/
22+
class InvalidPackagesException(packages: List<String>) :
23+
GraphQLKotlinException("The configured packages do not contain any valid classes: \"$packages\"")

graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/exceptions/TypeNotSupportedException.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,4 @@ import kotlin.reflect.KType
2222
* Thrown when the generator does not have a type to map to in GraphQL or in the hooks.
2323
*/
2424
class TypeNotSupportedException(kType: KType, packageList: List<String>) :
25-
GraphQLKotlinException("Cannot convert $kType since it is not a valid GraphQL type or outside the supported packages $packageList")
25+
GraphQLKotlinException("Cannot convert $kType since it is not a valid GraphQL type or outside the supported packages \"$packageList\"")

graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/SchemaGenerator.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package com.expediagroup.graphql.generator
1818

1919
import com.expediagroup.graphql.SchemaGeneratorConfig
2020
import com.expediagroup.graphql.TopLevelObject
21+
import com.expediagroup.graphql.exceptions.InvalidPackagesException
2122
import com.expediagroup.graphql.generator.state.ClassScanner
2223
import com.expediagroup.graphql.generator.state.TypesCache
2324
import com.expediagroup.graphql.generator.types.generateGraphQLType
@@ -40,7 +41,10 @@ import kotlin.reflect.full.createType
4041
*
4142
* This class maintains the state of the schema while generation is taking place. It is passed into the internal functions
4243
* so they can use the cache and add additional types and directives into the schema as they parse the Kotlin code.
43-
*/
44+
*
45+
* This class should be used from a try-with-resouces block
46+
* or another closable object as the internals can take up a lot of resources.
47+
*/
4448
open class SchemaGenerator(internal val config: SchemaGeneratorConfig) : Closeable {
4549

4650
internal val additionalTypes: MutableSet<KType> = mutableSetOf()
@@ -49,6 +53,15 @@ open class SchemaGenerator(internal val config: SchemaGeneratorConfig) : Closeab
4953
internal val codeRegistry = GraphQLCodeRegistry.newCodeRegistry()
5054
internal val directives = ConcurrentHashMap<String, GraphQLDirective>()
5155

56+
/**
57+
* Validate that the supported packages contain classes
58+
*/
59+
init {
60+
if (classScanner.isEmptyScan()) {
61+
throw InvalidPackagesException(config.supportedPackages)
62+
}
63+
}
64+
5265
/**
5366
* Generate a schema given a list of objects to parse for the queries, mutations, and subscriptions.
5467
*/
@@ -70,6 +83,7 @@ open class SchemaGenerator(internal val config: SchemaGeneratorConfig) : Closeab
7083
if (!config.introspectionEnabled) {
7184
codeRegistry.fieldVisibility(NoIntrospectionGraphqlFieldVisibility.NO_INTROSPECTION_FIELD_VISIBILITY)
7285
}
86+
7387
builder.codeRegistry(codeRegistry.build())
7488

7589
return config.hooks.willBuildSchema(builder).build()

graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/state/ClassScanner.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ import java.io.Closeable
2323
import kotlin.reflect.KClass
2424
import kotlin.reflect.jvm.jvmName
2525

26+
/**
27+
* This class should be used from a try-with-resouces block
28+
* or another closable object as the internal scan result can take up a lot of resources.
29+
*/
2630
internal class ClassScanner(supportedPackages: List<String>) : Closeable {
2731

2832
@Suppress("Detekt.SpreadOperator")
@@ -31,6 +35,14 @@ internal class ClassScanner(supportedPackages: List<String>) : Closeable {
3135
.whitelistPackages(*supportedPackages.toTypedArray())
3236
.scan()
3337

38+
/**
39+
* Return true if there are no valid packages scanned
40+
*/
41+
fun isEmptyScan() = scanResult.packageInfo.isEmpty()
42+
43+
/**
44+
* Get the sub-types/implementations of specific KClass
45+
*/
3446
fun getSubTypesOf(kclass: KClass<*>): List<KClass<*>> {
3547
val classInfo = scanResult.getClassInfo(kclass.jvmName) ?: return emptyList()
3648

@@ -40,8 +52,14 @@ internal class ClassScanner(supportedPackages: List<String>) : Closeable {
4052
.filterNot { it.isAbstract }
4153
}
4254

55+
/**
56+
* Find any class that has the specified annotation
57+
*/
4358
fun getClassesWithAnnotation(annotation: KClass<*>) = scanResult.getClassesWithAnnotation(annotation.jvmName).map { it.loadClass().kotlin }
4459

60+
/**
61+
* Clean up the scan result resources
62+
*/
4563
override fun close() = scanResult.close()
4664

4765
@Suppress("Detekt.SwallowedException")

graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/SchemaGeneratorTest.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
package com.expediagroup.graphql.generator
1818

1919
import com.expediagroup.graphql.SchemaGeneratorConfig
20+
import com.expediagroup.graphql.exceptions.InvalidPackagesException
2021
import com.expediagroup.graphql.extensions.deepName
2122
import org.junit.jupiter.api.Test
2223
import kotlin.reflect.KClass
2324
import kotlin.test.assertEquals
25+
import kotlin.test.assertFailsWith
2426

2527
class SchemaGeneratorTest {
2628

@@ -51,6 +53,14 @@ class SchemaGeneratorTest {
5153
assertEquals("SomeObjectWithAnnotation!", result.first().deepName)
5254
}
5355

56+
@Test
57+
fun invalidPackagesThrowsException() {
58+
assertFailsWith(InvalidPackagesException::class) {
59+
val config = SchemaGeneratorConfig(listOf("foo.bar"))
60+
SchemaGenerator(config)
61+
}
62+
}
63+
5464
class CustomSchemaGenerator(config: SchemaGeneratorConfig) : SchemaGenerator(config) {
5565
internal fun addTypes(annotation: KClass<*>) = addAdditionalTypesWithAnnotation(annotation)
5666

graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/state/ClassScannerTest.kt

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,46 +20,46 @@ import com.expediagroup.graphql.defaultSupportedPackages
2020
import org.junit.jupiter.api.AfterAll
2121
import org.junit.jupiter.api.Test
2222
import kotlin.test.assertEquals
23+
import kotlin.test.assertTrue
2324

24-
@Suppress("Detekt.UnusedPrivateClass")
25-
internal class ClassScannerTest {
25+
class ClassScannerTest {
2626

27-
private interface NoSubTypesInterface
27+
interface NoSubTypesInterface
2828

29-
private abstract class NoSubTypesClass
29+
abstract class NoSubTypesClass
3030

31-
private interface MyInterface {
31+
interface MyInterface {
3232
fun getValue(): Int
3333
}
3434

35-
private class FirstClass : MyInterface {
35+
class FirstClass : MyInterface {
3636
override fun getValue() = 1
3737
}
3838

39-
private class SecondClass : MyInterface {
39+
class SecondClass : MyInterface {
4040
override fun getValue() = 2
4141
}
4242

4343
@Suppress("Detekt.UnnecessaryAbstractClass")
44-
private abstract class MyAbstractClass {
44+
abstract class MyAbstractClass {
4545
abstract fun someValue(): Int
4646
}
4747

48-
private class ThirdClass : MyAbstractClass() {
48+
class ThirdClass : MyAbstractClass() {
4949
override fun someValue() = 3
5050
}
5151

52-
private abstract class FourthClass : MyAbstractClass() {
52+
abstract class FourthClass : MyAbstractClass() {
5353
override fun someValue() = 3
5454

5555
abstract fun getOtherValue(): Int
5656
}
5757

58-
private annotation class SimpleAnnotation
59-
private annotation class OtherSimpleAnnotation
58+
annotation class SimpleAnnotation
59+
annotation class OtherSimpleAnnotation
6060

6161
@SimpleAnnotation
62-
private class MyClassWithAnnotaiton
62+
class MyClassWithAnnotaiton
6363

6464
private val basicClassScanner = ClassScanner(defaultSupportedPackages)
6565

@@ -78,9 +78,11 @@ internal class ClassScannerTest {
7878
@Test
7979
fun `subtypes of non-supported packages`() {
8080
val classScannerOfOtherPackages = ClassScanner(listOf("com.example"))
81-
val list = classScannerOfOtherPackages.getSubTypesOf(MyInterface::class)
82-
assertEquals(expected = 0, actual = list.size)
83-
classScannerOfOtherPackages.close()
81+
classScannerOfOtherPackages.use {
82+
assertTrue(classScannerOfOtherPackages.isEmptyScan())
83+
val list = classScannerOfOtherPackages.getSubTypesOf(MyInterface::class)
84+
assertEquals(expected = 0, actual = list.size)
85+
}
8486
}
8587

8688
@Test

0 commit comments

Comments
 (0)