Skip to content

Commit 944713d

Browse files
committed
reworked schema builder to be modular
- removed registrar system - removed namer system - added min/max/clamp annotations (without validation) - updated tests accordingly
1 parent aeacf51 commit 944713d

48 files changed

Lines changed: 792 additions & 326 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

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

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

33
import kotlin.reflect.KType
4-
import kotlin.reflect.KVisibility
54
import kotlin.reflect.full.createType
6-
import kotlin.reflect.full.declaredMemberProperties
75
import kotlin.reflect.jvm.jvmErasure
86
import kotlin.reflect.typeOf
97

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

Lines changed: 22 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,18 @@ import com.papsign.ktor.openapigen.model.base.OpenAPIModel
44
import com.papsign.ktor.openapigen.model.info.ContactModel
55
import com.papsign.ktor.openapigen.model.info.ExternalDocumentationModel
66
import com.papsign.ktor.openapigen.model.info.InfoModel
7-
import com.papsign.ktor.openapigen.model.schema.SchemaModel
87
import com.papsign.ktor.openapigen.model.server.ServerModel
98
import com.papsign.ktor.openapigen.modules.CachingModuleProvider
109
import com.papsign.ktor.openapigen.modules.OpenAPIModule
11-
import com.papsign.ktor.openapigen.modules.schema.*
12-
import com.sun.xml.internal.ws.protocol.soap.ServerMUTube
1310
import io.ktor.application.ApplicationCallPipeline
1411
import io.ktor.application.ApplicationFeature
1512
import io.ktor.application.call
1613
import io.ktor.request.path
1714
import io.ktor.util.AttributeKey
1815
import org.reflections.Reflections
19-
import kotlin.reflect.KType
2016

