Skip to content

Commit 5a1b189

Browse files
smyrickbrennantaylor
authored andcommitted
feat: allow directive with Enums (#116)
* feat: allow directive with KClass * test: update test names * fix: simplify imports
1 parent fd6227b commit 5a1b189

File tree

16 files changed

+210
-84
lines changed

16 files changed

+210
-84
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.expedia.graphql.SchemaGeneratorConfig
44
import com.expedia.graphql.TopLevelObjectDef
55
import com.expedia.graphql.generator.extensions.getValidFunctions
66
import com.expedia.graphql.generator.state.SchemaGeneratorState
7+
import com.expedia.graphql.generator.types.DirectiveTypeBuilder
78
import com.expedia.graphql.generator.types.EnumTypeBuilder
89
import com.expedia.graphql.generator.types.FunctionTypeBuilder
910
import com.expedia.graphql.generator.types.InputObjectTypeBuilder
@@ -16,6 +17,7 @@ import com.expedia.graphql.generator.types.UnionTypeBuilder
1617
import graphql.schema.GraphQLInterfaceType
1718
import graphql.schema.GraphQLObjectType
1819
import graphql.schema.GraphQLSchema
20+
import kotlin.reflect.KAnnotatedElement
1921
import kotlin.reflect.KClass
2022
import kotlin.reflect.KFunction
2123
import kotlin.reflect.KProperty
@@ -40,6 +42,7 @@ internal class SchemaGenerator(
4042
private val functionTypeBuilder = FunctionTypeBuilder(this)
4143
private val enumTypeBuilder = EnumTypeBuilder(this)
4244
private val scalarTypeBuilder = ScalarTypeBuilder(this)
45+
private val directiveTypeBuilder = DirectiveTypeBuilder(this)
4346

4447
internal fun generate(): GraphQLSchema {
4548
val builder = GraphQLSchema.newSchema()
@@ -121,4 +124,7 @@ internal class SchemaGenerator(
121124

122125
internal fun scalarType(type: KType, annotatedAsID: Boolean = false) =
123126
scalarTypeBuilder.scalarType(type, annotatedAsID)
127+
128+
internal fun directives(element: KAnnotatedElement) =
129+
directiveTypeBuilder.directives(element)
124130
}

src/main/kotlin/com/expedia/graphql/generator/TypeBuilder.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ internal open class TypeBuilder constructor(val generator: SchemaGenerator) {
2020

2121
internal fun graphQLTypeOf(type: KType, inputType: Boolean = false, annotatedAsID: Boolean = false): GraphQLType {
2222
val hookGraphQLType = config.hooks.willGenerateGraphQLType(type)
23-
val graphQLType = hookGraphQLType ?: generator.scalarType(type, annotatedAsID)
24-
?: objectFromReflection(type, inputType)
23+
val graphQLType = hookGraphQLType
24+
?: generator.scalarType(type, annotatedAsID)
25+
?: objectFromReflection(type, inputType)
2526
val typeWithNullityTakenIntoAccount = graphQLType.wrapInNonNull(type)
2627
config.hooks.didGenerateGraphQLType(type, typeWithNullityTakenIntoAccount)
2728
return typeWithNullityTakenIntoAccount

src/main/kotlin/com/expedia/graphql/generator/extensions/directiveExtensions.kt

Lines changed: 0 additions & 56 deletions
This file was deleted.

src/main/kotlin/com/expedia/graphql/generator/schemaFilters.kt

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,27 @@ package com.expedia.graphql.generator
22

33
import com.expedia.graphql.generator.extensions.isGraphQLIgnored
44
import com.expedia.graphql.generator.extensions.isPropertyGraphQLIgnored
5-
import kotlin.reflect.KAnnotatedElement
5+
import com.expedia.graphql.generator.extensions.qualifiedName
66
import kotlin.reflect.KCallable
77
import kotlin.reflect.KClass
8-
import kotlin.reflect.KFunction
98
import kotlin.reflect.KProperty
109
import kotlin.reflect.KVisibility
1110

1211
private typealias CallableFilter = (KCallable<*>) -> Boolean
13-
private typealias AnnotatedElementFilter = (KAnnotatedElement) -> Boolean
1412
private typealias PropertyFilter = (KProperty<*>, KClass<*>) -> Boolean
15-
private typealias FunctionFilter = (KFunction<*>) -> Boolean
1613

17-
private val blackListFunctions: List<String> = listOf("annotationType", "toString", "copy", "equals", "hashCode")
14+
private val blacklistFunctions: List<String> = listOf("annotationType", "toString", "copy", "equals", "hashCode")
15+
private val blacklistTypes: List<String> = listOf("kotlin.reflect.KClass")
1816
private val componentFunctionRegex = Regex("component([0-9]+)")
1917

2018
private val isPublic: CallableFilter = { it.visibility == KVisibility.PUBLIC }
2119
private val isPropertyPublic: PropertyFilter = { prop, _ -> isPublic(prop) }
22-
private val isNotGraphQLIgnored: AnnotatedElementFilter = { it.isGraphQLIgnored().not() }
20+
private val isNotGraphQLIgnored: CallableFilter = { it.isGraphQLIgnored().not() }
2321
private val isPropertyNotGraphQLIgnored: PropertyFilter = { prop, parentClass -> prop.isPropertyGraphQLIgnored(parentClass).not() }
24-
private val isBlacklistedFunction: FunctionFilter = { blackListFunctions.contains(it.name) }
25-
private val isComponentFunction: FunctionFilter = { it.name.matches(componentFunctionRegex) }
26-
private val isNotBlackListed: FunctionFilter = { (isBlacklistedFunction(it) || isComponentFunction(it)).not() }
22+
private val isNotBlacklistedType: PropertyFilter = { prop, _ -> blacklistTypes.contains(prop.returnType.qualifiedName).not() }
23+
private val isBlacklistedFunction: CallableFilter = { blacklistFunctions.contains(it.name) }
24+
private val isComponentFunction: CallableFilter = { it.name.matches(componentFunctionRegex) }
25+
private val isNotBlacklistedFunction: CallableFilter = { isBlacklistedFunction(it).not() && isComponentFunction(it).not() }
2726

28-
internal val propertyFilters: List<PropertyFilter> = listOf(isPropertyPublic, isPropertyNotGraphQLIgnored)
29-
internal val functionFilters: List<FunctionFilter> = listOf(isPublic, isNotGraphQLIgnored, isNotBlackListed)
27+
internal val propertyFilters: List<PropertyFilter> = listOf(isPropertyPublic, isNotBlacklistedType, isPropertyNotGraphQLIgnored)
28+
internal val functionFilters: List<CallableFilter> = listOf(isPublic, isNotGraphQLIgnored, isNotBlacklistedFunction)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.expedia.graphql.generator.types
2+
3+
import com.expedia.graphql.generator.SchemaGenerator
4+
import com.expedia.graphql.generator.TypeBuilder
5+
import com.expedia.graphql.generator.extensions.getSimpleName
6+
import com.expedia.graphql.generator.extensions.getValidProperties
7+
import com.google.common.base.CaseFormat
8+
import graphql.schema.GraphQLArgument
9+
import graphql.schema.GraphQLDirective
10+
import graphql.schema.GraphQLInputType
11+
import kotlin.reflect.KAnnotatedElement
12+
import com.expedia.graphql.annotations.GraphQLDirective as GraphQLDirectiveAnnotation
13+
14+
internal class DirectiveTypeBuilder(generator: SchemaGenerator) : TypeBuilder(generator) {
15+
16+
internal fun directives(element: KAnnotatedElement): List<GraphQLDirective> =
17+
element.annotations.asSequence()
18+
.mapNotNull { it.getDirectiveInfo() }
19+
.map(this::getDirective)
20+
.toList()
21+
22+
private fun getDirective(directiveInfo: DirectiveInfo): GraphQLDirective {
23+
24+
val directiveClass = directiveInfo.directive.annotationClass
25+
26+
val builder = GraphQLDirective.newDirective()
27+
.name(directiveInfo.effectiveName)
28+
.description(directiveInfo.directiveAnnotation.description)
29+
30+
directiveInfo.directiveAnnotation.locations.forEach {
31+
builder.validLocation(it)
32+
}
33+
34+
directiveClass.getValidProperties(config.hooks).forEach { prop ->
35+
val propertyName = prop.name
36+
val value = prop.call(directiveInfo.directive)
37+
val type = graphQLTypeOf(prop.returnType)
38+
39+
val argument = GraphQLArgument.newArgument()
40+
.name(propertyName)
41+
.value(value)
42+
.type(type as? GraphQLInputType)
43+
.build()
44+
45+
builder.argument(argument)
46+
}
47+
48+
return builder.build()
49+
}
50+
}
51+
52+
private fun String.normalizeDirectiveName() = CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, this)
53+
54+
private fun Annotation.getDirectiveInfo(): DirectiveInfo? = this.annotationClass.annotations
55+
.filterIsInstance(GraphQLDirectiveAnnotation::class.java)
56+
.map { DirectiveInfo(this, it) }
57+
.firstOrNull()
58+
59+
private data class DirectiveInfo(val directive: Annotation, val directiveAnnotation: GraphQLDirectiveAnnotation) {
60+
val effectiveName: String = when {
61+
directiveAnnotation.name.isNotEmpty() -> directiveAnnotation.name
62+
else -> directive.annotationClass.getSimpleName().normalizeDirectiveName()
63+
}
64+
}

src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.expedia.graphql.KotlinDataFetcher
44
import com.expedia.graphql.Parameter
55
import com.expedia.graphql.generator.SchemaGenerator
66
import com.expedia.graphql.generator.TypeBuilder
7-
import com.expedia.graphql.generator.extensions.directives
87
import com.expedia.graphql.generator.extensions.getDeprecationReason
98
import com.expedia.graphql.generator.extensions.getGraphQLDescription
109
import com.expedia.graphql.generator.extensions.getName
@@ -32,7 +31,7 @@ internal class FunctionTypeBuilder(generator: SchemaGenerator) : TypeBuilder(gen
3231
builder.deprecate(it)
3332
}
3433

35-
fn.directives(generator).forEach {
34+
generator.directives(fn).forEach {
3635
builder.withDirective(it)
3736
state.directives.add(it)
3837
}
@@ -68,7 +67,7 @@ internal class FunctionTypeBuilder(generator: SchemaGenerator) : TypeBuilder(gen
6867
.description(parameter.getGraphQLDescription() ?: parameter.type.getGraphQLDescription())
6968
.type(graphQLTypeOf(parameter.type, true) as GraphQLInputType)
7069

71-
parameter.directives(generator).forEach {
70+
generator.directives(parameter).forEach {
7271
builder.withDirective(it)
7372
state.directives.add(it)
7473
}

src/main/kotlin/com/expedia/graphql/generator/types/ObjectTypeBuilder.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package com.expedia.graphql.generator.types
22

33
import com.expedia.graphql.generator.SchemaGenerator
44
import com.expedia.graphql.generator.TypeBuilder
5-
import com.expedia.graphql.generator.extensions.directives
65
import com.expedia.graphql.generator.extensions.getGraphQLDescription
76
import com.expedia.graphql.generator.extensions.getSimpleName
87
import com.expedia.graphql.generator.extensions.getValidFunctions
@@ -25,7 +24,7 @@ internal class ObjectTypeBuilder(generator: SchemaGenerator) : TypeBuilder(gener
2524
builder.name(kClass.getSimpleName())
2625
builder.description(kClass.getGraphQLDescription())
2726

28-
kClass.directives(generator).forEach {
27+
generator.directives(kClass).forEach {
2928
builder.withDirective(it)
3029
state.directives.add(it)
3130
}

src/main/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilder.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package com.expedia.graphql.generator.types
22

3-
import com.expedia.graphql.generator.extensions.directives
3+
import com.expedia.graphql.generator.SchemaGenerator
4+
import com.expedia.graphql.generator.TypeBuilder
45
import com.expedia.graphql.generator.extensions.getPropertyDeprecationReason
56
import com.expedia.graphql.generator.extensions.getPropertyDescription
67
import com.expedia.graphql.generator.extensions.isPropertyGraphQLID
7-
import com.expedia.graphql.generator.SchemaGenerator
8-
import com.expedia.graphql.generator.TypeBuilder
98
import graphql.schema.DataFetcherFactory
109
import graphql.schema.GraphQLFieldDefinition
1110
import graphql.schema.GraphQLNonNull
@@ -24,7 +23,7 @@ internal class PropertyTypeBuilder(generator: SchemaGenerator) : TypeBuilder(gen
2423
.type(propertyType)
2524
.deprecate(prop.getPropertyDeprecationReason(parentClass))
2625

27-
prop.directives(generator).forEach {
26+
generator.directives(prop).forEach {
2827
fieldBuilder.withDirective(it)
2928
state.directives.add(it)
3029
}

src/test/kotlin/com/expedia/graphql/generator/DirectiveTests.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class DirectiveTests {
6464

6565
assertEquals("arenaming", directive.arguments[0].value)
6666
assertEquals("arg", directive.arguments[0].name)
67-
assertEquals(Scalars.GraphQLString, directive.arguments[0].type)
67+
assertEquals(GraphQLNonNull(Scalars.GraphQLString), directive.arguments[0].type)
6868
}
6969
}
7070

src/test/kotlin/com/expedia/graphql/generator/SchemaFiltersTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ internal class SchemaFiltersTest {
2626

2727
internal val nonPublicProperty: Int = 0
2828

29+
val kClass: KClass<*> = MyDataClass::class
30+
2931
@GraphQLIgnore
3032
internal val ignoredProperty: Int = 0
3133

@@ -56,6 +58,7 @@ internal class SchemaFiltersTest {
5658
assertTrue(testProperty(MyClass::publicProperty, MyClass::class))
5759
assertFalse(testProperty(MyClass::nonPublicProperty, MyClass::class))
5860
assertFalse(testProperty(MyClass::ignoredProperty, MyClass::class))
61+
assertFalse(testProperty(MyClass::kClass, MyDataClass::class))
5962
assertTrue(testProperty(MyDataClass::id, MyDataClass::class))
6063
assertFalse(testProperty(MyDataClass::ignoredProperty, MyDataClass::class))
6164
}

0 commit comments

Comments
 (0)