Skip to content

Commit cb716fd

Browse files
authored
Merge pull request #63 from davidhiendl/allow-explicit-class-references
Allow explicit class references when building routes
2 parents e4fe15b + ca1f46d commit cb716fd

File tree

19 files changed

+409
-109
lines changed

19 files changed

+409
-109
lines changed

src/main/kotlin/com/papsign/ktor/openapigen/KTypeUtil.kt

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
11
package com.papsign.ktor.openapigen
22

3-
import com.papsign.ktor.openapigen.annotations.mapping.openAPIName
4-
import java.lang.reflect.Field
53
import kotlin.reflect.*
64
import kotlin.reflect.full.createType
7-
import kotlin.reflect.full.declaredMemberProperties
85
import kotlin.reflect.full.memberProperties
9-
import kotlin.reflect.jvm.javaField
106
import kotlin.reflect.jvm.jvmErasure
117

128
val unitKType = getKType<Unit>()
139

14-
inline fun <reified T> isNullable(): Boolean {
10+
internal inline fun <reified T> isNullable(): Boolean {
1511
return null is T
1612
}
1713

18-
inline fun <reified T> getKType() = typeOf<T>()
14+
@PublishedApi
15+
internal inline fun <reified T> getKType() = typeOf<T>()
1916

20-
fun KType.strip(nullable: Boolean = isMarkedNullable): KType {
17+
internal fun KType.strip(nullable: Boolean = isMarkedNullable): KType {
2118
return jvmErasure.createType(arguments, nullable)
2219
}
2320

24-
fun KType.deepStrip(nullable: Boolean = isMarkedNullable): KType {
21+
internal fun KType.deepStrip(nullable: Boolean = isMarkedNullable): KType {
2522
return jvmErasure.createType(arguments.map { it.copy(type = it.type?.deepStrip()) }, nullable)
2623
}
2724

@@ -44,4 +41,4 @@ val KType.memberProperties: List<KTypeProperty>
4441
}
4542
}
4643

47-
val KClass<*>.isInterface get() = java.isInterface
44+
internal val KClass<*>.isInterface get() = java.isInterface

src/main/kotlin/com/papsign/ktor/openapigen/content/type/BodyParser.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ package com.papsign.ktor.openapigen.content.type
33
import io.ktor.application.ApplicationCall
44
import io.ktor.http.ContentType
55
import io.ktor.util.pipeline.PipelineContext
6-
import kotlin.reflect.KClass
76
import kotlin.reflect.KType
87

98
interface BodyParser: ContentTypeProvider {
10-
fun <T: Any> getParseableContentTypes(clazz: KClass<T>): List<ContentType>
9+
fun <T: Any> getParseableContentTypes(type: KType): List<ContentType>
1110
suspend fun <T: Any> parseBody(clazz: KType, request: PipelineContext<Unit, ApplicationCall>): T
1211
}

src/main/kotlin/com/papsign/ktor/openapigen/content/type/ResponseSerializer.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import io.ktor.application.ApplicationCall
44
import io.ktor.http.ContentType
55
import io.ktor.http.HttpStatusCode
66
import io.ktor.util.pipeline.PipelineContext
7-
import kotlin.reflect.KClass
7+
import kotlin.reflect.KType
88

99
interface ResponseSerializer: ContentTypeProvider {
1010
/**
1111
* used to determine which registered response serializer is used, based on the accept header
1212
*/
13-
fun <T: Any> getSerializableContentTypes(clazz: KClass<T>): List<ContentType>
13+
fun <T: Any> getSerializableContentTypes(type: KType): List<ContentType>
1414
suspend fun <T: Any> respond(response: T, request: PipelineContext<Unit, ApplicationCall>, contentType: ContentType)
1515
suspend fun <T: Any> respond(statusCode: HttpStatusCode, response: T, request: PipelineContext<Unit, ApplicationCall>, contentType: ContentType)
1616
}

src/main/kotlin/com/papsign/ktor/openapigen/content/type/binary/BinaryContentTypeParser.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,12 @@ object BinaryContentTypeParser: BodyParser, ResponseSerializer, OpenAPIGenModule
2929
return constructors.first { it.parameters.size == 1 && acceptedTypes.contains(it.parameters[0].type) }
3030
}
3131

