Skip to content

Commit ad0a820

Browse files
committed
refactored code to use reified only with DSL, using KClass instances when generating routes to allow external code to generate routes with reflection
1 parent afd7098 commit ad0a820

File tree

10 files changed

+112
-47
lines changed

10 files changed

+112
-47
lines changed

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import com.papsign.ktor.openapigen.modules.registerModule
1717
import kotlin.reflect.KClass
1818
import kotlin.reflect.KType
1919
import kotlin.reflect.full.findAnnotation
20+
import kotlin.reflect.full.starProjectedType
2021

2122
class RequestHandlerModule<T : Any>(
2223
val requestClass: KClass<T>,
@@ -51,7 +52,7 @@ class RequestHandlerModule<T : Any>(
5152
}
5253

5354
companion object {
54-
inline fun <reified T : Any> create(requestExample: T? = null) = RequestHandlerModule(T::class,
55-
getKType<T>(), requestExample)
55+
inline fun <reified T : Any> create(requestExample: T? = null) = RequestHandlerModule(T::class, getKType<T>(), requestExample)
56+
fun <T : Any> create(tClass: KClass<T>, requestExample: T? = null) = RequestHandlerModule(tClass, tClass.starProjectedType, requestExample)
5657
}
5758
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@ import com.papsign.ktor.openapigen.modules.providers.StatusProvider
1616
import com.papsign.ktor.openapigen.modules.registerModule
1717
import io.ktor.http.HttpStatusCode
1818
import kotlin.reflect.KAnnotatedElement
19+
import kotlin.reflect.KClass
1920
import kotlin.reflect.KType
2021
import kotlin.reflect.full.findAnnotation
22+
import kotlin.reflect.full.starProjectedType
2123

2224
class ResponseHandlerModule<T>(val responseType: KType, val responseExample: T? = null) : OperationModule {
2325
private val log = classLogger()
@@ -47,5 +49,6 @@ class ResponseHandlerModule<T>(val responseType: KType, val responseExample: T?
4749

4850
companion object {
4951
inline fun <reified T : Any> create(responseExample: T? = null) = ResponseHandlerModule(getKType<T>(), responseExample)
52+
fun <T : Any> create(tClass: KClass<T>, responseExample: T? = null) = ResponseHandlerModule(tClass.starProjectedType, responseExample)
5053
}
5154
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ 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.KClass
1011
import kotlin.reflect.KParameter
1112
import kotlin.reflect.full.findAnnotation
1213
import kotlin.reflect.full.primaryConstructor
1314

1415

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.")
16+
inline fun <T : Any> buildParameterHandler(tClass: KClass<T>): ParameterHandler<T> {
17+
if (tClass == Unit::class) return UnitParameterHandler as ParameterHandler<T>
18+
assert(tClass.isData) { "API route with ${tClass.simpleName} must be a data class." }
19+
val constructor = tClass.primaryConstructor ?: error("API routes with ${tClass.simpleName} must have a primary constructor.")
2020
val parsers: Map<KParameter, Builder<*>> = constructor.parameters.associateWith { param ->
2121
val type = param.type
2222
param.findAnnotation<HeaderParam>()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?:

src/main/kotlin/com/papsign/ktor/openapigen/route/Functions.kt

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,32 @@ import io.ktor.http.HttpMethod
1212
import io.ktor.routing.HttpMethodRouteSelector
1313
import io.ktor.routing.createRouteFromPath
1414
import io.ktor.util.pipeline.ContextDsl
15+
import kotlin.reflect.KClass
1516
import kotlin.reflect.full.findAnnotation
1617

17-
fun <T: OpenAPIRoute<T>> T.route(path: String): T {
18+
fun <T : OpenAPIRoute<T>> T.route(path: String): T {
1819
return child(ktorRoute.createRouteFromPath(path)).apply {
1920
provider.registerModule(PathProviderModule(path))
2021
}
2122
}
2223

2324
@ContextDsl
24-
inline fun <T: OpenAPIRoute<T>> T.route(path: String, crossinline fn: T.() -> Unit) {
25+
inline fun <T : OpenAPIRoute<T>> T.route(path: String, crossinline fn: T.() -> Unit) {
2526
route(path).fn()
2627
}
2728

28-
fun <T: OpenAPIRoute<T>> T.method(method: HttpMethod): T {
29+
fun <T : OpenAPIRoute<T>> T.method(method: HttpMethod): T {
2930
return child(ktorRoute.createChild(HttpMethodRouteSelector(method))).apply {
3031
provider.registerModule(HttpMethodProviderModule(method))
3132
}
3233
}
3334

3435
@ContextDsl
35-
inline fun <T: OpenAPIRoute<T>> T.method(method: HttpMethod, crossinline fn: T.() -> Unit) {
36+
inline fun <T : OpenAPIRoute<T>> T.method(method: HttpMethod, crossinline fn: T.() -> Unit) {
3637
method(method).fn()
3738
}
3839

39-
fun <T: OpenAPIRoute<T>> T.provider(vararg content: ContentTypeProvider): T {
40+
fun <T : OpenAPIRoute<T>> T.provider(vararg content: ContentTypeProvider): T {
4041
return child().apply {
4142
content.forEach {
4243
provider.registerModule(it)
@@ -45,38 +46,51 @@ fun <T: OpenAPIRoute<T>> T.provider(vararg content: ContentTypeProvider): T {
4546
}
4647

4748
@ContextDsl
48-
inline fun <T: OpenAPIRoute<T>> T.provider(vararg content: ContentTypeProvider, crossinline fn: T.() -> Unit) {
49+
inline fun <T : OpenAPIRoute<T>> T.provider(vararg content: ContentTypeProvider, crossinline fn: T.() -> Unit) {
4950
provider(*content).fn()
5051
}
5152

5253

53-
fun <T: OpenAPIRoute<T>> T.tag(tag: APITag): T {
54+
fun <T : OpenAPIRoute<T>> T.tag(tag: APITag): T {
5455
return child().apply {
5556
provider.registerModule(TagModule(listOf(tag)))
5657
}
5758
}
5859

5960

6061
@ContextDsl
61-
inline fun <T: OpenAPIRoute<T>> T.tag(tag: APITag, crossinline fn: T.() -> Unit) {
62+
inline fun <T : OpenAPIRoute<T>> T.tag(tag: APITag, crossinline fn: T.() -> Unit) {
6263
tag(tag).fn()
6364
}
6465

65-
inline fun <reified P : Any, reified R : Any, reified B : Any, T: OpenAPIRoute<T>> T.preHandle(
66+
inline fun <reified P : Any, reified R : Any, reified B : Any, T : OpenAPIRoute<T>> T.preHandle(
6667
exampleResponse: R? = null,
6768
exampleRequest: B? = null,
6869
handle: T.() -> Unit
6970
) {
70-
val path = P::class.findAnnotation<Path>()
71+
preHandle(P::class, R::class, B::class, exampleResponse, exampleRequest, handle)
72+
}
73+
74+
inline fun <P : Any, R : Any, B : Any, T : OpenAPIRoute<T>> T.preHandle(
75+
pClass: KClass<P>,
76+
rClass: KClass<R>,
77+
bClass: KClass<B>,
78+
exampleResponse: R? = null,
79+
exampleRequest: B? = null,
80+
handle: T.() -> Unit
81+
) {
82+
val path = pClass.findAnnotation<Path>()
7183
val new = if (path != null) child(ktorRoute.createRouteFromPath(path.path)) else child()
7284
new.apply {
7385
provider.registerModule(
7486
RequestHandlerModule.create(
87+
bClass,
7588
exampleRequest
7689
)
7790
)
7891
provider.registerModule(
7992
ResponseHandlerModule.create(
93+
rClass,
8094
exampleResponse
8195
)
8296
)

src/main/kotlin/com/papsign/ktor/openapigen/route/OpenAPIRoute.kt

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,44 +24,52 @@ import io.ktor.routing.application
2424
import io.ktor.routing.contentType
2525
import io.ktor.util.pipeline.PipelineContext
2626
import kotlin.reflect.KClass
27+
import kotlin.reflect.full.starProjectedType
2728
import kotlin.reflect.typeOf
2829

2930
abstract class OpenAPIRoute<T : OpenAPIRoute<T>>(val ktorRoute: Route, val provider: CachingModuleProvider) {
3031
private val log = classLogger()
3132

3233
abstract fun child(route: Route = this.ktorRoute): T
3334

34-
inline fun <reified P : Any, reified R : Any, reified B : Any> handle(
35+
inline fun <P : Any, R : Any, B : Any> handle(
36+
paramsClass: KClass<P>,
37+
responseClass: KClass<R>,
38+
bodyClass: KClass<B>,
3539
crossinline pass: suspend OpenAPIRoute<*>.(pipeline: PipelineContext<Unit, ApplicationCall>, responder: Responder, P, B) -> Unit
3640
) {
37-
val parameterHandler = buildParameterHandler<P>()
41+
val parameterHandler = buildParameterHandler<P>(paramsClass)
3842
provider.registerModule(parameterHandler)
3943

4044
val apiGen = ktorRoute.application.openAPIGen
4145
provider.ofType<HandlerModule>().forEach {
4246
it.configure(apiGen, provider)
4347
}
4448

45-
val BHandler = ValidationHandler.build<B>()
46-
val PHandler = ValidationHandler.build<P>()
49+
val BHandler = ValidationHandler.build(bodyClass)
50+
val PHandler = ValidationHandler.build(paramsClass)
4751

4852
ktorRoute.apply {
49-
getAcceptMap(R::class).let {
53+
getAcceptMap(responseClass).let {
5054
if (it.isNotEmpty()) it else listOf(ContentType.Any to listOf(SelectedSerializer(KtorContentProvider)))
5155
}.forEach { (acceptType, serializers) ->
5256
val responder = ContentTypeResponder(serializers.getResponseSerializer(acceptType), acceptType)
5357
accept(acceptType) {
54-
if (Unit is B) {
58+
if (bodyClass == Unit::class) {
5559
handle {
56-
val params: P = if (Unit is P) Unit else parameterHandler.parse(call.parameters, call.request.headers)
57-
pass(this, responder, PHandler.handle(params), Unit)
60+
@Suppress("UNCHECKED_CAST")
61+
val params: P = if (paramsClass == Unit::class) Unit as P else parameterHandler.parse(call.parameters, call.request.headers)
62+
@Suppress("UNCHECKED_CAST")
63+
pass(this, responder, PHandler.handle(params), Unit as B)
5864
}
5965
} else {
60-
getContentTypesMap(B::class).forEach { (contentType, parsers) ->
66+
if(paramsClass == Unit::class)
67+
getContentTypesMap(bodyClass).forEach { (contentType, parsers) ->
6168
contentType(contentType) {
6269
handle {
63-
val receive: B = parsers.getBodyParser(call.request.contentType()).parseBody(typeOf<B>(), this)
64-
val params: P = if (Unit is P) Unit else parameterHandler.parse(call.parameters, call.request.headers)
70+
val receive: B = parsers.getBodyParser(call.request.contentType()).parseBody(bodyClass.starProjectedType, this)
71+
@Suppress("UNCHECKED_CAST")
72+
val params: P = if (paramsClass == Unit::class) Unit as P else parameterHandler.parse(call.parameters, call.request.headers)
6573
pass(this, responder, PHandler.handle(params), BHandler.handle(receive))
6674
}
6775
}

src/main/kotlin/com/papsign/ktor/openapigen/route/path/auth/Functions.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.papsign.ktor.openapigen.route.preHandle
66
import com.papsign.ktor.openapigen.route.response.OpenAPIPipelineAuthContext
77
import io.ktor.http.HttpMethod
88
import io.ktor.util.pipeline.ContextDsl
9+
import kotlin.reflect.KClass
910
import kotlin.reflect.full.starProjectedType
1011

1112
@ContextDsl
@@ -81,17 +82,29 @@ inline fun <reified P : Any, reified R : Any, reified B : Any, A> OpenAPIAuthent
8182
crossinline body: suspend OpenAPIPipelineAuthContext<A, R>.(P, B) -> Unit
8283
) {
8384
preHandle<P, R, B, OpenAPIAuthenticatedRoute<A>>(exampleResponse, exampleRequest) {
84-
handle(body)
85+
handle(P::class, R::class, B::class, body)
8586
}
8687
}
8788

8889
@ContextDsl
89-
inline fun <reified P : Any, reified R : Any, A> OpenAPIAuthenticatedRoute<A>.handle(
90+
inline fun <reified P : Any, reified R : Any, A> OpenAPIAuthenticatedRoute<A>.handle(
9091
exampleResponse: R? = null,
9192
crossinline body: suspend OpenAPIPipelineAuthContext<A, R>.(P) -> Unit
9293
) {
9394
preHandle<P, R, Unit, OpenAPIAuthenticatedRoute<A>>(exampleResponse, Unit) {
94-
handle(body)
95+
handle(P::class, R::class, body)
96+
}
97+
}
98+
99+
@ContextDsl
100+
inline fun <P : Any, R : Any, A> OpenAPIAuthenticatedRoute<A>.handle(
101+
pClass: KClass<P>,
102+
rClass: KClass<R>,
103+
exampleResponse: R? = null,
104+
crossinline body: suspend OpenAPIPipelineAuthContext<A, R>.(P) -> Unit
105+
) {
106+
preHandle<P, R, Unit, OpenAPIAuthenticatedRoute<A>>(pClass, rClass, Unit::class, exampleResponse, Unit) {
107+
handle(pClass, rClass, body)
95108
}
96109
}
97110

src/main/kotlin/com/papsign/ktor/openapigen/route/path/auth/OpenAPIAuthenticatedRoute.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,40 @@ import com.papsign.ktor.openapigen.route.OpenAPIRoute
77
import com.papsign.ktor.openapigen.route.response.AuthResponseContextImpl
88
import com.papsign.ktor.openapigen.route.response.OpenAPIPipelineAuthContext
99
import io.ktor.routing.Route
10+
import kotlin.reflect.KClass
1011

11-
class OpenAPIAuthenticatedRoute<A>(route: Route, provider: CachingModuleProvider = CachingModuleProvider(), val authProvider: AuthProvider<A>): OpenAPIRoute<OpenAPIAuthenticatedRoute<A>>(route, provider) {
12+
class OpenAPIAuthenticatedRoute<A>(
13+
route: Route,
14+
provider: CachingModuleProvider = CachingModuleProvider(),
15+
val authProvider: AuthProvider<A>
16+
) : OpenAPIRoute<OpenAPIAuthenticatedRoute<A>>(route, provider) {
1217

1318
override fun child(route: Route): OpenAPIAuthenticatedRoute<A> {
1419
return OpenAPIAuthenticatedRoute(route, provider.child(), authProvider)
1520
}
1621

17-
inline fun <reified P : Any, reified R: Any, reified B : Any> handle(crossinline body: suspend OpenAPIPipelineAuthContext<A, R>.(P, B) -> Unit) {
22+
inline fun <P : Any, R : Any, B : Any> handle(
23+
pClass: KClass<P>,
24+
rClass: KClass<R>,
25+
bClass: KClass<B>,
26+
crossinline body: suspend OpenAPIPipelineAuthContext<A, R>.(P, B) -> Unit
27+
) {
1828
child().apply {// child in case path is branch to prevent propagation of the mutable nature of the provider
1929
provider.registerModule(authProvider)
20-
handle<P, R, B> { pipeline, responder, p, b ->
30+
handle<P, R, B>(pClass, rClass, bClass) { pipeline, responder, p, b ->
2131
AuthResponseContextImpl<A, R>(pipeline, authProvider, this, responder).body(p, b)
2232
}
2333
}
2434
}
2535

26-
inline fun <reified P : Any, reified R: Any> handle(crossinline body: suspend OpenAPIPipelineAuthContext<A, R>.(P) -> Unit) {
36+
inline fun <P : Any, R : Any> handle(
37+
pClass: KClass<P>,
38+
rClass: KClass<R>,
39+
crossinline body: suspend OpenAPIPipelineAuthContext<A, R>.(P) -> Unit
40+
) {
2741
child().apply {// child in case path is branch to prevent propagation of the mutable nature of the provider
2842
provider.registerModule(authProvider)
29-
handle<P, R, Unit> { pipeline, responder, p: P, _ ->
43+
handle<P, R, Unit>(pClass, rClass, Unit::class) { pipeline, responder, p: P, _ ->
3044
AuthResponseContextImpl<A, R>(pipeline, authProvider, this, responder).body(p)
3145
}
3246
}

src/main/kotlin/com/papsign/ktor/openapigen/route/path/normal/Functions.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ inline fun <reified P : Any, reified R : Any, reified B : Any> NormalOpenAPIRout
8282
crossinline body: suspend OpenAPIPipelineResponseContext<R>.(P, B) -> Unit
8383
) {
8484
preHandle<P, R, B, NormalOpenAPIRoute>(exampleResponse, exampleRequest) {
85-
handle(body)
85+
handle(P::class, R::class, B::class, body)
8686
}
8787
}
8888

@@ -92,6 +92,6 @@ inline fun <reified P : Any, reified R : Any> NormalOpenAPIRoute.handle(
9292
crossinline body: suspend OpenAPIPipelineResponseContext<R>.(P) -> Unit
9393
) {
9494
preHandle<P, R, Unit, NormalOpenAPIRoute>(exampleResponse, Unit) {
95-
handle(body)
95+
handle(P::class, R::class, body)
9696
}
9797
}

src/main/kotlin/com/papsign/ktor/openapigen/route/path/normal/NormalOpenAPIRoute.kt

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,32 @@ import com.papsign.ktor.openapigen.route.OpenAPIRoute
55
import com.papsign.ktor.openapigen.route.response.OpenAPIPipelineResponseContext
66
import com.papsign.ktor.openapigen.route.response.ResponseContextImpl
77
import io.ktor.routing.Route
8+
import kotlin.reflect.KClass
89

9-
class NormalOpenAPIRoute(route: Route, provider: CachingModuleProvider = CachingModuleProvider()): OpenAPIRoute<NormalOpenAPIRoute>(route, provider) {
10+
class NormalOpenAPIRoute(route: Route, provider: CachingModuleProvider = CachingModuleProvider()) :
11+
OpenAPIRoute<NormalOpenAPIRoute>(route, provider) {
1012

1113
override fun child(route: Route): NormalOpenAPIRoute {
1214
return NormalOpenAPIRoute(route, provider.child())
1315
}
1416

15-
inline fun <reified P : Any, reified R: Any, reified B : Any> handle(crossinline body: suspend OpenAPIPipelineResponseContext<R>.(P, B) -> Unit) {
16-
handle<P, R, B> {pipeline, responder, p, b ->
17+
inline fun <P : Any, R : Any, B : Any> handle(
18+
pClass: KClass<P>,
19+
rClass: KClass<R>,
20+
bClass: KClass<B>,
21+
crossinline body: suspend OpenAPIPipelineResponseContext<R>.(P, B) -> Unit
22+
) {
23+
handle<P, R, B>(pClass, rClass, bClass) { pipeline, responder, p, b ->
1724
ResponseContextImpl<R>(pipeline, this, responder).body(p, b)
1825
}
1926
}
2027

21-
inline fun <reified P : Any, reified R: Any> handle(crossinline body: suspend OpenAPIPipelineResponseContext<R>.(P) -> Unit) {
22-
handle<P, R, Unit> {pipeline, responder, p, _ ->
28+
inline fun <P : Any, R : Any> handle(
29+
pClass: KClass<P>,
30+
rClass: KClass<R>,
31+
crossinline body: suspend OpenAPIPipelineResponseContext<R>.(P) -> Unit
32+
) {
33+
handle<P, R, Unit>(pClass, rClass, Unit::class) { pipeline, responder, p, _ ->
2334
ResponseContextImpl<R>(pipeline, this, responder).body(p)
2435
}
2536
}

src/main/kotlin/com/papsign/ktor/openapigen/validation/ValidationHandler.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -281,8 +281,8 @@ class ValidationHandler private constructor(
281281
get() = classAnnotation + typeAnnotation + additionalAnnotations
282282

283283
companion object {
284-
inline operator fun <reified T> invoke(annotations: List<Annotation> = listOf()): AnnotatedKType {
285-
val type = getKType<T>()
284+
operator fun <T:Any> invoke(tClass: KClass<T>, annotations: List<Annotation> = listOf()): AnnotatedKType {
285+
val type = tClass.starProjectedType
286286
return AnnotatedKType(
287287
type,
288288
annotations
@@ -313,9 +313,10 @@ class ValidationHandler private constructor(
313313
}()
314314
}
315315

316-
inline fun <reified T> build(annotations: List<Annotation> = listOf()): ValidationHandler {
316+
inline fun <T:Any> build(tClass: KClass<T>, annotations: List<Annotation> = listOf()): ValidationHandler {
317317
return build(
318-
AnnotatedKType<T>(
318+
AnnotatedKType(
319+
tClass.starProjectedType,
319320
annotations
320321
)
321322
)

0 commit comments

Comments
 (0)