Skip to content

Commit 2cb2d2e

Browse files
committed
changed Serializaton/Deserialization modules to be annotation based
e.g. @FormDataRequest data class c(val a: String) will use a form data parser. Added Binary serialization and deserialization Added Form Data deserialization Added handler to dynamically detect and load serializaers and deserializers, currently limited to com.papsign.ktor.openapigen.content.type package
1 parent 9523265 commit 2cb2d2e

34 files changed

+543
-184
lines changed

build.gradle

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ buildscript {
55

66
dependencies {
77
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
8-
classpath "com.github.jengelman.gradle.plugins:shadow:4.0.2"
98
}
109
}
1110

1211
plugins {
13-
id 'org.jetbrains.kotlin.jvm' version '1.3.30'
12+
id 'org.jetbrains.kotlin.jvm'
1413
}
1514

1615
group 'com.papsign.ktor'
@@ -36,11 +35,14 @@ dependencies {
3635
implementation "io.ktor:ktor-client-apache:$ktor_version"
3736
// ------------------------
3837

39-
implementation "io.ktor:ktor-jackson:$ktor_version" // needed for parameter parsing
38+
implementation "io.ktor:ktor-jackson:$ktor_version" // needed for parameter parsing and multipart parsing
39+
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.8" // needed for multipart parsing
4040
implementation 'org.webjars:swagger-ui:3.18.2'
41-
42-
testImplementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.8"
41+
42+
implementation "org.reflections:reflections:0.9.11" // only used while initializing
43+
4344
testImplementation "io.ktor:ktor-server-netty:$ktor_version"
45+
testImplementation "io.ktor:ktor-server-test-host:$ktor_version"
4446

4547
}
4648

