Skip to content

Commit b20a531

Browse files
committed
Changed module provider to work with KTypes to allow for generic specialization
Changed module provider to guarantee insertion order is preserved, and last insertion is at the end added status info
1 parent 6593fde commit b20a531

26 files changed

+181
-72
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import io.ktor.application.call
1313
import io.ktor.request.path
1414
import io.ktor.util.AttributeKey
1515
import org.reflections.Reflections
16+
import kotlin.reflect.full.starProjectedType
1617

1718
class OpenAPIGen(
1819
config: Configuration,
@@ -37,7 +38,7 @@ class OpenAPIGen(
3738
}
3839
}
3940
config.removeModules.forEach(globalModuleProvider::unRegisterModule)
40-
config.addModules.forEach(globalModuleProvider::registerModule)
41+
config.addModules.forEach { globalModuleProvider.registerModule(it, it::class.starProjectedType) }
4142
}
4243

4344
class Configuration(val api: OpenAPIModel) {
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
package com.papsign.ktor.openapigen
22

33
import com.papsign.ktor.openapigen.modules.OpenAPIModule
4+
import kotlin.reflect.full.starProjectedType
45

56
/**
67
* implement this to automatically register an object as [OpenAPIModule] in the global context
78
* only works if the object is in a package declared in [OpenAPIGen.Configuration.scanPackagesForModules]
89
*/
910
interface OpenAPIGenModuleExtension: OpenAPIModule, OpenAPIGenExtension {
1011
override fun onInit(gen: OpenAPIGen) {
11-
gen.globalModuleProvider.registerModule(this)
12+
gen.globalModuleProvider.registerModule(this, this::class.starProjectedType)
1213
}
1314
}

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
@@ -1,6 +1,5 @@
11
package com.papsign.ktor.openapigen.content.type.ktor
22

3-
import com.papsign.ktor.openapigen.getKType
43
import com.papsign.ktor.openapigen.unitKType
54
import com.papsign.ktor.openapigen.OpenAPIGen
65
import com.papsign.ktor.openapigen.OpenAPIGenModuleExtension
@@ -12,7 +11,7 @@ import com.papsign.ktor.openapigen.content.type.ResponseSerializer
1211
import com.papsign.ktor.openapigen.model.operation.MediaTypeModel
1312
import com.papsign.ktor.openapigen.model.schema.SchemaModel
1413
import com.papsign.ktor.openapigen.modules.ModuleProvider
15-
import com.papsign.ktor.openapigen.modules.ofClass
14+
import com.papsign.ktor.openapigen.modules.ofType
1615
import com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProviderModule
1716
import io.ktor.application.ApplicationCall
1817
import io.ktor.application.call
@@ -58,7 +57,7 @@ object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer
5857
}
5958
}
6059
val contentTypes = initContentTypes(apiGen) ?: return null
61-
val schemaBuilder = provider.ofClass<FinalSchemaBuilderProviderModule>().last().provide(apiGen, provider)
60+
val schemaBuilder = provider.ofType<FinalSchemaBuilderProviderModule>().last().provide(apiGen, provider)
6261
@Suppress("UNCHECKED_CAST")
6362
val media = MediaTypeModel(schemaBuilder.build(type) as SchemaModel<T>, example)
6463
return contentTypes.associateWith { media.copy() }

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.papsign.ktor.openapigen.model.operation.MediaTypeEncodingModel
99
import com.papsign.ktor.openapigen.model.operation.MediaTypeModel
1010
import com.papsign.ktor.openapigen.model.schema.SchemaModel
1111
import com.papsign.ktor.openapigen.modules.ModuleProvider
12-
import com.papsign.ktor.openapigen.modules.ofClass
12+
import com.papsign.ktor.openapigen.modules.ofType
1313
import com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProviderModule
1414
import io.ktor.application.ApplicationCall
1515
import io.ktor.http.ContentType
@@ -140,7 +140,7 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
140140
.mapValues { MediaTypeEncodingModel(it.value!!.contentType) }
141141
}.toMap()
142142
}
143-
val schemaBuilder = provider.ofClass<FinalSchemaBuilderProviderModule>().last().provide(apiGen, provider)
143+
val schemaBuilder = provider.ofType<FinalSchemaBuilderProviderModule>().last().provide(apiGen, provider)
144144
@Suppress("UNCHECKED_CAST")
145145
return mapOf(ContentType.MultiPart.FormData to MediaTypeModel(schemaBuilder.build(type) as SchemaModel<T>, example, null, contentTypes))
146146
}

src/main/kotlin/com/papsign/ktor/openapigen/interop/StatusPages.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.papsign.ktor.openapigen.interop
22

33
import com.papsign.ktor.openapigen.APIException.Companion.apiException
44
import com.papsign.ktor.openapigen.OpenAPIGen
5+
import com.papsign.ktor.openapigen.modules.registerModule
56
import com.papsign.ktor.openapigen.route.ThrowsInfo
67
import io.ktor.application.call
78
import io.ktor.features.StatusPages

