Skip to content

Commit d5b993f

Browse files
gscheibelbrennantaylor
authored andcommitted
Add support for GraphQL Scalar ID (#54)
1 parent f513d41 commit d5b993f

File tree

5 files changed

+68
-7
lines changed

5 files changed

+68
-7
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.expedia.graphql.annotations
2+
3+
/**
4+
* Used to indicate that a property of type Int, String, Long, java.util.UUID is a GraphqQL Scalar ID
5+
*/
6+
annotation class GraphQLID

src/main/kotlin/com/expedia/graphql/schema/extensions/annotationExtensions.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.expedia.graphql.schema.extensions
22

33
import com.expedia.graphql.annotations.GraphQLDescription
4+
import com.expedia.graphql.annotations.GraphQLID
45
import com.expedia.graphql.annotations.GraphQLIgnore
56
import com.expedia.graphql.schema.exceptions.CouldNotGetNameOfAnnotationException
67
import com.expedia.graphql.schema.generator.isNotBlackListed
@@ -60,6 +61,8 @@ internal fun KAnnotatedElement.getDeprecationReason(): String? {
6061

6162
internal fun KAnnotatedElement.isGraphQLIgnored() = this.findAnnotation<GraphQLIgnore>() != null
6263

64+
internal fun KAnnotatedElement.isGraphQLID() = this.findAnnotation<GraphQLID>() != null
65+
6366
internal fun Annotation.getDirectiveInfo() =
6467
this.annotationClass.annotations.find { it is DirectiveAnnotation } as? DirectiveAnnotation
6568

src/main/kotlin/com/expedia/graphql/schema/generator/SchemaGenerator.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.expedia.graphql.schema.extensions.getValidFunctions
1313
import com.expedia.graphql.schema.extensions.getValidProperties
1414
import com.expedia.graphql.schema.extensions.graphQLDescription
1515
import com.expedia.graphql.schema.extensions.isGraphQLContext
16+
import com.expedia.graphql.schema.extensions.isGraphQLID
1617
import com.expedia.graphql.schema.extensions.throwIfUnathorizedInterface
1718
import com.expedia.graphql.schema.extensions.wrapInNonNull
1819
import com.expedia.graphql.schema.generator.types.defaultGraphQLScalars
@@ -156,7 +157,7 @@ internal class SchemaGenerator(
156157
}
157158

158159
private fun property(prop: KProperty<*>): GraphQLFieldDefinition {
159-
val propertyType = graphQLTypeOf(prop.returnType) as GraphQLOutputType
160+
val propertyType = graphQLTypeOf(type = prop.returnType, annotatedAsID = prop.isGraphQLID()) as GraphQLOutputType
160161

161162
val fieldBuilder = GraphQLFieldDefinition.newFieldDefinition()
162163
.description(prop.graphQLDescription())
@@ -180,9 +181,9 @@ internal class SchemaGenerator(
180181
.build()
181182
}
182183

183-
private fun graphQLTypeOf(type: KType, inputType: Boolean = false): GraphQLType {
184+
private fun graphQLTypeOf(type: KType, inputType: Boolean = false, annotatedAsID: Boolean = false): GraphQLType {
184185
val hookGraphQLType = config.hooks.willGenerateGraphQLType(type)
185-
val graphQLType = hookGraphQLType ?: defaultGraphQLScalars(type) ?: objectFromReflection(type, inputType)
186+
val graphQLType = hookGraphQLType ?: defaultGraphQLScalars(type, annotatedAsID) ?: objectFromReflection(type, inputType)
186187
val typeWithNullityTakenIntoAccount = graphQLType.wrapInNonNull(type)
187188
config.hooks.didGenerateGraphQLType(type, typeWithNullityTakenIntoAccount)
188189
return typeWithNullityTakenIntoAccount

src/main/kotlin/com/expedia/graphql/schema/generator/types/defaultGraphQLScalars.kt

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import graphql.Scalars
44
import graphql.schema.GraphQLType
55
import java.math.BigDecimal
66
import java.math.BigInteger
7+
import java.util.UUID
78
import kotlin.reflect.KClass
89
import kotlin.reflect.KType
910

@@ -20,8 +21,18 @@ private val defaultScalarsMap = mapOf(
2021
Boolean::class to Scalars.GraphQLBoolean
2122
)
2223

23-
internal fun defaultGraphQLScalars(type: KType): GraphQLType? {
24-
val kclass = type.classifier as? KClass<*>
24+
private val validIdTypes = listOf(Int::class, String::class, Long::class, UUID::class)
2525

26-
return defaultScalarsMap[kclass]
26+
internal fun defaultGraphQLScalars(type: KType, annotatedAsID: Boolean = false): GraphQLType? {
27+
val kclass = type.classifier as? KClass<*>
28+
return if (annotatedAsID) {
29+
if (validIdTypes.contains(kclass)) {
30+
Scalars.GraphQLID
31+
} else {
32+
val types = validIdTypes.joinToString(prefix = "[", postfix = "]", separator = ", ") { it.qualifiedName ?: "" }
33+
throw IllegalArgumentException("${kclass?.simpleName} is not a valid ID type, only $types are accepted")
34+
}
35+
} else {
36+
defaultScalarsMap[kclass]
37+
}
2738
}

src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorTest.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,26 @@ package com.expedia.graphql.schema.generator
22

33
import com.expedia.graphql.TopLevelObjectDef
44
import com.expedia.graphql.annotations.GraphQLDescription
5+
import com.expedia.graphql.annotations.GraphQLID
56
import com.expedia.graphql.annotations.GraphQLIgnore
67
import com.expedia.graphql.schema.exceptions.ConflictingTypesException
78
import com.expedia.graphql.schema.exceptions.InvalidSchemaException
89
import com.expedia.graphql.schema.extensions.deepName
910
import com.expedia.graphql.schema.testSchemaConfig
1011
import com.expedia.graphql.toSchema
1112
import graphql.GraphQL
13+
import graphql.schema.GraphQLNonNull
1214
import graphql.schema.GraphQLObjectType
1315
import org.junit.jupiter.api.Assertions.assertNull
1416
import org.junit.jupiter.api.Assertions.assertThrows
1517
import java.net.CookieManager
18+
import java.util.UUID
1619
import kotlin.test.Test
1720
import kotlin.test.assertEquals
1821
import kotlin.test.assertNotNull
1922
import kotlin.test.assertTrue
2023

21-
@Suppress("Detekt.UnusedPrivateMember", "Detekt.FunctionOnlyReturningConstant")
24+
@Suppress("Detekt.UnusedPrivateMember", "Detekt.FunctionOnlyReturningConstant", "Detekt.LargeClass")
2225
class SchemaGeneratorTest {
2326
@Test
2427
fun `SchemaGenerator generates a simple GraphQL schema`() {
@@ -228,6 +231,26 @@ class SchemaGeneratorTest {
228231
assertNull(firstChild?.get("children"))
229232
}
230233

234+
@Test
235+
fun `SchemaGenerator support GraphQLID scalar`() {
236+
val schema = toSchema(queries = listOf(TopLevelObjectDef(QueryWithId())), config = testSchemaConfig)
237+
238+
val placeType = schema.getObjectType("PlaceOfIds")
239+
assertEquals(graphql.Scalars.GraphQLID, (placeType.getFieldDefinition("intId").type as? GraphQLNonNull)?.wrappedType)
240+
assertEquals(graphql.Scalars.GraphQLID, (placeType.getFieldDefinition("longId").type as? GraphQLNonNull)?.wrappedType)
241+
assertEquals(graphql.Scalars.GraphQLID, (placeType.getFieldDefinition("stringId").type as? GraphQLNonNull)?.wrappedType)
242+
assertEquals(graphql.Scalars.GraphQLID, (placeType.getFieldDefinition("uuid").type as? GraphQLNonNull)?.wrappedType)
243+
}
244+
245+
@Test
246+
fun `SchemaGenerator throws an exception for invalid GraphQLID`() {
247+
val exception = assertThrows(IllegalArgumentException::class.java) {
248+
toSchema(queries = listOf(TopLevelObjectDef(QueryWithInvalidId())), config = testSchemaConfig)
249+
}
250+
251+
assertEquals("Person is not a valid ID type, only [kotlin.Int, kotlin.String, kotlin.Long, java.util.UUID] are accepted", exception.message)
252+
}
253+
231254
class QueryObject {
232255
@GraphQLDescription("A GraphQL query method")
233256
fun query(@GraphQLDescription("A GraphQL value") value: Int): Geography = Geography(value, GeoType.CITY, listOf())
@@ -355,4 +378,21 @@ class SchemaGeneratorTest {
355378
}
356379

357380
data class Person(val name: String, val children: List<Person>? = null)
381+
382+
data class PlaceOfIds(
383+
@property:GraphQLID val intId: Int,
384+
@property:GraphQLID val longId: Long,
385+
@property:GraphQLID val stringId: String,
386+
@property:GraphQLID val uuid: UUID
387+
)
388+
389+
data class InvalidIds(@property:GraphQLID val person: Person)
390+
391+
class QueryWithId {
392+
fun query(): PlaceOfIds = PlaceOfIds(42, 24, "42", UUID.randomUUID())
393+
}
394+
395+
class QueryWithInvalidId {
396+
fun query(): InvalidIds = InvalidIds(Person("person id not a valid type id"))
397+
}
358398
}

0 commit comments

Comments
 (0)