Skip to content

Commit 4b07532

Browse files
committed
generified, built and cleaned deep object builders
1 parent 900a906 commit 4b07532

26 files changed

+489
-96
lines changed

build.gradle

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ dependencies {
1818
implementation "io.ktor:ktor-metrics:$ktor_version"
1919
implementation "io.ktor:ktor-server-sessions:$ktor_version"
2020

21-
// needed for oauth interop
22-
implementation "io.ktor:ktor-auth:$ktor_version"
23-
implementation "io.ktor:ktor-client-core:$ktor_version"
24-
implementation "io.ktor:ktor-client-core-jvm:$ktor_version"
25-
implementation "io.ktor:ktor-client-apache:$ktor_version"
26-
// ------------------------
27-
2821
implementation "io.ktor:ktor-jackson:$ktor_version" // needed for parameter parsing and multipart parsing
2922
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.8" // needed for multipart parsing
3023
implementation 'org.webjars:swagger-ui:3.18.2'
@@ -37,10 +30,10 @@ dependencies {
3730
}
3831

3932
compileKotlin {
40-
kotlinOptions.jvmTarget = "1.8"
33+
kotlinOptions.jvmTarget = "1.6"
4134
}
4235
compileTestKotlin {
43-
kotlinOptions.jvmTarget = "1.8"
36+
kotlinOptions.jvmTarget = "1.6"
4437
}
4538

4639
wrapper {
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
#Mon Mar 02 14:17:20 CET 2020
2+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
13
distributionBase=GRADLE_USER_HOME
24
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
4-
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6+
zipStoreBase=GRADLE_USER_HOME

src/main/kotlin/com/papsign/ktor/openapigen/openapi/OpenAPIModels.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ data class Parameter<T>(
1717
var schema: Schema<T>? = null,
1818
var example: T? = null,
1919
var examples: MutableMap<String, T>? = null,
20-
var style: ParameterStyle? = null,
20+
var style: ParameterStyle<*>? = null,
2121
var explode: Boolean = false
2222
// incomplete
2323
)
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.papsign.ktor.openapigen.parameters
22

3-
interface ParameterStyle {
3+
import com.papsign.ktor.openapigen.parameters.parsers.generic.BuilderFactory
4+
5+
interface ParameterStyle<S> where S: ParameterStyle<S>, S: Enum<S> {
46
val name: String
57
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.papsign.ktor.openapigen.parameters
22

3-
enum class PathParamStyle: ParameterStyle {
3+
4+
enum class PathParamStyle: ParameterStyle<PathParamStyle> {
45
DEFAULT, simple, label, matrix
56
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
package com.papsign.ktor.openapigen.parameters
22

3-
enum class QueryParamStyle: ParameterStyle {
3+
enum class QueryParamStyle: ParameterStyle<QueryParamStyle> {
44
DEFAULT, form, spaceDelimited, pipeDelimited, deepObject
55
}

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

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
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.Companion.getBuilderForType
4+
import com.papsign.ktor.openapigen.parameters.parsers.deepobject.DeepBuilder
5+
import com.papsign.ktor.openapigen.parameters.parsers.deepobject.DeepBuilderFactory
56
import com.papsign.ktor.openapigen.parameters.util.ParameterInfo
67
import io.ktor.http.Parameters
8+
import io.ktor.util.toMap
79
import kotlin.reflect.KType
8-
import kotlin.reflect.jvm.jvmErasure
910

1011
class ObjectParameterParser(info: ParameterInfo, type: KType) : InfoParameterParser(info, {
1112
when (it) {
@@ -15,28 +16,18 @@ class ObjectParameterParser(info: ParameterInfo, type: KType) : InfoParameterPar
1516
}
1617
}) {
1718

18-
private val cvt: (Parameters) -> Any?
19-
20-
init {
21-
val kclass = type.jvmErasure
22-
if (kclass.isData) {
23-
cvt = when (queryStyle) {
24-
QueryParamStyle.deepObject -> {
25-
val builder = getBuilderForType(type)
26-
({ builder.build(key, it) })
27-
}
19+
private val cvt: (Parameters) -> Any? = when (queryStyle) {
20+
QueryParamStyle.deepObject -> {
21+
val builder = DeepBuilderFactory.buildBuilder(type, true)
22+
({ builder?.build(key, it.toMap()) })
23+
}
2824
// QueryParamStyle.form -> {
2925
//
3026
// }
31-
null -> error("Only query params can hold objects")
32-
else -> error("Query param style $queryStyle is not supported")
33-
}
34-
} else {
35-
error("Only data classes are currently supported as parameter objects")
36-
}
27+
null -> error("Only query params can hold objects")
28+
else -> error("Query param style $queryStyle is not supported")
3729
}
3830

39-
4031
override fun parse(parameters: Parameters): Any? {
4132
return cvt(parameters)
4233
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
2+
3+
import com.papsign.kotlin.reflection.toKType
4+
import com.papsign.ktor.openapigen.parameters.parsers.generic.BuilderSelector
5+
import com.papsign.ktor.openapigen.parameters.util.primCVT
6+
import kotlin.reflect.KType
7+
import kotlin.reflect.jvm.jvmErasure
8+
9+
class ArrayBuilder(val type: KType) : CollectionBuilder() {
10+
private val componentType = type.jvmErasure.java.componentType
11+
override val contentType = componentType.toKType()
12+
13+
override val builder by lazy { // must be lazy or it will recurse infinitely
14+
DeepBuilderFactory.buildBuilderForced(contentType, exploded)
15+
}
16+
17+
private fun cvt(list: List<Any?>): Any? {
18+
val arr = java.lang.reflect.Array.newInstance(componentType, list.size)
19+
list.forEachIndexed { index, elem ->
20+
java.lang.reflect.Array.set(arr, index, elem)
21+
}
22+
return arr
23+
}
24+
25+
override fun build(key: String, parameters: Map<String, List<String>>): Any? {
26+
return cvt(super.build(key, parameters) as List<Any?>)
27+
}
28+
29+
companion object : BuilderSelector<ArrayBuilder> {
30+
31+
private val primCVT = mapOf(
32+
primCVT<Long> { it.toLongArray() },
33+
primCVT<Int> { it.toIntArray() },
34+
primCVT<Float> { it.toFloatArray() },
35+
primCVT<Double> { it.toDoubleArray() },
36+
primCVT<Boolean> { it.toBooleanArray() }
37+
)
38+
39+
/**
40+
* you may think it is redundant but it is not. Maybe the nullable types are useless though.
41+
*/
42+
private val arrCVT = mapOf(
43+
primCVT<Long> { it.toTypedArray() },
44+
primCVT<Int> { it.toTypedArray() },
45+
primCVT<Float> { it.toTypedArray() },
46+
primCVT<Double> { it.toTypedArray() },
47+
primCVT<Boolean> { it.toTypedArray() }
48+
)
49+
50+
override fun canHandle(type: KType): Boolean {
51+
return type.jvmErasure.java.isArray
52+
}
53+
54+
override fun create(type: KType, exploded: Boolean): ArrayBuilder {
55+
return ArrayBuilder(type)
56+
}
57+
}
58+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
2+
3+
import kotlin.reflect.KType
4+
5+
abstract class CollectionBuilder: DeepBuilder() {
6+
7+
protected abstract val contentType: KType
8+
protected abstract val builder: DeepBuilder
9+
10+
override fun build(key: String, parameters: Map<String, List<String>>): Any? {
11+
val names = parameters.filterKeys { it != key && it.startsWith(key) }
12+
if (names.isEmpty()) { return listOf<Any?>() }
13+
val indices =
14+
names.entries.groupBy { (k, _) -> k.substring(key.length + 1, k.indexOf("]", key.length)).toInt() }
15+
return indices.entries.fold(
16+
ArrayList<Any?>((0..(indices.keys.max() ?: 0)).map { null })
17+
) { acc, (idx, params) ->
18+
acc[idx] = builder.build("$key[$idx]", params.associate { (key, value) -> key to value })
19+
acc
20+
}
21+
}
22+
}
Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,25 @@
11
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
22

3+
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
4+
import com.papsign.ktor.openapigen.parameters.parsers.generic.Builder
5+
import com.papsign.ktor.openapigen.parameters.parsers.generic.BuilderFactory
6+
import com.papsign.ktor.openapigen.parameters.parsers.generic.BuilderSelector
7+
import com.papsign.ktor.openapigen.parameters.parsers.generic.SelectorFactory
38
import com.papsign.ktor.openapigen.parameters.util.primitiveParsers
4-
import io.ktor.http.Parameters
59
import kotlin.reflect.KType
610
import kotlin.reflect.full.isSubclassOf
711
import kotlin.reflect.jvm.jvmErasure
812

9-
interface DeepBuilder {
10-
fun build(path: String, parameters: Parameters): Any?
11-
12-
companion object {
13-
fun getBuilderForType(type: KType): DeepBuilder {
14-
primitiveParsers[type]?.let {
15-
return PrimitiveBuilder(it)
16-
}
17-
val clazz = type.jvmErasure
18-
val jclazz = clazz.java
19-
return when {
20-
jclazz.isEnum -> EnumBuilder(jclazz.enumConstants.associateBy { it.toString() })
21-
clazz.isSubclassOf(List::class) -> ListBuilder(type)
22-
jclazz.isArray -> error("Nested array are not yet supported")
23-
clazz.isSubclassOf(Map::class) -> MapBuilder(type)
24-
else -> ObjectBuilder(type)
25-
}
26-
}
27-
}
13+
abstract class DeepBuilder : Builder<QueryParamStyle> {
14+
override val style: QueryParamStyle = QueryParamStyle.deepObject
15+
override val exploded: Boolean = true
2816
}
17+
18+
object DeepBuilderFactory : SelectorFactory<DeepBuilder, QueryParamStyle>(
19+
PrimitiveBuilder,
20+
EnumBuilder,
21+
ListBuilder,
22+
ArrayBuilder,
23+
MapBuilder,
24+
ObjectBuilder
25+
)

0 commit comments

Comments
 (0)