Skip to content

Commit 148c983

Browse files
authored
Fix bugs with generating arguments (#319)
Fixes #249 Fixes #318 Resolve the two bugs and refactor the function to its own ArgumentBuilder so we can more easily test the bugs
1 parent a13e883 commit 148c983

File tree

10 files changed

+165
-75
lines changed

10 files changed

+165
-75
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.expedia.graphql.generator
33
import com.expedia.graphql.SchemaGeneratorConfig
44
import com.expedia.graphql.TopLevelObject
55
import com.expedia.graphql.generator.state.SchemaGeneratorState
6+
import com.expedia.graphql.generator.types.ArgumentBuilder
67
import com.expedia.graphql.generator.types.DirectiveBuilder
78
import com.expedia.graphql.generator.types.EnumBuilder
89
import com.expedia.graphql.generator.types.FunctionBuilder
@@ -16,13 +17,15 @@ import com.expedia.graphql.generator.types.QueryBuilder
1617
import com.expedia.graphql.generator.types.ScalarBuilder
1718
import com.expedia.graphql.generator.types.SubscriptionBuilder
1819
import com.expedia.graphql.generator.types.UnionBuilder
20+
import graphql.schema.GraphQLArgument
1921
import graphql.schema.GraphQLCodeRegistry
2022
import graphql.schema.GraphQLDirective
2123
import graphql.schema.GraphQLSchema
2224
import java.lang.reflect.Field
2325
import kotlin.reflect.KAnnotatedElement
2426
import kotlin.reflect.KClass
2527
import kotlin.reflect.KFunction
28+
import kotlin.reflect.KParameter
2629
import kotlin.reflect.KProperty
2730
import kotlin.reflect.KType
2831

@@ -45,6 +48,7 @@ open class SchemaGenerator(val config: SchemaGeneratorConfig) {
4548
private val enumTypeBuilder = EnumBuilder(this)
4649
private val scalarTypeBuilder = ScalarBuilder(this)
4750
private val directiveTypeBuilder = DirectiveBuilder(this)
51+
private val argumentBuilder = ArgumentBuilder(this)
4852

4953
open fun generate(
5054
queries: List<TopLevelObject>,
@@ -98,4 +102,7 @@ open class SchemaGenerator(val config: SchemaGeneratorConfig) {
98102

99103
open fun fieldDirectives(field: Field): List<GraphQLDirective> =
100104
directiveTypeBuilder.fieldDirectives(field)
105+
106+
open fun argument(parameter: KParameter): GraphQLArgument =
107+
argumentBuilder.argument(parameter)
101108
}

graphql-kotlin-schema-generator/src/main/kotlin/com/expedia/graphql/generator/extensions/kParameterExtensions.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal fun KParameter.isDataFetchingEnvironment() = this.type.classifier == Da
1616

1717
@Throws(CouldNotGetNameOfKParameterException::class)
1818
internal fun KParameter.getName(): String =
19-
this.name ?: throw CouldNotGetNameOfKParameterException(this)
19+
this.getGraphQLName() ?: this.name ?: throw CouldNotGetNameOfKParameterException(this)
2020

2121
@Throws(CouldNotCastArgumentException::class)
2222
internal fun KParameter.javaTypeClass(): Class<*> =

graphql-kotlin-schema-generator/src/main/kotlin/com/expedia/graphql/generator/extensions/kPropertyExtensions.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ package com.expedia.graphql.generator.extensions
33
import kotlin.reflect.KClass
44
import kotlin.reflect.KProperty
55

6+
/**
7+
* KProperty Extensions:
8+
* For properties we need to check both the property for annotations and the constructor argument
9+
*/
10+
611
internal fun KProperty<*>.isPropertyGraphQLID(parentClass: KClass<*>): Boolean = when {
712
this.isGraphQLID() -> true
813
getConstructorParameter(parentClass)?.isGraphQLID().isTrue() -> true
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.expedia.graphql.generator.types
2+
3+
import com.expedia.graphql.exceptions.InvalidInputFieldTypeException
4+
import com.expedia.graphql.generator.SchemaGenerator
5+
import com.expedia.graphql.generator.TypeBuilder
6+
import com.expedia.graphql.generator.extensions.getGraphQLDescription
7+
import com.expedia.graphql.generator.extensions.getName
8+
import com.expedia.graphql.generator.extensions.isGraphQLID
9+
import com.expedia.graphql.generator.extensions.isInterface
10+
import com.expedia.graphql.generator.extensions.safeCast
11+
import graphql.schema.GraphQLArgument
12+
import kotlin.reflect.KParameter
13+
14+
internal class ArgumentBuilder(generator: SchemaGenerator) : TypeBuilder(generator) {
15+
16+
@Throws(InvalidInputFieldTypeException::class)
17+
internal fun argument(parameter: KParameter): GraphQLArgument {
18+
19+
if (parameter.isInterface()) {
20+
throw InvalidInputFieldTypeException(parameter)
21+
}
22+
23+
val graphQLType = graphQLTypeOf(parameter.type, inputType = true, annotatedAsID = parameter.isGraphQLID())
24+
25+
// Deprecation of arguments is currently unsupported: https://github.com/facebook/graphql/issues/197
26+
val builder = GraphQLArgument.newArgument()
27+
.name(parameter.getName())
28+
.description(parameter.getGraphQLDescription())
29+
.type(graphQLType.safeCast())
30+
31+
generator.directives(parameter).forEach {
32+
builder.withDirective(it)
33+
}
34+
35+
return config.hooks.onRewireGraphQLType(builder.build()).safeCast()
36+
}
37+
}
Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,21 @@
11
package com.expedia.graphql.generator.types
22

33
import com.expedia.graphql.directives.deprecatedDirectiveWithReason
4-
import com.expedia.graphql.exceptions.InvalidInputFieldTypeException
54
import com.expedia.graphql.generator.SchemaGenerator
65
import com.expedia.graphql.generator.TypeBuilder
76
import com.expedia.graphql.generator.extensions.getDeprecationReason
87
import com.expedia.graphql.generator.extensions.getFunctionName
98
import com.expedia.graphql.generator.extensions.getGraphQLDescription
109
import com.expedia.graphql.generator.extensions.getKClass
11-
import com.expedia.graphql.generator.extensions.getName
1210
import com.expedia.graphql.generator.extensions.getTypeOfFirstArgument
1311
import com.expedia.graphql.generator.extensions.getValidArguments
14-
import com.expedia.graphql.generator.extensions.isInterface
1512
import com.expedia.graphql.generator.extensions.safeCast
1613
import graphql.schema.FieldCoordinates
17-
import graphql.schema.GraphQLArgument
1814
import graphql.schema.GraphQLFieldDefinition
1915
import graphql.schema.GraphQLOutputType
2016
import org.reactivestreams.Publisher
2117
import java.util.concurrent.CompletableFuture
2218
import kotlin.reflect.KFunction
23-
import kotlin.reflect.KParameter
2419
import kotlin.reflect.KType
2520
import kotlin.reflect.full.isSubclassOf
2621

@@ -41,11 +36,9 @@ internal class FunctionBuilder(generator: SchemaGenerator) : TypeBuilder(generat
4136
builder.withDirective(it)
4237
}
4338

44-
fn.getValidArguments()
45-
.forEach {
46-
// deprecation of arguments is currently unsupported: https://github.com/facebook/graphql/issues/197
47-
builder.argument(argument(it))
48-
}
39+
fn.getValidArguments().forEach {
40+
builder.argument(generator.argument(it))
41+
}
4942

5043
val typeFromHooks = config.hooks.willResolveMonad(fn.returnType)
5144
val returnType = getWrappedReturnType(typeFromHooks)
@@ -67,23 +60,4 @@ internal class FunctionBuilder(generator: SchemaGenerator) : TypeBuilder(generat
6760
returnType.classifier == CompletableFuture::class -> returnType.getTypeOfFirstArgument()
6861
else -> returnType
6962
}
70-
71-
@Throws(InvalidInputFieldTypeException::class)
72-
private fun argument(parameter: KParameter): GraphQLArgument {
73-
74-
if (parameter.isInterface()) {
75-
throw InvalidInputFieldTypeException(parameter)
76-
}
77-
78-
val builder = GraphQLArgument.newArgument()
79-
.name(parameter.getName())
80-
.description(parameter.getGraphQLDescription())
81-
.type(graphQLTypeOf(parameter.type, true).safeCast())
82-
83-
generator.directives(parameter).forEach {
84-
builder.withDirective(it)
85-
}
86-
87-
return config.hooks.onRewireGraphQLType(builder.build()).safeCast()
88-
}
8963
}

graphql-kotlin-schema-generator/src/test/kotlin/com/expedia/graphql/extensions/GraphQLSchemaExtensionsTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ class GraphQLSchemaExtensionsTest {
8787
val sdl = schema.print(includeDefaultSchemaDefinition = false, includeDirectives = false).trim()
8888
val expected = """
8989
type Query {
90-
queryById(id: String!): TypeWithId!
90+
queryById(id: ID!): TypeWithId!
9191
}
9292
9393
type TypeWithId {

graphql-kotlin-schema-generator/src/test/kotlin/com/expedia/graphql/generator/extensions/KParameterExtensionsKtTest.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ internal class KParameterExtensionsKtTest {
5656
@Test
5757
fun getNameException() {
5858
val mockParam: KParameter = mockk()
59+
every { mockParam.annotations } returns emptyList()
5960
every { mockParam.name } returns null
6061
assertFailsWith(CouldNotGetNameOfKParameterException::class) {
6162
mockParam.getName()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.expedia.graphql.generator.types
2+
3+
import com.expedia.graphql.annotations.GraphQLDescription
4+
import com.expedia.graphql.annotations.GraphQLID
5+
import com.expedia.graphql.annotations.GraphQLName
6+
import com.expedia.graphql.exceptions.InvalidInputFieldTypeException
7+
import com.expedia.graphql.test.utils.SimpleDirective
8+
import graphql.schema.GraphQLNonNull
9+
import org.junit.jupiter.api.Test
10+
import kotlin.reflect.full.findParameterByName
11+
import kotlin.test.assertEquals
12+
import kotlin.test.assertFailsWith
13+
import kotlin.test.assertNotNull
14+
15+
internal class ArgumentBuilderTest : TypeTestHelper() {
16+
17+
private lateinit var builder: ArgumentBuilder
18+
19+
override fun beforeTest() {
20+
builder = ArgumentBuilder(generator)
21+
}
22+
23+
internal interface MyInterface {
24+
val id: String
25+
}
26+
27+
internal class ArgumentTestClass {
28+
fun description(@GraphQLDescription("Argument description") input: String) = input
29+
30+
fun directive(@SimpleDirective input: String) = input
31+
32+
fun changeName(@GraphQLName("newName") input: String) = input
33+
34+
fun id(@GraphQLID idArg: Int) = "Your id is $idArg"
35+
36+
fun interfaceArg(input: MyInterface) = input.id
37+
}
38+
39+
@Test
40+
fun `Description is set on arguments`() {
41+
val kParameter = ArgumentTestClass::description.findParameterByName("input")
42+
assertNotNull(kParameter)
43+
val result = builder.argument(kParameter)
44+
45+
assertEquals("String", (result.type as? GraphQLNonNull)?.wrappedType?.name)
46+
assertEquals("Argument description", result.description)
47+
}
48+
49+
@Test
50+
fun `Directives are included on arguments`() {
51+
val kParameter = ArgumentTestClass::directive.findParameterByName("input")
52+
assertNotNull(kParameter)
53+
val result = builder.argument(kParameter)
54+
55+
assertEquals("String", (result.type as? GraphQLNonNull)?.wrappedType?.name)
56+
assertEquals(1, result.directives.size)
57+
assertEquals("simpleDirective", result.directives.firstOrNull()?.name)
58+
}
59+
60+
@Test
61+
fun `Argument names can be changed with @GraphQLName`() {
62+
val kParameter = ArgumentTestClass::changeName.findParameterByName("input")
63+
assertNotNull(kParameter)
64+
val result = builder.argument(kParameter)
65+
66+
assertEquals("String", (result.type as? GraphQLNonNull)?.wrappedType?.name)
67+
assertEquals("newName", result.name)
68+
}
69+
70+
@Test
71+
fun `ID argument type is valid`() {
72+
val kParameter = ArgumentTestClass::id.findParameterByName("idArg")
73+
assertNotNull(kParameter)
74+
val result = builder.argument(kParameter)
75+
76+
assertEquals(expected = "idArg", actual = result.name)
77+
assertEquals("ID", (result.type as? GraphQLNonNull)?.wrappedType?.name)
78+
}
79+
80+
@Test
81+
fun `Interface argument type throws exception`() {
82+
val kParameter = ArgumentTestClass::interfaceArg.findParameterByName("input")
83+
assertNotNull(kParameter)
84+
85+
assertFailsWith(InvalidInputFieldTypeException::class) {
86+
builder.argument(kParameter)
87+
}
88+
}
89+
}

graphql-kotlin-schema-generator/src/test/kotlin/com/expedia/graphql/generator/types/FunctionBuilderTest.kt

Lines changed: 8 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -39,26 +39,18 @@ internal class FunctionBuilderTest : TypeTestHelper() {
3939
@GraphQLDirective(locations = [Introspection.DirectiveLocation.FIELD_DEFINITION])
4040
internal annotation class FunctionDirective(val arg: String)
4141

42-
@GraphQLDirective
43-
internal annotation class ArgumentDirective(val arg: String)
44-
4542
private class Happy {
4643

4744
@GraphQLDescription("By bob")
4845
@FunctionDirective("happy")
4946
fun littleTrees() = UUID.randomUUID().toString()
5047

51-
fun paint(@GraphQLDescription("brush color") @ArgumentDirective("red") color: String) = color.reversed()
52-
53-
@Deprecated("Should paint instead")
48+
@Deprecated("Should paint instead", ReplaceWith("paint"))
5449
fun sketch(tree: String) = tree
5550

5651
@GraphQLName("renamedFunction")
5752
fun originalName(input: String) = input
5853

59-
@Deprecated("No saw, just paint", replaceWith = ReplaceWith("paint"))
60-
fun saw(tree: String) = tree
61-
6254
fun context(@GraphQLContext context: String, string: String) = "$context and $string"
6355

6456
fun ignoredParameter(color: String, @GraphQLIgnore ignoreMe: String) = "$color and $ignoreMe"
@@ -73,46 +65,31 @@ internal class FunctionBuilderTest : TypeTestHelper() {
7365
}
7466

7567
@Test
76-
fun `test description`() {
68+
fun `Function description can be set`() {
7769
val kFunction = Happy::littleTrees
7870
val result = builder.function(kFunction, "Query")
7971
assertEquals("By bob", result.description)
8072
}
8173

8274
@Test
83-
fun `test description on argument`() {
84-
val kFunction = Happy::paint
85-
val result = builder.function(kFunction, "Query").arguments[0]
86-
assertEquals("brush color", result.description)
75+
fun `Function names can be changed`() {
76+
val kFunction = Happy::originalName
77+
val result = builder.function(kFunction, "Query")
78+
assertEquals("renamedFunction", result.name)
8779
}
8880

8981
@Test
90-
fun `test deprecation`() {
82+
fun `Functions can be deprecated`() {
9183
val kFunction = Happy::sketch
9284
val result = builder.function(kFunction, "Query")
9385
assertTrue(result.isDeprecated)
94-
assertEquals("Should paint instead", result.deprecationReason)
86+
assertEquals("Should paint instead, replace with paint", result.deprecationReason)
9587

9688
val fieldDirectives = result.directives
9789
assertEquals(1, fieldDirectives.size)
9890
assertEquals("deprecated", fieldDirectives.first().name)
9991
}
10092

101-
@Test
102-
fun `test changing the name of a function`() {
103-
val kFunction = Happy::originalName
104-
val result = builder.function(kFunction, "Query")
105-
assertEquals("renamedFunction", result.name)
106-
}
107-
108-
@Test
109-
fun `test deprecation with replacement`() {
110-
val kFunction = Happy::saw
111-
val result = builder.function(kFunction, "Query")
112-
assertTrue(result.isDeprecated)
113-
assertEquals("No saw, just paint, replace with paint", result.deprecationReason)
114-
}
115-
11693
@Test
11794
fun `test custom directive on function`() {
11895
val kFunction = Happy::littleTrees
@@ -130,19 +107,6 @@ internal class FunctionBuilderTest : TypeTestHelper() {
130107
)
131108
}
132109

133-
@Test
134-
fun `test custom directive on function argument`() {
135-
val kFunction = Happy::paint
136-
val result = builder.function(kFunction, "Query").arguments[0]
137-
138-
assertEquals(1, result.directives.size)
139-
val directive = result.directives[0]
140-
assertEquals("argumentDirective", directive.name)
141-
assertEquals("red", directive.arguments[0].value)
142-
assertEquals("arg", directive.arguments[0].name)
143-
assertEquals(GraphQLNonNull(Scalars.GraphQLString), directive.arguments[0].type)
144-
}
145-
146110
@Test
147111
fun `test context on argument`() {
148112
val kFunction = Happy::context

0 commit comments

Comments
 (0)