src/main/kotlin/com/papsign/ktor/openapigen/modules/CachingModuleProvider.kt

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ package com.papsign.ktor.openapigen.modules
33
import java.util.*
44
import kotlin.collections.LinkedHashSet
55
import kotlin.reflect.KClass
6+
import kotlin.reflect.KType
7+
import kotlin.reflect.full.starProjectedType
68
import kotlin.reflect.full.superclasses
79

810
class CachingModuleProvider: ModuleProvider<CachingModuleProvider> {
911

1012
@Synchronized
11-
override fun <T : OpenAPIModule> ofClass(clazz: KClass<T>): Collection<T> {
12-
return modules[clazz]?.let @Suppress("UNCHECKED_CAST") { it.toSet() as Set<T> } ?: setOf()
13+
override fun ofType(type: KType): Collection<Any> {
14+
return modules[type]?.toList() ?: listOf()
1315
}
1416

1517
@Synchronized
16-
override fun registerModule(module: OpenAPIModule) {
17-
registerModuleForClass(module::class, module)
18+
override fun registerModule(module: OpenAPIModule, type: KType) {
19+
registerModuleForClass(type, module)
1820
if (module is DependentModule) {
19-
module.handlers.forEach(this::registerModule)
21+
module.handlers.forEach { registerModule(it, it::class.starProjectedType) }
2022
}
2123
}
2224

@@ -26,22 +28,21 @@ class CachingModuleProvider: ModuleProvider<CachingModuleProvider> {
2628
}
2729

2830
@Synchronized
29-
private fun registerModuleForClass(clazz: KClass<*>, module: OpenAPIModule) {
30-
val lst = modules.getOrPut(clazz) {LinkedHashSet()}
31-
if (!lst.contains(module)) {
32-
lst.add(module)
33-
clazz.superclasses.forEach {
34-
registerModuleForClass(it, module)
35-
}
31+
private fun registerModuleForClass(type: KType, module: OpenAPIModule) {
32+
val lst = modules.getOrPut(type) {LinkedHashSet()}
33+
lst.remove(module)
34+
lst.add(module)
35+
(type.classifier as KClass<*>).supertypes.forEach {
36+
registerModuleForClass(it, module)
3637
}
3738
}
3839

39-
private val modules = HashMap<KClass<*>, LinkedHashSet<OpenAPIModule>>()
40+
private val modules = HashMap<KType, LinkedHashSet<OpenAPIModule>>()
4041

4142
@Synchronized
4243
override fun child(): CachingModuleProvider {
4344
val new = CachingModuleProvider()
44-
modules.forEach { (t: KClass<*>, u) ->
45+
modules.forEach { (t: KType, u) ->
4546
new.modules[t] = LinkedHashSet(u)
4647
}
4748
return new
Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
package com.papsign.ktor.openapigen.modules
22

3-
import kotlin.reflect.KClass
3+
import com.papsign.ktor.openapigen.getKType
4+
import kotlin.reflect.KType
45

56
interface ModuleProvider<THIS: ModuleProvider<THIS>> {
6-
fun <T: OpenAPIModule> ofClass(clazz: KClass<T>): Collection<T>
7-
fun registerModule(module: OpenAPIModule)
7+
fun ofType(type: KType): Collection<Any>
8+
fun registerModule(module: OpenAPIModule, type: KType)
89
fun unRegisterModule(module: OpenAPIModule)
910
fun child(): THIS
1011
}
1112

12-
inline fun <reified T: OpenAPIModule> ModuleProvider<*>.ofClass(): Collection<T> {
13-
return ofClass(T::class)
14-
}
13+
inline fun <reified T: OpenAPIModule> ModuleProvider<*>.ofType(): Collection<T> {
14+
return ofType(getKType<T>()) as Collection<T>
15+
}
16+
17+
inline fun <reified T: OpenAPIModule> ModuleProvider<*>.registerModule(module: T) {
18+
return registerModule(module, getKType<T>())
19+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import com.papsign.ktor.openapigen.OpenAPIGen
44
import com.papsign.ktor.openapigen.model.operation.OperationModel
55
import com.papsign.ktor.openapigen.model.security.SecurityModel
66
import com.papsign.ktor.openapigen.modules.ModuleProvider
7-
import com.papsign.ktor.openapigen.modules.ofClass
7+
import com.papsign.ktor.openapigen.modules.ofType
88
import com.papsign.ktor.openapigen.modules.openapi.OperationModule
99
import com.papsign.ktor.openapigen.modules.providers.AuthProvider
1010

1111
object AuthHandler: OperationModule {
1212

1313
override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) {
14-
val authHandlers = provider.ofClass<AuthProvider<*>>()
14+
val authHandlers = provider.ofType<AuthProvider<*>>()
1515
val security = authHandlers.flatMap { it.security }.distinct()
1616
operation.security = security.map { SecurityModel().also { sec ->
1717
it.forEach { sec[it.scheme.name] = it.requirements }

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ import com.papsign.ktor.openapigen.content.type.SelectedParser
1010
import com.papsign.ktor.openapigen.model.operation.OperationModel
1111
import com.papsign.ktor.openapigen.model.operation.RequestBodyModel
1212
import com.papsign.ktor.openapigen.modules.ModuleProvider
13-
import com.papsign.ktor.openapigen.modules.ofClass
13+
import com.papsign.ktor.openapigen.modules.ofType
1414
import com.papsign.ktor.openapigen.modules.openapi.OperationModule
1515
import com.papsign.ktor.openapigen.modules.providers.ParameterProvider
16+
import com.papsign.ktor.openapigen.modules.registerModule
1617
import kotlin.reflect.KClass
1718
import kotlin.reflect.KType
1819
import kotlin.reflect.full.findAnnotation
@@ -26,7 +27,7 @@ class RequestHandlerModule<T : Any>(
2627
private val log = classLogger()
2728

2829
override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) {
29-
val map = provider.ofClass<BodyParser>().mapNotNull {
30+
val map = provider.ofType<BodyParser>().mapNotNull {
3031
val mediaType = it.getMediaType(requestType, apiGen, provider, requestExample, ContentTypeProvider.Usage.PARSE)
3132
?: return@mapNotNull null
3233
provider.registerModule(SelectedParser(it))
@@ -35,7 +36,7 @@ class RequestHandlerModule<T : Any>(
3536

3637
val requestMeta = requestClass.findAnnotation<Request>()
3738

38-
val parameters = provider.ofClass<ParameterProvider>().flatMap { it.getParameters(apiGen, provider) }
39+
val parameters = provider.ofType<ParameterProvider>().flatMap { it.getParameters(apiGen, provider) }
3940
operation.parameters = operation.parameters?.let { (it + parameters).distinct() } ?: parameters
4041
operation.requestBody = operation.requestBody?.apply {
4142
map.forEach { (key, value) ->

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

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@ import com.papsign.ktor.openapigen.content.type.SelectedSerializer
1010
import com.papsign.ktor.openapigen.model.operation.OperationModel
1111
import com.papsign.ktor.openapigen.model.operation.StatusResponseModel
1212
import com.papsign.ktor.openapigen.modules.ModuleProvider
13-
import com.papsign.ktor.openapigen.modules.ofClass
13+
import com.papsign.ktor.openapigen.modules.ofType
1414
import com.papsign.ktor.openapigen.modules.openapi.OperationModule
15+
import com.papsign.ktor.openapigen.modules.providers.StatusProvider
16+
import com.papsign.ktor.openapigen.modules.registerModule
1517
import io.ktor.http.HttpStatusCode
16-
import kotlin.reflect.KClass
18+
import kotlin.reflect.KAnnotatedElement
1719
import kotlin.reflect.KType
1820
import kotlin.reflect.full.findAnnotation
1921

20-
class ResponseHandlerModule<T : Any>(val responseClass: KClass<T>, val responseType: KType, val responseExample: T? = null) : OperationModule {
22+
class ResponseHandlerModule<T>(val responseType: KType, val responseExample: T? = null) : OperationModule {
2123
private val log = classLogger()
2224
override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: OperationModel) {
23-
24-
val responseMeta = responseClass.findAnnotation<Response>()
25-
val statusCode = responseMeta?.statusCode?.let { HttpStatusCode.fromValue(it) } ?: HttpStatusCode.OK
25+
val responseMeta = (responseType.classifier as? KAnnotatedElement)?.findAnnotation<Response>()
26+
val statusCode = provider.ofType<StatusProvider>().lastOrNull()?.getStatusForType(responseType) ?: responseMeta?.statusCode?.let { HttpStatusCode.fromValue(it) }
27+
?: HttpStatusCode.OK
2628
val status = statusCode.value.toString()
27-
val map = provider.ofClass<ResponseSerializer>().mapNotNull {
29+
val map = provider.ofType<ResponseSerializer>().mapNotNull {
2830
val mediaType = it.getMediaType(responseType, apiGen, provider, responseExample, ContentTypeProvider.Usage.SERIALIZE)
2931
?: return@mapNotNull null
3032
provider.registerModule(SelectedSerializer(it))
@@ -44,7 +46,6 @@ class ResponseHandlerModule<T : Any>(val responseClass: KClass<T>, val responseT
4446
}
4547

4648
companion object {
47-
inline fun <reified T : Any> create(responseExample: T? = null) = ResponseHandlerModule(T::class,
48-
getKType<T>(), responseExample)
49+
inline fun <reified T : Any> create(responseExample: T? = null) = ResponseHandlerModule(getKType<T>(), responseExample)
4950
}
5051
}

0 commit comments

Comments
 (0)