Skip to content

Commit 7627dd6

Browse files
author
Dariusz Kuc
authored
[generator] filter lambda properties (ExpediaGroup#1366)
Currently we don't support lambda parameters. Attempting to use a lambda property blows up with somewhat ambiguous messages (lambda return type is outside of supported packages/cannot calculate erasure type for suspendable lambda). Currently it was also not possible to filter out lambda properties using `@GraphQLIgnore` annotations as it was applied AFTER attempting to read lambda erasure type (which blows up as it cannot be calculated). This PR adds new property filter that checks whether property is a lambda and throws `InvalidPropertyReturnTypeException` if it is. Also moved `@GraphQLIgnore` filter to be applied before we attempt to calculate JVM erasure (used to verify property type is not blacklisted). Throwing exception from a filter might not be ideal but IMHO is a better alternative than throwing current ambiguous exception (about JVM erasure) or by just filtering lambda parameters (which leads to questions of why my public property is not available in the graph).
1 parent de02352 commit 7627dd6

File tree

4 files changed

+96
-7
lines changed

4 files changed

+96
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2022 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.generator.exceptions
18+
19+
import kotlin.reflect.KClass
20+
import kotlin.reflect.KProperty
21+
22+
/**
23+
* Thrown when the schema defines an object property which is a lambda.
24+
*/
25+
class InvalidPropertyReturnTypeException(kClass: KClass<*>, property: KProperty<*>) :
26+
GraphQLKotlinException("The class $kClass defines $property as a lambda which is currently unsupported")

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/internal/filters/propertyFilters.kt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,15 +16,18 @@
1616

1717
package com.expediagroup.graphql.generator.internal.filters
1818

19+
import com.expediagroup.graphql.generator.exceptions.InvalidPropertyReturnTypeException
1920
import com.expediagroup.graphql.generator.internal.extensions.isGraphQLIgnored
2021
import com.expediagroup.graphql.generator.internal.extensions.isPropertyGraphQLIgnored
2122
import com.expediagroup.graphql.generator.internal.extensions.isPublic
2223
import com.expediagroup.graphql.generator.internal.extensions.qualifiedName
2324
import com.expediagroup.graphql.generator.internal.types.utils.validGraphQLNameRegex
2425
import kotlin.reflect.KClass
2526
import kotlin.reflect.KProperty
27+
import kotlin.reflect.full.isSubtypeOf
2628
import kotlin.reflect.full.memberProperties
2729
import kotlin.reflect.jvm.jvmErasure
30+
import kotlin.reflect.typeOf
2831

2932
private typealias PropertyFilter = (KProperty<*>, KClass<*>) -> Boolean
3033

@@ -34,13 +37,21 @@ private val isPropertyPublic: PropertyFilter = { prop, _ -> prop.isPublic() }
3437
private val isPropertyNotGraphQLIgnored: PropertyFilter = { prop, parentClass -> prop.isPropertyGraphQLIgnored(parentClass).not() }
3538
private val isNotBlacklistedType: PropertyFilter = { prop, _ -> blacklistTypes.contains(prop.returnType.qualifiedName).not() }
3639
private val isValidPropertyName: PropertyFilter = { prop, _ -> prop.name.matches(validGraphQLNameRegex) }
40+
@OptIn(ExperimentalStdlibApi::class)
41+
private val isNotLambda: PropertyFilter = { prop, parentClass ->
42+
if (prop.returnType.isSubtypeOf(typeOf<Function<*>>())) {
43+
throw InvalidPropertyReturnTypeException(parentClass, prop)
44+
} else {
45+
true
46+
}
47+
}
3748

3849
private val isNotIgnoredFromSuperClass: PropertyFilter = { prop, parentClass ->
3950
val superPropsIgnored = parentClass.supertypes
4051
.flatMap { superType ->
4152
superType.jvmErasure.memberProperties
42-
.filter { superProp -> basicPropertyFilters.all { it.invoke(superProp, superType::class) } }
4353
.filter { it.isGraphQLIgnored() }
54+
.filter { superProp -> basicPropertyFilters.all { it.invoke(superProp, superType::class) } }
4455
}
4556

4657
superPropsIgnored.none { superProp ->
@@ -49,5 +60,5 @@ private val isNotIgnoredFromSuperClass: PropertyFilter = { prop, parentClass ->
4960
}
5061
}
5162

52-
private val basicPropertyFilters = listOf(isPropertyPublic, isNotBlacklistedType)
53-
internal val propertyFilters: List<PropertyFilter> = basicPropertyFilters + isPropertyNotGraphQLIgnored + isNotIgnoredFromSuperClass + isValidPropertyName
63+
private val basicPropertyFilters = listOf(isPropertyPublic, isNotLambda, isNotBlacklistedType)
64+
internal val propertyFilters: List<PropertyFilter> = listOf(isPropertyNotGraphQLIgnored) + basicPropertyFilters + isNotIgnoredFromSuperClass + isValidPropertyName

generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/internal/filters/PropertyFiltersTest.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,7 +17,9 @@
1717
package com.expediagroup.graphql.generator.internal.filters
1818

1919
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
20+
import com.expediagroup.graphql.generator.exceptions.InvalidPropertyReturnTypeException
2021
import org.junit.jupiter.api.Test
22+
import org.junit.jupiter.api.assertThrows
2123
import kotlin.reflect.KClass
2224
import kotlin.reflect.KProperty
2325
import kotlin.test.assertFalse
@@ -35,6 +37,13 @@ internal class PropertyFiltersTest {
3537
assertFalse(isValidProperty(MyDataClass::ignoredProperty, MyDataClass::class))
3638
assertFalse(isValidProperty(MyClass::foo, MyClass::class))
3739
assertFalse(isValidProperty(MyClass::`$invalidPropertyName`, MyClass::class))
40+
41+
assertThrows<InvalidPropertyReturnTypeException> {
42+
isValidProperty(MyClass::lambda, MyClass::class)
43+
}
44+
assertThrows<InvalidPropertyReturnTypeException> {
45+
isValidProperty(MyClass::suspendableLambda, MyClass::class)
46+
}
3847
}
3948

4049
internal data class MyDataClass(
@@ -49,7 +58,10 @@ internal class PropertyFiltersTest {
4958
val foo: Int
5059
}
5160

52-
internal class MyClass : MyInterface {
61+
internal class MyClass(
62+
val lambda: () -> String,
63+
val suspendableLambda: suspend () -> String
64+
) : MyInterface {
5365

5466
val publicProperty: Int = 0
5567

generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/internal/types/GenerateObjectTest.kt

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2021 Expedia, Inc
2+
* Copyright 2022 Expedia, Inc
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,16 +18,19 @@ package com.expediagroup.graphql.generator.internal.types
1818

1919
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
2020
import com.expediagroup.graphql.generator.annotations.GraphQLDirective
21+
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
2122
import com.expediagroup.graphql.generator.annotations.GraphQLName
2223
import com.expediagroup.graphql.generator.annotations.GraphQLValidObjectLocations
2324
import com.expediagroup.graphql.generator.exceptions.InvalidGraphQLNameException
2425
import com.expediagroup.graphql.generator.exceptions.InvalidObjectLocationException
26+
import com.expediagroup.graphql.generator.exceptions.InvalidPropertyReturnTypeException
2527
import graphql.Scalars
2628
import graphql.introspection.Introspection
2729
import graphql.schema.GraphQLNonNull
2830
import graphql.schema.GraphQLObjectType
2931
import org.junit.jupiter.api.Test
3032
import org.junit.jupiter.api.assertDoesNotThrow
33+
import org.junit.jupiter.api.assertThrows
3134
import kotlin.test.assertEquals
3235
import kotlin.test.assertFailsWith
3336
import kotlin.test.assertNotNull
@@ -66,6 +69,20 @@ class GenerateObjectTest : TypeTestHelper() {
6669
@GraphQLName("Invalid\$Name")
6770
class InvalidOutputTypeNameOverride
6871

72+
data class FooLambda(
73+
val foo: () -> String
74+
)
75+
76+
data class SuspendableFooLambda(
77+
val foo: suspend () -> String?
78+
)
79+
80+
data class Foo(
81+
val foo: String,
82+
@GraphQLIgnore val fooLambda: () -> String?,
83+
private val fooSuspendable: suspend () -> String?
84+
)
85+
6986
@Test
7087
fun `Test naming`() {
7188
val result = generateObject(generator, BeHappy::class) as? GraphQLObjectType
@@ -139,4 +156,27 @@ class GenerateObjectTest : TypeTestHelper() {
139156
generateInputObject(generator, InvalidOutputTypeNameOverride::class)
140157
}
141158
}
159+
160+
@Test
161+
fun `Generation of output object will fail if one of the properties is lambda`() {
162+
assertThrows<InvalidPropertyReturnTypeException> {
163+
generateObject(generator, FooLambda::class)
164+
}
165+
}
166+
167+
@Test
168+
fun `Generation of output object will fail if one of the properties is suspendable lambda`() {
169+
assertThrows<InvalidPropertyReturnTypeException> {
170+
generateObject(generator, SuspendableFooLambda::class)
171+
}
172+
}
173+
174+
@Test
175+
fun `Generation of output objects ignores private or @GraphQLIgnored lambdas `() {
176+
val result = generateObject(generator, Foo::class)
177+
assertNotNull(result)
178+
assertEquals("Foo", result.name)
179+
assertEquals(1, result.fieldDefinitions.size)
180+
assertEquals("foo", result.fieldDefinitions[0].name)
181+
}
142182
}

0 commit comments

Comments
 (0)