Skip to content

Commit 900a906

Browse files
committed
refactored DeepBuilder
1 parent c364af5 commit 900a906

File tree

11 files changed

+159
-95
lines changed

11 files changed

+159
-95
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import kotlin.reflect.KType
1010
class CollectionParameterParser<T, A>(info: ParameterInfo, type: KType, val cvt: (List<T>?) -> A) :
1111
InfoParameterParser(info, { style ->
1212
when (style) {
13-
QueryParamStyle.DEFAULT -> QueryParamStyle.form
13+
QueryParamStyle.DEFAULT -> QueryParamStyle.form to null
1414
QueryParamStyle.deepObject -> error("Deep Objects are not supported for Arrays")
15-
else -> style
15+
else -> style to null
1616
}
1717
}) {
1818

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import io.ktor.http.Parameters
99

1010
class EnumParameterParser(info: ParameterInfo, val enumMap: Map<String, *>, val nullable: Boolean) : InfoParameterParser(info, { style ->
1111
when (style) {
12-
QueryParamStyle.DEFAULT, QueryParamStyle.form-> QueryParamStyle.form
12+
QueryParamStyle.DEFAULT, QueryParamStyle.form-> QueryParamStyle.form to false
1313
else -> {
1414
log.warn("Using non-form style for enum type, it is undefined in the OpenAPI standard, reverting to form style")
15-
QueryParamStyle.form
15+
QueryParamStyle.form to false
1616
}
1717
}
1818
}) {

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

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.papsign.ktor.openapigen.parameters.parsers
22

3+
import com.papsign.ktor.openapigen.classLogger
34
import com.papsign.ktor.openapigen.parameters.PathParamStyle
45
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
56
import com.papsign.ktor.openapigen.parameters.util.ParameterInfo
67

78
abstract class InfoParameterParser(
89
info: ParameterInfo,
9-
queryStyle: (QueryParamStyle) -> QueryParamStyle,
10-
pathStyle: (PathParamStyle) -> PathParamStyle
10+
queryStyle: (QueryParamStyle) -> Pair<QueryParamStyle, Boolean?>,
11+
pathStyle: (PathParamStyle) -> Pair<PathParamStyle, Boolean?>
1112
) : ParameterParser {
1213

1314
constructor(info: ParameterInfo, queryStyle: QueryParamStyle = QueryParamStyle.form, pathStyle: PathParamStyle = PathParamStyle.simple) : this(
@@ -16,29 +17,43 @@ abstract class InfoParameterParser(
1617
genReplace(PathParamStyle.DEFAULT, pathStyle)
1718
)
1819

19-
constructor(info: ParameterInfo, queryStyle: (QueryParamStyle) -> QueryParamStyle, pathStyle: PathParamStyle = PathParamStyle.simple) : this(
20+
constructor(info: ParameterInfo, queryStyle: (QueryParamStyle) -> Pair<QueryParamStyle, Boolean?>, pathStyle: PathParamStyle = PathParamStyle.simple) : this(
2021
info,
2122
queryStyle,
2223
genReplace(PathParamStyle.DEFAULT, pathStyle)
2324
)
2425

25-
constructor(info: ParameterInfo, queryStyle: QueryParamStyle = QueryParamStyle.form, pathStyle: (PathParamStyle) -> PathParamStyle) : this(
26+
constructor(info: ParameterInfo, queryStyle: QueryParamStyle = QueryParamStyle.form, pathStyle: (PathParamStyle) -> Pair<PathParamStyle, Boolean?>) : this(
2627
info,
2728
genReplace(QueryParamStyle.DEFAULT, queryStyle),
2829
pathStyle
2930
)
3031

31-
override val key: String = info.key
32-
override val pathStyle: PathParamStyle? = info.pathAnnotation?.style?.let(pathStyle)
33-
override val queryStyle: QueryParamStyle? = info.queryAnnotation?.style?.let(queryStyle)
34-
override val explode: Boolean = info.pathAnnotation?.explode ?: info.queryAnnotation!!.explode
32+
final override val key: String = info.key
33+
final override val pathStyle: PathParamStyle?
34+
final override val queryStyle: QueryParamStyle?
35+
final override val explode: Boolean
36+
37+
init {
38+
val (path, explodePath) = info.pathAnnotation?.style?.let(pathStyle) ?: null to null
39+
val (query, explodeQuery) = info.queryAnnotation?.style?.let(queryStyle) ?: null to null
40+
this.pathStyle = path
41+
this.queryStyle = query
42+
val baseExplode = info.pathAnnotation?.explode ?: info.queryAnnotation!!.explode
43+
val explodeOverride = explodePath ?: explodeQuery
44+
if (explodeOverride != null && explodeOverride != baseExplode) log.warn("Overriding explode $baseExplode to $explodeOverride for style ${path ?: query}")
45+
this.explode = explodeOverride ?: baseExplode
46+
}
3547

3648
companion object {
37-
private fun <T> genReplace(default: T, replace: T): (T) -> T {
49+
50+
val log = classLogger<InfoParameterParser>()
51+
52+
private fun <T> genReplace(default: T, replace: T): (T) -> Pair<T, Boolean?> {
3853
return { value ->
3954
when (value) {
40-
default -> replace
41-
else -> value
55+
default -> replace to null
56+
else -> value to null
4257
}
4358
}
4459
}
Lines changed: 8 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,33 @@
11
package com.papsign.ktor.openapigen.parameters.parsers
22

33
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
4-
import com.papsign.ktor.openapigen.parameters.util.*
4+
import com.papsign.ktor.openapigen.parameters.parsers.deepobject.DeepBuilder.Companion.getBuilderForType
5+
import com.papsign.ktor.openapigen.parameters.util.ParameterInfo
56
import io.ktor.http.Parameters
6-
import kotlin.reflect.KFunction
7-
import kotlin.reflect.KParameter
87
import kotlin.reflect.KType
9-
import kotlin.reflect.full.isSubclassOf
10-
import kotlin.reflect.full.primaryConstructor
118
import kotlin.reflect.jvm.jvmErasure
129

1310
class ObjectParameterParser(info: ParameterInfo, type: KType) : InfoParameterParser(info, {
1411
when (it) {
15-
QueryParamStyle.DEFAULT, QueryParamStyle.deepObject -> QueryParamStyle.deepObject
16-
QueryParamStyle.form -> error("Due to unmanageable ambiguities the form style is forbidden for objects")
12+
QueryParamStyle.DEFAULT, QueryParamStyle.form -> QueryParamStyle.form to false
13+
QueryParamStyle.deepObject -> QueryParamStyle.deepObject to true
1714
else -> error("Query param style $it is undefined for objects in the OpenAPI Spec")
1815
}
1916
}) {
2017

21-
private val builder: (Map<String, Any?>) -> Any?
2218
private val cvt: (Parameters) -> Any?
2319

2420
init {
2521
val kclass = type.jvmErasure
2622
if (kclass.isData) {
27-
val constructor = kclass.primaryConstructor ?: error("Parameter objects must have primary constructors")
28-
val parameters = constructor.parameters
29-
val parameterMap = parameters.associateBy { it.name!! }
30-
builder = { map -> constructor.callBy(map.mapKeys { parameterMap[it.key] }.filterKeys { it != null }.mapKeys { it.key!! }) }
3123
cvt = when (queryStyle) {
3224
QueryParamStyle.deepObject -> {
3325
val builder = getBuilderForType(type)
3426
({ builder.build(key, it) })
3527
}
28+
// QueryParamStyle.form -> {
29+
//
30+
// }
3631
null -> error("Only query params can hold objects")
3732
else -> error("Query param style $queryStyle is not supported")
3833
}
@@ -45,70 +40,5 @@ class ObjectParameterParser(info: ParameterInfo, type: KType) : InfoParameterPar
4540
override fun parse(parameters: Parameters): Any? {
4641
return cvt(parameters)
4742
}
48-
49-
companion object {
50-
51-
interface DeepBuilder {
52-
fun build(path: String, parameters: Parameters): Any?
53-
}
54-
55-
class ObjectBuilder(val type: KType) : DeepBuilder {
56-
private val builderMap: Map<KParameter, DeepBuilder>
57-
private val constructor: KFunction<Any>
58-
init {
59-
val kclass = type.jvmErasure
60-
if (kclass.isData) {
61-
constructor = kclass.primaryConstructor ?: error("Parameter objects must have primary constructors")
62-
val parameters = constructor.parameters
63-
builderMap = parameters.associate { parameter ->
64-
parameter to getBuilderForType(parameter.type)
65-
}
66-
} else {
67-
error("Only data classes are currently supported as parameter objects")
68-
}
69-
}
70-
71-
override fun build(path: String, parameters: Parameters): Any? {
72-
return constructor.callBy(builderMap.mapValues { it.value.build("$path[${it.key.name}]", parameters) })
73-
}
74-
}
75-
76-
class PrimitiveBuilder(val cvt: (String?)->Any?): DeepBuilder {
77-
override fun build(path: String, parameters: Parameters): Any? {
78-
return cvt(parameters[path])
79-
}
80-
}
81-
82-
class EnumBuilder(val enumMap: Map<String, Any?>): DeepBuilder {
83-
override fun build(path: String, parameters: Parameters): Any? {
84-
return parameters[path]?.let { enumMap[it] }
85-
}
86-
}
87-
88-
class ListBuilder(val type: KType): DeepBuilder {
89-
private val contentType = type.arguments[0].type!!
90-
private val builder = getBuilderForType(contentType)
91-
override fun build(path: String, parameters: Parameters): Any? {
92-
val names = parameters.names().filter { it.startsWith(path) }.toSet()
93-
val indices = names.map { it.substring(path.length + 1, it.indexOf("]", path.length)).toInt() }.toSet()
94-
return indices.fold(ArrayList<Any?>().also { it.ensureCapacity(indices.max()?:0) }) { acc, idx ->
95-
acc[idx] = builder.build("$path[$idx]", parameters)
96-
acc
97-
}
98-
}
99-
}
100-
101-
private fun getBuilderForType(type: KType): DeepBuilder {
102-
primitiveParsers[type]?.let { return PrimitiveBuilder(it) }
103-
val clazz = type.jvmErasure
104-
val jclazz = clazz.java
105-
return when {
106-
jclazz.isEnum -> EnumBuilder(jclazz.enumConstants.associateBy { it.toString() })
107-
clazz.isSubclassOf(List::class) -> ListBuilder(type)
108-
jclazz.isArray -> error("Nested array are not yet supported")
109-
clazz.isSubclassOf(Map::class) -> error("Nested maps are not yet supported")
110-
else -> ObjectBuilder(type)
111-
}
112-
}
113-
}
11443
}
44+

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ class PrimitiveParameterParser<T>(
1111
val parse: (String?) -> T
1212
) : InfoParameterParser(info, { style ->
1313
when (style) {
14-
QueryParamStyle.DEFAULT, QueryParamStyle.form-> QueryParamStyle.form
14+
QueryParamStyle.DEFAULT, QueryParamStyle.form-> QueryParamStyle.form to false
1515
else -> {
1616
log.warn("Using non-form style for primitive type, it is undefined in the OpenAPI standard, reverting to form style")
17-
QueryParamStyle.form
17+
QueryParamStyle.form to false
1818
}
1919
}
2020
}) {
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
2+
3+
import com.papsign.ktor.openapigen.parameters.util.primitiveParsers
4+
import io.ktor.http.Parameters
5+
import kotlin.reflect.KType
6+
import kotlin.reflect.full.isSubclassOf
7+
import kotlin.reflect.jvm.jvmErasure
8+
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+
}
28+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
2+
3+
import io.ktor.http.Parameters
4+
5+
class EnumBuilder(val enumMap: Map<String, Any?>):
6+
DeepBuilder {
7+
override fun build(path: String, parameters: Parameters): Any? {
8+
return parameters[path]?.let { enumMap[it] }
9+
}
10+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
2+
3+
import com.papsign.ktor.openapigen.parameters.parsers.deepobject.DeepBuilder.Companion.getBuilderForType
4+
import io.ktor.http.Parameters
5+
import kotlin.reflect.KType
6+
7+
class ListBuilder(val type: KType):
8+
DeepBuilder {
9+
private val contentType = type.arguments[0].type!!
10+
private val builder = getBuilderForType(contentType)
11+
override fun build(path: String, parameters: Parameters): Any? {
12+
val names = parameters.names().filter { it.startsWith(path) }.toSet()
13+
val indices = names.map { it.substring(path.length + 1, it.indexOf("]", path.length)).toInt() }.toSet()
14+
return indices.fold(ArrayList<Any?>().also { it.ensureCapacity(indices.max()?:0) }) { acc, idx ->
15+
acc[idx] = builder.build("$path[$idx]", parameters)
16+
acc
17+
}
18+
}
19+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
2+
3+
import com.papsign.ktor.openapigen.parameters.parsers.deepobject.DeepBuilder.Companion.getBuilderForType
4+
import com.papsign.ktor.openapigen.parameters.util.primitiveParsers
5+
import io.ktor.http.Parameters
6+
import kotlin.reflect.KType
7+
8+
class MapBuilder(val type: KType) :
9+
DeepBuilder {
10+
private val keyType = type.arguments[0].type!!
11+
private val valueType = type.arguments[0].type!!
12+
private val keyBuilder = primitiveParsers[keyType] ?: error("Only primitives are allowed ")
13+
private val valueBuilder = getBuilderForType(valueType)
14+
override fun build(path: String, parameters: Parameters): Any? {
15+
val names = parameters.names().filter { it.startsWith(path) }.toSet()
16+
val indices =
17+
names.map { it.substring(path.length + 1, it.indexOf("]", path.length)) }.associateWith { keyBuilder(it) }
18+
return indices.entries.fold(LinkedHashMap<Any?, Any?>()) { acc, (key, value) ->
19+
acc[value] = valueBuilder.build("$path[$key]", parameters)
20+
acc
21+
}
22+
}
23+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.deepobject
2+
3+
import io.ktor.http.Parameters
4+
import kotlin.reflect.KFunction
5+
import kotlin.reflect.KParameter
6+
import kotlin.reflect.KType
7+
import kotlin.reflect.full.primaryConstructor
8+
import kotlin.reflect.jvm.jvmErasure
9+
10+
class ObjectBuilder(val type: KType) :
11+
DeepBuilder {
12+
private val builderMap: Map<KParameter, DeepBuilder>
13+
private val constructor: KFunction<Any>
14+
15+
init {
16+
val kclass = type.jvmErasure
17+
if (kclass.isData) {
18+
constructor = kclass.primaryConstructor ?: error("Parameter objects must have primary constructors")
19+
builderMap = constructor.parameters.associateWith { parameter -> DeepBuilder.getBuilderForType(parameter.type) }
20+
} else {
21+
error("Only data classes are currently supported as parameter objects")
22+
}
23+
}
24+
25+
override fun build(path: String, parameters: Parameters): Any? {
26+
return constructor.callBy(builderMap.mapValues { it.value.build("$path[${it.key.name}]", parameters) })
27+
}
28+
}

0 commit comments

Comments
 (0)