2117
class OpenAPIGen(
22-
private val config: Configuration,
18+
config: Configuration,
2319
@Deprecated("Will be replaced with less dangerous alternative when the use case has been fleshed out.") val pipeline: ApplicationCallPipeline
2420
) {
2521
private val log = classLogger()
@@ -28,15 +24,6 @@ class OpenAPIGen(
2824

2925
private val tags = HashMap<String, APITag>()
3026

31-
val schemaNamer = object : SchemaNamer {
32-
private val fn = config.schemaNamer
33-
34-
override fun get(type: KType): String = fn(type)
35-
}
36-
37-
private val registrars: Array<out PartialSchemaRegistrar> = config.registrars.plus(PrimitiveSchemas(schemaNamer))
38-
val schemaRegistrar = Schemas()
39-
4027
val globalModuleProvider = CachingModuleProvider()
4128

4229
init {
@@ -49,6 +36,8 @@ class OpenAPIGen(
4936
it.onInit(this)
5037
}
5138
}
39+
config.removeModules.forEach(globalModuleProvider::unRegisterModule)
40+
config.addModules.forEach(globalModuleProvider::registerModule)
5241
}
5342

5443
class Configuration(val api: OpenAPIModel) {
@@ -71,39 +60,34 @@ class OpenAPIGen(
7160
var swaggerUiPath = "swagger-ui"
7261
var serveSwaggerUi = true
7362

74-
var schemaNamer: (KType) -> String = KType::toString
75-
76-
var registrars: Array<PartialSchemaRegistrar> = arrayOf()
7763
var scanPackagesForModules: Array<String> = arrayOf()
78-
}
7964

65+
var addModules = mutableListOf<OpenAPIModule>()
66+
var removeModules = mutableListOf<OpenAPIModule>()
67+
68+
fun addModules(vararg modules: OpenAPIModule) {
69+
addModules.addAll(modules)
70+
}
8071

81-
inner class Schemas : SimpleSchemaRegistrar(schemaNamer) {
72+
fun addModules(modules: Iterable<OpenAPIModule>) {
73+
addModules.addAll(modules)
74+
}
8275

83-
private val schemas = HashSchemaMap()
84-
private val names = HashMap<KType, String>()
76+
fun removeModules(vararg modules: OpenAPIModule) {
77+
removeModules.addAll(modules)
78+
}
8579

86-
override fun get(type: KType, master: SchemaRegistrar): NamedSchema {
87-
val predefined = registrars.fold(null as NamedSchema?) { acc, reg ->
88-
acc ?: reg[type]
89-
}
90-
if (predefined != null)
91-
return predefined
92-
val current = schemas[type]
93-
if (current != null) {
94-
val name = names[type]!!
95-
return NamedSchema(name, SchemaModel.SchemaModelRef<Any>("#/components/schemas/$name"))
96-
}
97-
val (name, schema) = super.get(type, master)
98-
if (schema is SchemaModel.SchemaModelArr<*>) return NamedSchema(name, schema)
80+
fun removeModules(modules: Iterable<OpenAPIModule>) {
81+
removeModules.addAll(modules)
82+
}
9983

100-
schemas[type] = schema
101-
names[type] = name
102-
api.components.schemas[name] = schema
103-
return NamedSchema(name, SchemaModel.SchemaModelRef<Any>("#/components/schemas/$name"))
84+
fun replaceModule(delete: OpenAPIModule, add: OpenAPIModule) {
85+
addModules.add(add)
86+
removeModules.add(delete)
10487
}
10588
}
10689

90+
10791
fun getOrRegisterTag(tag: APITag): String {
10892
val other = tags.getOrPut(tag.name) {
10993
api.tags.add(tag.toTag())

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ object BinaryContentTypeParser: BodyParser, ResponseSerializer, OpenAPIGenModule
6868
}.also {
6969
it.forEach { ContentType.parse(it) }
7070
}
71-
val subtypes = type.jvmErasure.getAcceptableConstructor().parameters.map { it.type.strip() }.toSet()
71+
val subtypes = type.jvmErasure.getAcceptableConstructor().parameters.map { it.type }.toSet()
7272
assertContent (acceptedTypes.containsAll(subtypes)) {
7373
"${this::class.simpleName} can only be used with type ${acceptedTypes.joinToString()}, you are using ${subtypes.minus(acceptedTypes)}"
7474
}

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

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ import com.papsign.ktor.openapigen.content.type.BodyParser
1010
import com.papsign.ktor.openapigen.content.type.ContentTypeProvider
1111
import com.papsign.ktor.openapigen.content.type.ResponseSerializer
1212
import com.papsign.ktor.openapigen.model.operation.MediaTypeModel
13-
import com.papsign.ktor.openapigen.model.schema.DataFormat
14-
import com.papsign.ktor.openapigen.model.schema.DataType
1513
import com.papsign.ktor.openapigen.model.schema.SchemaModel
1614
import com.papsign.ktor.openapigen.modules.ModuleProvider
17-
import com.papsign.ktor.openapigen.modules.schema.NamedSchema
18-
import com.papsign.ktor.openapigen.modules.schema.SchemaRegistrar
19-
import com.papsign.ktor.openapigen.modules.schema.SimpleSchemaRegistrar
15+
import com.papsign.ktor.openapigen.modules.ofClass
16+
import com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProviderModule
2017
import io.ktor.application.ApplicationCall
2118
import io.ktor.application.call
2219
import io.ktor.application.featureOrNull
@@ -29,16 +26,13 @@ import io.ktor.util.pipeline.PipelineContext
2926
import kotlin.reflect.KClass
3027
import kotlin.reflect.KType
3128
import kotlin.reflect.full.findAnnotation
32-
import kotlin.reflect.full.isSubtypeOf
33-
import kotlin.reflect.full.withNullability
3429
import kotlin.reflect.jvm.jvmErasure
3530

3631
/**
3732
* default content provider using the ktor pipeline to handle the serialization and deserialization
3833
*/
3934
object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer, OpenAPIGenModuleExtension {
4035

41-
private val arrayType = getKType<ByteArray>()
4236
private var contentNegotiation: ContentNegotiation? = null
4337
private var contentTypes: Set<ContentType>? = null
4438

@@ -48,23 +42,6 @@ object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer
4842
return contentTypes
4943
}
5044

51-
private class Registrar(val previous: SchemaRegistrar) : SchemaRegistrar {
52-
53-
override fun get(type: KType, master: SchemaRegistrar): NamedSchema {
54-
return if (type == arrayType.withNullability(type.isMarkedNullable)) {
55-
NamedSchema(
56-
"Base64ByteArray", SchemaModel.SchemaModelLitteral(
57-
DataType.string,
58-
DataFormat.byte,
59-
type.isMarkedNullable,
60-
null,
61-
null
62-
)
63-
)
64-
} else previous[type, master]
65-
}
66-
}
67-
6845
override fun <T> getMediaType(type: KType, apiGen: OpenAPIGen, provider: ModuleProvider<*>, example: T?, usage: ContentTypeProvider.Usage):Map<ContentType, MediaTypeModel<T>>? {
6946
if (type == unitKType) return null
7047
val clazz = type.jvmErasure
@@ -81,12 +58,9 @@ object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer
8158
}
8259
}
8360
val contentTypes = initContentTypes(apiGen) ?: return null
84-
val reg = if (type.isSubtypeOf(arrayType)) {
85-
Registrar(SimpleSchemaRegistrar(apiGen.schemaRegistrar.namer))
86-
} else apiGen.schemaRegistrar
87-
88-
val media = MediaTypeModel(reg[type].schema as SchemaModel<T>, example)
61+
val schemaBuilder = provider.ofClass<FinalSchemaBuilderProviderModule>().last().provide(apiGen, provider)
8962
@Suppress("UNCHECKED_CAST")
63+
val media = MediaTypeModel(schemaBuilder.build(type) as SchemaModel<T>, example)
9064
return contentTypes.associateWith { media.copy() }
9165
}
9266

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

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,10 @@ import com.papsign.ktor.openapigen.content.type.ContentTypeProvider
66
import com.papsign.ktor.openapigen.exceptions.assertContent
77
import com.papsign.ktor.openapigen.model.operation.MediaTypeEncodingModel
88
import com.papsign.ktor.openapigen.model.operation.MediaTypeModel
9-
import com.papsign.ktor.openapigen.model.schema.DataFormat
10-
import com.papsign.ktor.openapigen.model.schema.DataType
119
import com.papsign.ktor.openapigen.model.schema.SchemaModel
1210
import com.papsign.ktor.openapigen.modules.ModuleProvider
13-
import com.papsign.ktor.openapigen.modules.schema.NamedSchema
14-
import com.papsign.ktor.openapigen.modules.schema.SchemaRegistrar
11+
import com.papsign.ktor.openapigen.modules.ofClass
12+
import com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProviderModule
1513
import io.ktor.application.ApplicationCall
1614
import io.ktor.http.ContentType
1715
import io.ktor.http.content.PartData
@@ -36,23 +34,6 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
3634
return listOf(ContentType.MultiPart.FormData)
3735
}
3836

39-
private class Registrar(val previous: SchemaRegistrar) : SchemaRegistrar {
40-
41-
override fun get(type: KType, master: SchemaRegistrar): NamedSchema {
42-
return if (streamTypes.contains(type)) {
43-
NamedSchema(
44-
"InputStream", SchemaModel.SchemaModelLitteral(
45-
DataType.string,
46-
DataFormat.binary,
47-
type.isMarkedNullable,
48-
null,
49-
null
50-
)
51-
)
52-
} else previous[type, master]
53-
}
54-
}
55-
5637
data class MultipartCVT<T>(val default: T?, val type: KType, val clazz: KClass<*>, val serializer: (T) -> String, val parser: (String) -> T)
5738

5839
inline fun <reified T> cvt(noinline serializer: (T) -> String, noinline parser: (String) -> T, default: T? = null) = MultipartCVT(default,
@@ -138,12 +119,12 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
138119
assertContent(ctor != null) {
139120
"${this::class.simpleName} requires a primary constructor"
140121
}
141-
assertContent(allowedTypes.containsAll(ctor!!.parameters.map { it.type.strip(false) })) {
122+
assertContent(allowedTypes.containsAll(ctor!!.parameters.map { it.type.withNullability(false) })) {
142123
"${this::class.simpleName} all constructor parameters must be of types: $allowedTypes"
143124
}
144125
}
145126
ContentTypeProvider.Usage.SERIALIZE -> {
146-
assertContent(allowedTypes.containsAll(ctor!!.parameters.map { it.type.strip(false) })) {
127+
assertContent(allowedTypes.containsAll(ctor!!.parameters.map { it.type.withNullability(false) })) {
147128
"${this::class.simpleName} only supports DTOs containing following types: ${allowedTypes.joinToString()}"
148129
}
149130
}
@@ -158,9 +139,8 @@ object MultipartFormDataContentProvider : BodyParser, OpenAPIGenModuleExtension
158139
.mapValues { MediaTypeEncodingModel(it.value!!.contentType) }
159140
}.toMap()
160141
}
161-
val schema = Registrar(apiGen.schemaRegistrar)[type]
162-
142+
val schemaBuilder = provider.ofClass<FinalSchemaBuilderProviderModule>().last().provide(apiGen, provider)
163143
@Suppress("UNCHECKED_CAST")
164-
return mapOf(ContentType.MultiPart.FormData to MediaTypeModel(schema.schema as SchemaModel<T>, example, null, contentTypes))
144+
return mapOf(ContentType.MultiPart.FormData to MediaTypeModel(schemaBuilder.build(type) as SchemaModel<T>, example, null, contentTypes))
165145
}
166146
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ class CachingModuleProvider: ModuleProvider<CachingModuleProvider> {
99

1010
@Synchronized
1111
override fun <T : OpenAPIModule> ofClass(clazz: KClass<T>): Collection<T> {
12-
return modules[clazz]?.let { it.toSet() as Collection<T> } ?: listOf()
12+
return modules[clazz]?.let @Suppress("UNCHECKED_CAST") { it.toSet() as Set<T> } ?: setOf()
1313
}
1414

1515
@Synchronized
@@ -41,9 +41,9 @@ class CachingModuleProvider: ModuleProvider<CachingModuleProvider> {
4141
@Synchronized
4242
override fun child(): CachingModuleProvider {
4343
val new = CachingModuleProvider()
44-
modules.forEach { t: KClass<*>, u ->
44+
modules.forEach { (t: KClass<*>, u) ->
4545
new.modules[t] = LinkedHashSet(u)
4646
}
4747
return new
4848
}
49-
}
49+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.papsign.ktor.openapigen.modules
2+
3+
interface DefaultOpenAPIModule
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package com.papsign.ktor.openapigen.modules
22

3-
interface OpenAPIModule
3+
interface OpenAPIModule

src/main/kotlin/com/papsign/ktor/openapigen/modules/schema/NamedSchema.kt

Lines changed: 0 additions & 6 deletions
This file was deleted.

src/main/kotlin/com/papsign/ktor/openapigen/modules/schema/PartialSchemaRegistrar.kt

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)