Skip to content

Commit edfe862

Browse files
committed
Finished Query Builders
Finished Converter System Changed to experimental API to get correct KTypes for handling nullable arrays and collections, this is non negociable, it just doesn't work without
1 parent 4b07532 commit edfe862

File tree

64 files changed

+1090
-285
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+1090
-285
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131

3232
compileKotlin {
3333
kotlinOptions.jvmTarget = "1.6"
34+
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.ExperimentalStdlibApi"]
3435
}
3536
compileTestKotlin {
3637
kotlinOptions.jvmTarget = "1.6"

src/main/kotlin/com/papsign/kotlin/reflection/Reflection.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import kotlin.reflect.*
55
import kotlin.reflect.full.createType
66
import kotlin.reflect.full.declaredMemberProperties
77
import kotlin.reflect.full.isSubclassOf
8-
import kotlin.reflect.full.withNullability
98
import kotlin.reflect.jvm.jvmErasure
109

1110
val unitKType = getKType<Unit>()
@@ -15,7 +14,8 @@ inline fun <reified T> isNullable(): Boolean {
1514
}
1615

1716
inline fun <reified T> getKType(): KType {
18-
return object : SuperTypeTokenHolder<T>() { }.getKTypeImpl().withNullability(isNullable<T>())
17+
return typeOf<T>()
18+
// return object : SuperTypeTokenHolder<T>() {}.getKTypeImpl().withNullability(isNullable<T>())
1919
}
2020

2121
// TODO: Lobby for a real kotlin ::type for types because the status quo sucks
@@ -24,7 +24,7 @@ inline fun <reified T> getKType(): KType {
2424
open class SuperTypeTokenHolder<T>
2525

2626
fun SuperTypeTokenHolder<*>.getKTypeImpl(): KType =
27-
javaClass.genericSuperclass.toKType().arguments.single().type!!
27+
javaClass.genericSuperclass.toKType().arguments.single().type!!
2828

2929
fun KClass<*>.toInvariantFlexibleProjection(arguments: List<KTypeProjection> = emptyList()): KTypeProjection {
3030
// TODO: there should be an API in kotlin-reflect which creates KType instances corresponding to flexible types
@@ -41,13 +41,15 @@ fun Type.toKTypeProjection(): KTypeProjection = when (this) {
4141
is Class<*> -> this.kotlin.toInvariantFlexibleProjection()
4242
is ParameterizedType -> {
4343
val erasure = (rawType as Class<*>).kotlin
44-
erasure.toInvariantFlexibleProjection((erasure.typeParameters.zip(actualTypeArguments).map { (parameter, argument) ->
45-
val projection = argument.toKTypeProjection()
46-
projection.takeIf {
47-
// Get rid of use-site projections on arguments, where the corresponding parameters already have a declaration-site projection
48-
parameter.variance == KVariance.INVARIANT || parameter.variance != projection.variance
49-
} ?: KTypeProjection.invariant(projection.type!!)
50-
}))
44+
erasure.toInvariantFlexibleProjection(
45+
(erasure.typeParameters.zip(actualTypeArguments).map { (parameter, argument) ->
46+
val projection = argument.toKTypeProjection()
47+
projection.takeIf {
48+
// Get rid of use-site projections on arguments, where the corresponding parameters already have a declaration-site projection
49+
parameter.variance == KVariance.INVARIANT || parameter.variance != projection.variance
50+
} ?: KTypeProjection.invariant(projection.type!!)
51+
})
52+
)
5153
}
5254
is WildcardType -> when {
5355
lowerBounds.isNotEmpty() -> KTypeProjection.contravariant(lowerBounds.single().toKType())
@@ -122,4 +124,4 @@ fun KType.getObjectSubtypes(): Set<KType> {
122124
// println(getKType<Array<Int?>?>())
123125
// println(getKType<Array<Array<String>>>())
124126
// println(getKType<Unit>())
125-
//}
127+
//}
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.papsign.ktor.openapigen.parameters
22

3-
import com.papsign.ktor.openapigen.parameters.parsers.generic.BuilderFactory
4-
53
interface ParameterStyle<S> where S: ParameterStyle<S>, S: Enum<S> {
64
val name: String
75
}

src/main/kotlin/com/papsign/ktor/openapigen/parameters/parsers/ObjectParameterParser.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
package com.papsign.ktor.openapigen.parameters.parsers
22

33
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
4-
import com.papsign.ktor.openapigen.parameters.parsers.deepobject.DeepBuilder
5-
import com.papsign.ktor.openapigen.parameters.parsers.deepobject.DeepBuilderFactory
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject.DeepBuilderFactory
65
import com.papsign.ktor.openapigen.parameters.util.ParameterInfo
76
import io.ktor.http.Parameters
87
import io.ktor.util.toMap
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
package com.papsign.ktor.openapigen.parameters.parsers.generic
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders
22

33
import com.papsign.ktor.openapigen.parameters.ParameterStyle
44

55
interface Builder<S> where S: ParameterStyle<S>, S: Enum<S> {
66
val style: S
7-
val exploded: Boolean
7+
val explode: Boolean
88
fun build(key: String, parameters: Map<String, List<String>>): Any?
99
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders
2+
3+
import com.papsign.ktor.openapigen.parameters.ParameterStyle
4+
import kotlin.reflect.KType
5+
6+
interface BuilderFactory<out T: Builder<S>, S> where S: ParameterStyle<S>, S: Enum<S> {
7+
fun buildBuilder(type: KType, explode: Boolean): T?
8+
fun buildBuilderForced(type: KType, explode: Boolean): T = buildBuilder(type, explode) ?: error("No ${this.javaClass.declaringClass?.simpleName ?: this.javaClass.simpleName} Builder exists for type $type")
9+
}
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
package com.papsign.ktor.openapigen.parameters.parsers.generic
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders
22

33
import kotlin.reflect.KType
44

55
interface BuilderSelector<out T: Builder<*>> {
6-
fun canHandle(type: KType): Boolean
6+
fun canHandle(type: KType, explode: Boolean): Boolean
77
fun create(type: KType, exploded: Boolean): T
88
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders
2+
3+
import com.papsign.ktor.openapigen.parameters.ParameterStyle
4+
import kotlin.reflect.KType
5+
6+
open class BuilderSelectorFactory<out T: Builder<S>, S>(vararg val selectors: BuilderSelector<T>):
7+
BuilderFactory<T, S> where S: ParameterStyle<S>, S: Enum<S> {
8+
override fun buildBuilder(type: KType, explode: Boolean): T? {
9+
return selectors.find { it.canHandle(type, explode) }?.create(type, explode)
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject
2+
3+
import com.papsign.kotlin.reflection.toKType
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector
5+
import com.papsign.ktor.openapigen.parameters.util.ListToArray
6+
import kotlin.reflect.KType
7+
import kotlin.reflect.jvm.jvmErasure
8+
9+
class ArrayDeepBuilder(type: KType) : CollectionDeepBuilder(type) {
10+
11+
private val converter = ListToArray(type)
12+
13+
override fun transform(lst: List<Any?>): Any? {
14+
return converter.cvt(lst)
15+
}
16+
17+
companion object : BuilderSelector<ArrayDeepBuilder> {
18+
19+
override fun canHandle(type: KType, explode: Boolean): Boolean {
20+
return type.jvmErasure.java.isArray
21+
}
22+
23+
override fun create(type: KType, exploded: Boolean): ArrayDeepBuilder {
24+
return ArrayDeepBuilder(type)
25+
}
26+
}
27+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject
2+
3+
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
5+
import com.papsign.ktor.openapigen.parameters.util.ListToArray
6+
import kotlin.reflect.KType
7+
8+
abstract class CollectionDeepBuilder(type: KType) : DeepBuilder {
9+
10+
private val contentType = ListToArray.arrayComponentKType(type)
11+
12+
private val builder: Builder<QueryParamStyle> by lazy {
13+
DeepBuilderFactory.buildBuilderForced(contentType, explode)
14+
}
15+
16+
abstract fun transform(lst: List<Any?>): Any?
17+
18+
override fun build(key: String, parameters: Map<String, List<String>>): Any? {
19+
val names = parameters.filterKeys { it != key && it.startsWith(key) }
20+
return transform(if (names.isEmpty()) {
21+
listOf()
22+
} else {
23+
val indices =
24+
names.entries.groupBy { (k, _) -> k.substring(key.length + 1, k.indexOf("]", key.length)).toInt() }
25+
indices.entries.fold(
26+
ArrayList<Any?>((0..(indices.keys.max() ?: 0)).map { null })
27+
) { acc, (idx, params) ->
28+
acc[idx] = builder.build("$key[$idx]", params.associate { (key, value) -> key to value })
29+
acc
30+
}
31+
})
32+
}
33+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject
2+
3+
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
5+
6+
interface DeepBuilder :
7+
Builder<QueryParamStyle> {
8+
override val style: QueryParamStyle
9+
get() = QueryParamStyle.deepObject
10+
override val explode: Boolean
11+
get() = true
12+
}
13+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject
2+
3+
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
5+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory
6+
import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.ConverterFormBuilder
7+
8+
object DeepBuilderFactory : BuilderSelectorFactory<Builder<QueryParamStyle>, QueryParamStyle>(
9+
ConverterFormBuilder.primitive,
10+
ListDeepBuilder,
11+
ArrayDeepBuilder,
12+
MapDeepBuilder,
13+
ObjectDeepBuilder
14+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject
2+
3+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector
4+
import kotlin.reflect.KType
5+
import kotlin.reflect.full.isSubclassOf
6+
import kotlin.reflect.jvm.jvmErasure
7+
8+
class ListDeepBuilder(type: KType) : CollectionDeepBuilder(type) {
9+
10+
override fun transform(lst: List<Any?>): Any? {
11+
return lst
12+
}
13+
14+
companion object : BuilderSelector<ListDeepBuilder> {
15+
16+
override fun canHandle(type: KType, explode: Boolean): Boolean {
17+
return type.jvmErasure.isSubclassOf(List::class)
18+
}
19+
20+
override fun create(type: KType, exploded: Boolean): ListDeepBuilder {
21+
return ListDeepBuilder(type)
22+
}
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,38 @@
1-
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject
22

3-
import com.papsign.ktor.openapigen.parameters.parsers.generic.BuilderSelector
3+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector
4+
import com.papsign.ktor.openapigen.parameters.parsers.converters.primitive.PrimitiveConverterFactory
45
import com.papsign.ktor.openapigen.parameters.util.primitiveParsers
56
import kotlin.reflect.KType
67
import kotlin.reflect.full.isSubclassOf
78
import kotlin.reflect.jvm.jvmErasure
89

9-
class MapBuilder(val type: KType) : DeepBuilder() {
10+
class MapDeepBuilder(val type: KType) : DeepBuilder {
1011
private val keyType = type.arguments[0].type!!
1112
private val valueType = type.arguments[1].type!!
12-
private val keyBuilder = primitiveParsers[keyType] ?: error("Only primitives are allowed ")
13+
private val keyBuilder = PrimitiveConverterFactory.buildConverterForced(keyType)
1314
private val valueBuilder by lazy { // must be lazy or will recurse infinitely
14-
DeepBuilderFactory.buildBuilderForced(valueType, exploded)
15+
DeepBuilderFactory.buildBuilderForced(valueType, explode)
1516
}
1617

1718
override fun build(key: String, parameters: Map<String, List<String>>): Any? {
1819
val names = parameters.filterKeys { it.startsWith(key) }
1920
val indices = names.entries.groupBy { (k, _) -> k.substring(key.length + 1, k.indexOf("]", key.length)) }
2021
return indices.entries.fold(LinkedHashMap<Any?, Any?>()) { acc, (k, value) ->
21-
acc[keyBuilder(k)] = valueBuilder.build("$key[$k]", value.associate { (k, v) -> k to v })
22+
acc[keyBuilder.convert(k)] = valueBuilder.build("$key[$k]", value.associate { (k, v) -> k to v })
2223
acc
2324
}
2425
}
2526

26-
companion object: BuilderSelector<MapBuilder> {
27+
companion object:
28+
BuilderSelector<MapDeepBuilder> {
2729

28-
override fun canHandle(type: KType): Boolean {
30+
override fun canHandle(type: KType, explode: Boolean): Boolean {
2931
return type.jvmErasure.isSubclassOf(Map::class)
3032
}
3133

32-
override fun create(type: KType, exploded: Boolean): MapBuilder {
33-
return MapBuilder(type)
34+
override fun create(type: KType, exploded: Boolean): MapDeepBuilder {
35+
return MapDeepBuilder(type)
3436
}
3537
}
3638
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject
2+
3+
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
5+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector
6+
import java.lang.reflect.InvocationTargetException
7+
import kotlin.reflect.KFunction
8+
import kotlin.reflect.KParameter
9+
import kotlin.reflect.KType
10+
import kotlin.reflect.full.primaryConstructor
11+
import kotlin.reflect.jvm.jvmErasure
12+
13+
class ObjectDeepBuilder(val type: KType) : DeepBuilder {
14+
15+
private val builderMap: Map<KParameter, Builder<QueryParamStyle>>
16+
private val constructor: KFunction<Any>
17+
18+
init {
19+
val kclass = type.jvmErasure
20+
if (kclass.isData) {
21+
constructor = kclass.primaryConstructor ?: error("Parameter objects must have primary constructors")
22+
builderMap = constructor.parameters.associateWith { parameter ->
23+
DeepBuilderFactory.buildBuilder(
24+
parameter.type,
25+
explode
26+
) ?: error("Could not find DeepObject Builders for type $type")
27+
}
28+
} else {
29+
error("Only data classes are currently supported for deep objects")
30+
}
31+
}
32+
33+
override fun build(key: String, parameters: Map<String, List<String>>): Any? {
34+
return try {
35+
constructor.callBy(builderMap.mapValues { it.value.build("$key[${it.key.name}]", parameters) })
36+
} catch (e: InvocationTargetException) {
37+
null
38+
}
39+
}
40+
41+
companion object : BuilderSelector<ObjectDeepBuilder> {
42+
43+
override fun canHandle(type: KType, explode: Boolean): Boolean {
44+
return true
45+
}
46+
47+
override fun create(type: KType, exploded: Boolean): ObjectDeepBuilder {
48+
return ObjectDeepBuilder(type)
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited
2+
3+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector
4+
import com.papsign.ktor.openapigen.parameters.util.ListToArray
5+
import kotlin.reflect.KType
6+
import kotlin.reflect.jvm.jvmErasure
7+
8+
class ArrayPipeDelimitedBuilder(type: KType): CollectionDelimitedBuilder(type, "|") {
9+
10+
private val converter = ListToArray(type)
11+
12+
override fun transform(lst: List<Any?>): Any? {
13+
return converter.cvt(lst)
14+
}
15+
16+
companion object : BuilderSelector<ArrayPipeDelimitedBuilder> {
17+
18+
override fun canHandle(type: KType, explode: Boolean): Boolean {
19+
return !explode && type.jvmErasure.java.isArray
20+
}
21+
22+
override fun create(type: KType, exploded: Boolean): ArrayPipeDelimitedBuilder {
23+
return ArrayPipeDelimitedBuilder(
24+
type
25+
)
26+
}
27+
}
28+
}
29+

0 commit comments

Comments
 (0)