src/main/kotlin/com/papsign/kotlin/reflection/Reflection.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ inline fun <reified T> getKType(): KType {
2424
open class SuperTypeTokenHolder<T>
2525

2626
fun SuperTypeTokenHolder<*>.getKTypeImpl(): KType =
27-
javaClass.genericSuperclass.toKType().arguments.single().type!!
27+
javaClass.genericSuperclass.toKType().arguments.single().type!!
2828

2929
fun KClass<*>.toInvariantFlexibleProjection(arguments: List<KTypeProjection> = emptyList()): KTypeProjection {
3030
// TODO: there should be an API in kotlin-reflect which creates KType instances corresponding to flexible types
@@ -52,7 +52,7 @@ fun Type.toKTypeProjection(): KTypeProjection = when (this) {
5252
is WildcardType -> when {
5353
lowerBounds.isNotEmpty() -> KTypeProjection.contravariant(lowerBounds.single().toKType())
5454
upperBounds.isNotEmpty() -> KTypeProjection.covariant(upperBounds.single().toKType())
55-
// This looks impossible to obtain through Java com.papsign.reflection API, but someone may construct and pass such an instance here anyway
55+
// This looks impossible to obtain through Java reflection API, but someone may construct and pass such an instance here anyway
5656
else -> KTypeProjection.STAR
5757
}
5858
is GenericArrayType -> Array<Any>::class.toInvariantFlexibleProjection(listOf(genericComponentType.toKTypeProjection()))
@@ -100,6 +100,7 @@ private fun getObjectSubTypes(type: KType): Set<KType> {
100100
}.toSet()
101101
}
102102

103+
103104
private fun makeMapSchema(type: KType): Set<KType> {
104105
return setOf(type.arguments[1].type!!, getKType<String>())
105106
}
@@ -108,13 +109,17 @@ fun KType.allTypes(): Set<KType> {
108109
return getTypeSubTypes(this)
109110
}
110111

112+
fun KType.getObjectSubtypes(): Set<KType> {
113+
return getObjectSubTypes(this)
114+
}
115+
111116
// --- Usage example ---
112117

113-
fun main(args: Array<String>) {
114-
println(getKType<List<Map<String, Array<Double>>>>())
115-
println(getKType<List<*>>())
116-
println(getKType<Array<*>>())
117-
println(getKType<Array<Int?>?>())
118-
println(getKType<Array<Array<String>>>())
119-
println(getKType<Unit>())
120-
}
118+
//fun main(args: Array<String>) {
119+
// println(getKType<List<Map<String, Array<Double>>>>())
120+
// println(getKType<List<*>>())
121+
// println(getKType<Array<*>>())
122+
// println(getKType<Array<Int?>?>())
123+
// println(getKType<Array<Array<String>>>())
124+
// println(getKType<Unit>())
125+
//}

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

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

3-
import com.papsign.ktor.openapigen.content.type.ktor.KtorJSONContentProvider
4-
import com.papsign.ktor.openapigen.content.type.ktor.KtorJSONParser
5-
import com.papsign.ktor.openapigen.content.type.ktor.KtorJSONSerializer
3+
import com.papsign.ktor.openapigen.annotations.encodings.APIEncoding
4+
import com.papsign.ktor.openapigen.content.type.ContentTypeProvider
65
import com.papsign.ktor.openapigen.modules.CachingModuleProvider
76
import com.papsign.ktor.openapigen.modules.schema.*
87
import com.papsign.ktor.openapigen.openapi.ExternalDocumentation
@@ -14,6 +13,7 @@ import io.ktor.application.ApplicationFeature
1413
import io.ktor.application.call
1514
import io.ktor.request.path
1615
import io.ktor.util.AttributeKey
16+
import org.reflections.Reflections
1717
import kotlin.reflect.KType
1818

1919
class OpenAPIGen(private val config: Configuration) {
@@ -31,9 +31,18 @@ class OpenAPIGen(private val config: Configuration) {
3131
private val registrars: Array<out PartialSchemaRegistrar> = config.registrars.plus(PrimitiveSchemas(schemaNamer))
3232
val schemaRegistrar = Schemas()
3333

34-
val globalModuleProvider = CachingModuleProvider().apply {
35-
registerModule(KtorJSONParser)
36-
registerModule(KtorJSONSerializer)
34+
val globalModuleProvider = CachingModuleProvider()
35+
36+
init {
37+
val reflections = Reflections(this::class.java.`package`.name)
38+
val classes = reflections.getTypesAnnotatedWith(APIEncoding::class.java).mapNotNull { it.kotlin.objectInstance }
39+
classes.forEach {
40+
when (it) {
41+
is ContentTypeProvider -> {
42+
globalModuleProvider.registerModule(it)
43+
}
44+
}
45+
}
3746
}
3847

3948
class Configuration(val api: OpenAPI) {
@@ -94,7 +103,7 @@ class OpenAPIGen(private val config: Configuration) {
94103
return tag.name
95104
}
96105

97-
companion object Feature : ApplicationFeature<ApplicationCallPipeline, OpenAPIGen.Configuration, OpenAPIGen> {
106+
companion object Feature : ApplicationFeature<ApplicationCallPipeline, Configuration, OpenAPIGen> {
98107

99108
override val key = AttributeKey<OpenAPIGen>("OpenAPI Generator")
100109

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.papsign.ktor.openapigen.annotations.encodings
2+
3+
@Target(AnnotationTarget.ANNOTATION_CLASS, AnnotationTarget.CLASS)
4+
@Retention(AnnotationRetention.RUNTIME)
5+
/**
6+
* must be applied to parser or serializer object, or annotation to mark it as Encoding Selector
7+
*/
8+
annotation class APIEncoding

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ import io.ktor.http.ContentType
88
import kotlin.reflect.KType
99

1010
interface ContentTypeProvider: OpenAPIModule {
11-
val contentType: ContentType
12-
fun <T> getMediaType(type: KType, apiGen: OpenAPIGen, provider: ModuleProvider<*>, example: T? = null): MediaType<T>?
11+
12+
enum class Usage {
13+
SERIALIZE, PARSE
14+
}
15+
16+
/**
17+
* Done once when routes are created, for request object and response object
18+
* @return null to disable module, or [Map] to register the handler on every content type
19+
* @throws Exception to signal a bad configuration (usually with assert)
20+
*/
21+
fun <T> getMediaType(type: KType, apiGen: OpenAPIGen, provider: ModuleProvider<*>, example: T?, usage: Usage): Map<ContentType, MediaType<T>>?
1322
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package com.papsign.ktor.openapigen.content.type
22

33
import io.ktor.application.ApplicationCall
4+
import io.ktor.http.ContentType
45
import io.ktor.http.HttpStatusCode
56
import io.ktor.util.pipeline.PipelineContext
67

78
interface ResponseSerializer: ContentTypeProvider {
9+
/**
10+
* used to determine which registered response serializer is used, based on the accept header
11+
*/
12+
fun accept(contentType: ContentType): Boolean
813
suspend fun <T: Any> respond(response: T, request: PipelineContext<Unit, ApplicationCall>)
914
suspend fun <T: Any> respond(statusCode: HttpStatusCode, response: T, request: PipelineContext<Unit, ApplicationCall>)
1015
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.papsign.ktor.openapigen.content.type
2+
3+
import com.papsign.ktor.openapigen.modules.OpenAPIModule
4+
5+
interface SelectedModule: OpenAPIModule {
6+
val module: OpenAPIModule
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.papsign.ktor.openapigen.content.type
2+
3+
data class SelectedParser(override val module: BodyParser): SelectedModule
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.papsign.ktor.openapigen.content.type
2+
3+
data class SelectedSerializer(override val module: ResponseSerializer): SelectedModule

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

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

0 commit comments

Comments
 (0)