32-
override fun <T : Any> getParseableContentTypes(clazz: KClass<T>): List<ContentType> {
33-
return clazz.findAnnotation<BinaryRequest>()?.contentTypes?.map(ContentType.Companion::parse) ?: listOf()
32+
override fun <T : Any> getParseableContentTypes(type: KType): List<ContentType> {
33+
return type.jvmErasure.findAnnotation<BinaryRequest>()?.contentTypes?.map(ContentType.Companion::parse) ?: listOf()
3434
}
3535

36-
override fun <T: Any> getSerializableContentTypes(clazz: KClass<T>): List<ContentType> {
37-
return clazz.findAnnotation<BinaryResponse>()?.contentTypes?.map(ContentType.Companion::parse) ?: listOf()
36+
override fun <T: Any> getSerializableContentTypes(type: KType): List<ContentType> {
37+
return type.jvmErasure.findAnnotation<BinaryResponse>()?.contentTypes?.map(ContentType.Companion::parse) ?: listOf()
3838
}
3939

4040
override suspend fun <T : Any> respond(response: T, request: PipelineContext<Unit, ApplicationCall>, contentType: ContentType) {

src/main/kotlin/com/papsign/ktor/openapigen/content/type/ktor/KtorContentProvider.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import io.ktor.http.HttpStatusCode
2222
import io.ktor.request.receive
2323
import io.ktor.response.respond
2424
import io.ktor.util.pipeline.PipelineContext
25-
import kotlin.reflect.KClass
2625
import kotlin.reflect.KType
2726
import kotlin.reflect.full.findAnnotation
2827
import kotlin.reflect.jvm.jvmErasure
@@ -63,15 +62,15 @@ object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer
6362
return contentTypes.associateWith { media.copy() }
6463
}
6564

66-
override fun <T : Any> getParseableContentTypes(clazz: KClass<T>): List<ContentType> {
65+
override fun <T : Any> getParseableContentTypes(type: KType): List<ContentType> {
6766
return contentTypes!!.toList()
6867
}
6968

7069
override suspend fun <T: Any> parseBody(clazz: KType, request: PipelineContext<Unit, ApplicationCall>): T {
7170
return request.call.receive(clazz)
7271
}
7372

74-
override fun <T: Any> getSerializableContentTypes(clazz: KClass<T>): List<ContentType> {
73+
override fun <T: Any> getSerializableContentTypes(type: KType): List<ContentType> {
7574
return contentTypes!!.toList()
7675
}
7776

src/main/kotlin/com/papsign/ktor/openapigen/content/type/multipart/MultipartFormDataContentProvider.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import kotlin.reflect.jvm.jvmErasure
3131

3232
object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension {
3333

34-
override fun <T : Any> getParseableContentTypes(clazz: KClass<T>): List<ContentType> {
34+
override fun <T : Any> getParseableContentTypes(type: KType): List<ContentType> {
3535
return listOf(ContentType.MultiPart.FormData)
3636
}
3737

@@ -68,7 +68,7 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
6868
private val typeContentTypes = HashMap<KType, Map<String, MediaTypeEncodingModel>>()
6969

7070

71-
override suspend fun <T : Any> parseBody(clazz: KType, request: PipelineContext<Unit, ApplicationCall>): T {
71+
override suspend fun <T : Any> parseBody(type: KType, request: PipelineContext<Unit, ApplicationCall>): T {
7272
val objectMap = HashMap<String, Any>()
7373
request.context.receiveMultipart().forEachPart {
7474
val name = it.name
@@ -86,7 +86,8 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
8686
}
8787
}
8888
}
89-
val ctor = (clazz.classifier as KClass<T>).primaryConstructor!!
89+
@Suppress("UNCHECKED_CAST")
90+
val ctor = (type.classifier as KClass<T>).primaryConstructor!!
9091
return ctor.callBy(ctor.parameters.associateWith {
9192
val raw = objectMap[it.openAPIName]
9293
if ((raw == null || (raw !is InputStream && streamTypes.contains(it.type))) && it.type.isMarkedNullable) {

src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/RequestHandlerModule.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.papsign.ktor.openapigen.modules.handlers
22

3-
import com.papsign.ktor.openapigen.getKType
43
import com.papsign.ktor.openapigen.OpenAPIGen
54
import com.papsign.ktor.openapigen.annotations.Request
65
import com.papsign.ktor.openapigen.classLogger
@@ -14,12 +13,11 @@ import com.papsign.ktor.openapigen.modules.ofType
1413
import com.papsign.ktor.openapigen.modules.openapi.OperationModule
1514
import com.papsign.ktor.openapigen.modules.providers.ParameterProvider
1615
import com.papsign.ktor.openapigen.modules.registerModule
17-
import kotlin.reflect.KClass
1816
import kotlin.reflect.KType
1917
import kotlin.reflect.full.findAnnotation
18+
import kotlin.reflect.jvm.jvmErasure
2019

2120
class RequestHandlerModule<T : Any>(
22-
val requestClass: KClass<T>,
2321
val requestType: KType,
2422
val requestExample: T? = null
2523
) : OperationModule {
@@ -34,7 +32,7 @@ class RequestHandlerModule<T : Any>(
3432
mediaType.map { Pair(it.key.toString(), it.value) }
3533
}.flatten().associate { it }
3634

37-
val requestMeta = requestClass.findAnnotation<Request>()
35+
val requestMeta = requestType.jvmErasure.findAnnotation<Request>()
3836

3937
val parameters = provider.ofType<ParameterProvider>().flatMap { it.getParameters(apiGen, provider) }
4038
operation.parameters = operation.parameters?.let { (it + parameters).distinct() } ?: parameters
@@ -51,7 +49,6 @@ class RequestHandlerModule<T : Any>(
5149
}
5250

5351
companion object {
54-
inline fun <reified T : Any> create(requestExample: T? = null) = RequestHandlerModule(T::class,
55-
getKType<T>(), requestExample)
52+
fun <T : Any> create(tType: KType, requestExample: T? = null) = RequestHandlerModule(tType, requestExample)
5653
}
5754
}

src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/ResponseHandlerModule.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.papsign.ktor.openapigen.modules.handlers
22

3-
import com.papsign.ktor.openapigen.getKType
43
import com.papsign.ktor.openapigen.OpenAPIGen
54
import com.papsign.ktor.openapigen.annotations.Response
65
import com.papsign.ktor.openapigen.classLogger
@@ -46,6 +45,6 @@ class ResponseHandlerModule<T>(val responseType: KType, val responseExample: T?
4645
}
4746

4847
companion object {
49-
inline fun <reified T : Any> create(responseExample: T? = null) = ResponseHandlerModule(getKType<T>(), responseExample)
48+
fun <T : Any> create(tType: KType, responseExample: T? = null) = ResponseHandlerModule(tType, responseExample)
5049
}
5150
}

src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/ThrowOperationHandler.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ object ThrowOperationHandler : OperationModule {
2121
private val log = classLogger()
2222
override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) {
2323

24-
val exceptions = provider.ofType<ThrowInfoProvider>().flatMap { it.exceptions }
25-
exceptions.groupBy { it.status }.forEach { exceptions ->
24+
provider
25+
.ofType<ThrowInfoProvider>()
26+
.flatMap { it.exceptions }
27+
.groupBy { it.status }
28+
.forEach { exceptions ->
2629
val map: MutableMap<String, MediaTypeModel<*>> = exceptions.value.flatMap { ex ->
2730
provider.ofType<ResponseSerializer>().mapNotNull {
2831
if (ex.contentType == unitKType) return@mapNotNull null

src/main/kotlin/com/papsign/ktor/openapigen/parameters/util/Util.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,29 @@ import com.papsign.ktor.openapigen.parameters.handlers.ModularParameterHandler
77
import com.papsign.ktor.openapigen.parameters.handlers.ParameterHandler
88
import com.papsign.ktor.openapigen.parameters.handlers.UnitParameterHandler
99
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
10+
import kotlin.reflect.KFunction
1011
import kotlin.reflect.KParameter
12+
import kotlin.reflect.KType
1113
import kotlin.reflect.full.findAnnotation
1214
import kotlin.reflect.full.primaryConstructor
15+
import kotlin.reflect.jvm.jvmErasure
1316

14-
15-
inline fun <reified T : Any> buildParameterHandler(): ParameterHandler<T> {
16-
if (Unit is T) return UnitParameterHandler as ParameterHandler<T>
17-
val t = T::class
18-
assert(t.isData) { "API route with ${t.simpleName} must be a data class." }
19-
val constructor = t.primaryConstructor ?: error("API routes with ${t.simpleName} must have a primary constructor.")
17+
fun <T : Any> buildParameterHandler(tType: KType): ParameterHandler<T> {
18+
@Suppress("UNCHECKED_CAST")
19+
if (tType.classifier == Unit::class) return UnitParameterHandler as ParameterHandler<T>
20+
val tClass = tType.jvmErasure
21+
assert(tClass.isData) { "API route with ${tClass.simpleName} must be a data class." }
22+
val constructor = tClass.primaryConstructor ?: error("API routes with ${tClass.simpleName} must have a primary constructor.")
2023
val parsers: Map<KParameter, Builder<*>> = constructor.parameters.associateWith { param ->
2124
val type = param.type
2225
param.findAnnotation<HeaderParam>()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?:
2326
param.findAnnotation<PathParam>()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?:
2427
param.findAnnotation<QueryParam>()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?:
2528
error("Parameters must be annotated with @PathParam or @QueryParam")
2629
}
30+
@Suppress("UNCHECKED_CAST")
2731
return ModularParameterHandler(
2832
parsers,
29-
constructor
33+
constructor as KFunction<T>
3034
)
3135
}

0 commit comments

Comments
 (0)