Skip to content

Commit 37453e0

Browse files
authored
Merge pull request #13 from papsign/experimental-parameters
Experimental parameters
2 parents 5acd9fa + 486ba71 commit 37453e0

File tree

81 files changed

+1694
-118
lines changed

Some content is hidden

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

81 files changed

+1694
-118
lines changed

build.gradle

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,6 @@ dependencies {
1818
implementation "io.ktor:ktor-metrics:$ktor_version"
1919
implementation "io.ktor:ktor-server-sessions:$ktor_version"
2020

21-
// needed for oauth interop
22-
implementation "io.ktor:ktor-auth:$ktor_version"
23-
implementation "io.ktor:ktor-client-core:$ktor_version"
24-
implementation "io.ktor:ktor-client-core-jvm:$ktor_version"
25-
implementation "io.ktor:ktor-client-apache:$ktor_version"
26-
// ------------------------
27-
2821
implementation "io.ktor:ktor-jackson:$ktor_version" // needed for parameter parsing and multipart parsing
2922
implementation "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.8" // needed for multipart parsing
3023
implementation 'org.webjars:swagger-ui:3.18.2'
@@ -37,10 +30,11 @@ dependencies {
3730
}
3831

3932
compileKotlin {
40-
kotlinOptions.jvmTarget = "1.8"
33+
kotlinOptions.jvmTarget = "1.6"
34+
kotlinOptions.freeCompilerArgs += ["-Xuse-experimental=kotlin.ExperimentalStdlibApi"]
4135
}
4236
compileTestKotlin {
43-
kotlinOptions.jvmTarget = "1.8"
37+
kotlinOptions.jvmTarget = "1.6"
4438
}
4539

4640
wrapper {
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
#Mon Mar 02 14:17:20 CET 2020
2+
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-all.zip
13
distributionBase=GRADLE_USER_HOME
24
distributionPath=wrapper/dists
3-
distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip
4-
zipStoreBase=GRADLE_USER_HOME
55
zipStorePath=wrapper/dists
6+
zipStoreBase=GRADLE_USER_HOME

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

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import kotlin.reflect.*
55
import kotlin.reflect.full.createType
66
import kotlin.reflect.full.declaredMemberProperties
77
import kotlin.reflect.full.isSubclassOf
8-
import kotlin.reflect.full.withNullability
98
import kotlin.reflect.jvm.jvmErasure
109

1110
val unitKType = getKType<Unit>()
@@ -15,7 +14,8 @@ inline fun <reified T> isNullable(): Boolean {
1514
}
1615

1716
inline fun <reified T> getKType(): KType {
18-
return object : SuperTypeTokenHolder<T>() { }.getKTypeImpl().withNullability(isNullable<T>())
17+
return typeOf<T>()
18+
// return object : SuperTypeTokenHolder<T>() {}.getKTypeImpl().withNullability(isNullable<T>())
1919
}
2020

2121
// TODO: Lobby for a real kotlin ::type for types because the status quo sucks
@@ -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
@@ -41,13 +41,15 @@ fun Type.toKTypeProjection(): KTypeProjection = when (this) {
4141
is Class<*> -> this.kotlin.toInvariantFlexibleProjection()
4242
is ParameterizedType -> {
4343
val erasure = (rawType as Class<*>).kotlin
44-
erasure.toInvariantFlexibleProjection((erasure.typeParameters.zip(actualTypeArguments).map { (parameter, argument) ->
45-
val projection = argument.toKTypeProjection()
46-
projection.takeIf {
47-
// Get rid of use-site projections on arguments, where the corresponding parameters already have a declaration-site projection
48-
parameter.variance == KVariance.INVARIANT || parameter.variance != projection.variance
49-
} ?: KTypeProjection.invariant(projection.type!!)
50-
}))
44+
erasure.toInvariantFlexibleProjection(
45+
(erasure.typeParameters.zip(actualTypeArguments).map { (parameter, argument) ->
46+
val projection = argument.toKTypeProjection()
47+
projection.takeIf {
48+
// Get rid of use-site projections on arguments, where the corresponding parameters already have a declaration-site projection
49+
parameter.variance == KVariance.INVARIANT || parameter.variance != projection.variance
50+
} ?: KTypeProjection.invariant(projection.type!!)
51+
})
52+
)
5153
}
5254
is WildcardType -> when {
5355
lowerBounds.isNotEmpty() -> KTypeProjection.contravariant(lowerBounds.single().toKType())
@@ -122,4 +124,4 @@ fun KType.getObjectSubtypes(): Set<KType> {
122124
// println(getKType<Array<Int?>?>())
123125
// println(getKType<Array<Array<String>>>())
124126
// println(getKType<Unit>())
125-
//}
127+
//}
Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
package com.papsign.ktor.openapigen.annotations.parameters
22

33
import com.papsign.ktor.openapigen.openapi.ParameterLocation
4+
import com.papsign.ktor.openapigen.parameters.PathParamStyle
45

56
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
67
@Retention(AnnotationRetention.RUNTIME)
78
@APIParam(ParameterLocation.path)
8-
annotation class PathParam(val description: String, val required: Boolean = true, val deprecated: Boolean = false)
9+
annotation class PathParam(
10+
val description: String,
11+
val style: PathParamStyle = PathParamStyle.simple,
12+
val explode: Boolean = false,
13+
val deprecated: Boolean = false
14+
)
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
package com.papsign.ktor.openapigen.annotations.parameters
22

33
import com.papsign.ktor.openapigen.openapi.ParameterLocation
4+
import com.papsign.ktor.openapigen.parameters.QueryParamStyle
45

56
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
67
@Retention(AnnotationRetention.RUNTIME)
78
@APIParam(ParameterLocation.query)
8-
annotation class QueryParam(val description: String, val allowEmptyValues: Boolean = false, val required: Boolean = true, val deprecated: Boolean = false)
9+
annotation class QueryParam(
10+
val description: String,
11+
val style: QueryParamStyle = QueryParamStyle.form,
12+
val explode: Boolean = true,
13+
val allowEmptyValues: Boolean = false,
14+
val deprecated: Boolean = false
15+
)

src/main/kotlin/com/papsign/ktor/openapigen/generator/ParamBuilder.kt

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

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import com.papsign.ktor.openapigen.classLogger
77
import com.papsign.ktor.openapigen.content.type.BodyParser
88
import com.papsign.ktor.openapigen.content.type.ContentTypeProvider
99
import com.papsign.ktor.openapigen.content.type.SelectedParser
10-
import com.papsign.ktor.openapigen.generator.ParamBuilder
1110
import com.papsign.ktor.openapigen.modules.ModuleProvider
1211
import com.papsign.ktor.openapigen.modules.ofClass
1312
import com.papsign.ktor.openapigen.modules.openapi.OperationModule
@@ -36,7 +35,7 @@ class RequestHandlerModule<T : Any>(
3635

3736
val requestMeta = requestClass.findAnnotation<Request>()
3837

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

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ object RouteHandler: HandlerModule {
2121
val methods = provider.ofClass<MethodProvider>()
2222
if (methods.size > 1) error("API cannot have two methods simultaneously: ${methods.map { it.method.value }}")
2323
val paths = provider.ofClass<PathProvider>()
24-
val path = "/${paths.map { it.path.trim('/') }.filter { it.isNotBlank() }.joinToString("/")}"
24+
val path = "/${paths.flatMap { it.path.split('/').filter(String::isNotEmpty) }.joinToString("/")}"
2525
val operationModules = provider.ofClass<OperationModule>()
2626
apiGen.api.paths.getOrPut(path) { PathItem() }.also {pathItem ->
2727
methods.forEach {

src/main/kotlin/com/papsign/ktor/openapigen/modules/openapi/HandlerModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ import com.papsign.ktor.openapigen.modules.OpenAPIModule
66

77
interface HandlerModule: OpenAPIModule {
88
fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>)
9-
}
9+
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package com.papsign.ktor.openapigen.modules.providers
22

3-
import com.papsign.ktor.openapigen.generator.ParamBuilder
3+
import com.papsign.ktor.openapigen.OpenAPIGen
4+
import com.papsign.ktor.openapigen.modules.ModuleProvider
45
import com.papsign.ktor.openapigen.modules.OpenAPIModule
56
import com.papsign.ktor.openapigen.openapi.Parameter
67

78
interface ParameterProvider: OpenAPIModule {
8-
fun getParameters(builder: ParamBuilder): List<Parameter<*>>
9-
}
9+
fun getParameters(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List<Parameter<*>>
10+
}

src/main/kotlin/com/papsign/ktor/openapigen/openapi/OpenAPIModels.kt

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

3+
import com.papsign.ktor.openapigen.parameters.ParameterStyle
4+
35
data class ExternalDocumentation(
46
var url: String,
57
var description: String? = null
@@ -14,7 +16,9 @@ data class Parameter<T>(
1416
var allowEmptyValue: Boolean? = null,
1517
var schema: Schema<T>? = null,
1618
var example: T? = null,
17-
var examples: MutableMap<String, T>? = null
19+
var examples: MutableMap<String, T>? = null,
20+
var style: ParameterStyle<*>? = null,
21+
var explode: Boolean = false
1822
// incomplete
1923
)
2024

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.papsign.ktor.openapigen.parameters
2+
3+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderFactory
5+
6+
interface ParameterStyle<S> where S: ParameterStyle<S>, S: Enum<S> {
7+
val name: String
8+
val factory: BuilderFactory<Builder<S>, S>
9+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.papsign.ktor.openapigen.parameters
2+
3+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderFactory
5+
import com.papsign.ktor.openapigen.parameters.parsers.builders.path.matrix.MatrixBuilderFactory
6+
import com.papsign.ktor.openapigen.parameters.parsers.builders.path.label.LabelBuilderFactory
7+
import com.papsign.ktor.openapigen.parameters.parsers.builders.path.simple.SimpleBuilderFactory
8+
9+
10+
enum class PathParamStyle(override val factory: BuilderFactory<Builder<PathParamStyle>, PathParamStyle>): ParameterStyle<PathParamStyle> {
11+
simple(SimpleBuilderFactory), label(LabelBuilderFactory), matrix(MatrixBuilderFactory)
12+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.papsign.ktor.openapigen.parameters
2+
3+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderFactory
5+
import com.papsign.ktor.openapigen.parameters.parsers.builders.query.deepobject.DeepBuilderFactory
6+
import com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited.PipeDelimitedBuilderFactory
7+
import com.papsign.ktor.openapigen.parameters.parsers.builders.query.delimited.SpaceDelimitedBuilderFactory
8+
import com.papsign.ktor.openapigen.parameters.parsers.builders.query.form.FormBuilderFactory
9+
10+
enum class QueryParamStyle(override val factory: BuilderFactory<Builder<QueryParamStyle>, QueryParamStyle>): ParameterStyle<QueryParamStyle> {
11+
form(FormBuilderFactory), spaceDelimited(SpaceDelimitedBuilderFactory), pipeDelimited(PipeDelimitedBuilderFactory), deepObject(DeepBuilderFactory)
12+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.papsign.ktor.openapigen.parameters.handlers
2+
3+
import com.papsign.ktor.openapigen.OpenAPIGen
4+
import com.papsign.ktor.openapigen.annotations.parameters.PathParam
5+
import com.papsign.ktor.openapigen.annotations.parameters.QueryParam
6+
import com.papsign.ktor.openapigen.annotations.parameters.apiParam
7+
import com.papsign.ktor.openapigen.modules.ModuleProvider
8+
import com.papsign.ktor.openapigen.openapi.Parameter
9+
import com.papsign.ktor.openapigen.openapi.ParameterLocation
10+
import com.papsign.ktor.openapigen.openapi.Schema
11+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
12+
import io.ktor.http.Parameters
13+
import io.ktor.util.toMap
14+
import kotlin.reflect.KFunction
15+
import kotlin.reflect.KParameter
16+
import kotlin.reflect.full.findAnnotation
17+
18+
class ModularParameterHander<T>(val parsers: Map<KParameter, Builder<*>>, val constructor: KFunction<T>) :
19+
ParameterHandler<T> {
20+
21+
override fun parse(parameters: Parameters): T {
22+
return constructor.callBy(parsers.mapValues { it.value.build(it.key.name!!, parameters.toMap()) })
23+
}
24+
25+
override fun getParameters(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List<Parameter<*>> {
26+
27+
fun createParam(param: KParameter, `in`: ParameterLocation, config: (Parameter<*>) -> Unit): Parameter<*> {
28+
return Parameter<Any>(
29+
param.name.toString(),
30+
`in`,
31+
!param.type.isMarkedNullable
32+
).also {
33+
it.schema = apiGen.schemaRegistrar[param.type].schema as Schema<Any>
34+
config(it)
35+
}
36+
}
37+
38+
fun QueryParam.createParam(param: KParameter): Parameter<*> {
39+
val parser = parsers[param]!!
40+
return createParam(param, apiParam.`in`) {
41+
it.description = description
42+
it.allowEmptyValue = allowEmptyValues
43+
it.deprecated = deprecated
44+
it.style = parser.style
45+
it.explode = parser.explode
46+
}
47+
}
48+
49+
fun PathParam.createParam(param: KParameter): Parameter<*> {
50+
val parser = parsers[param]!!
51+
return createParam(param, apiParam.`in`) {
52+
it.description = description
53+
it.deprecated = deprecated
54+
it.style = parser.style
55+
it.explode = parser.explode
56+
}
57+
}
58+
59+
return constructor.parameters.map {
60+
it.findAnnotation<PathParam>()?.createParam(it) ?:
61+
it.findAnnotation<QueryParam>()?.createParam(it) ?:
62+
error("API routes with ${constructor.returnType} must have parameters annotated with one of ${paramAnnotationClasses.map { it.simpleName }}")
63+
}
64+
}
65+
66+
companion object {
67+
private val paramAnnotationClasses = hashSetOf(PathParam::class, QueryParam::class)
68+
}
69+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.papsign.ktor.openapigen.parameters.handlers
2+
3+
import com.papsign.ktor.openapigen.modules.providers.ParameterProvider
4+
import io.ktor.http.Parameters
5+
6+
interface ParameterHandler<T>: ParameterProvider {
7+
fun parse(parameters: Parameters): T
8+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.papsign.ktor.openapigen.parameters.handlers
2+
3+
import com.papsign.ktor.openapigen.OpenAPIGen
4+
import com.papsign.ktor.openapigen.modules.ModuleProvider
5+
import com.papsign.ktor.openapigen.openapi.Parameter
6+
import com.papsign.ktor.openapigen.parameters.handlers.ParameterHandler
7+
import io.ktor.http.Parameters
8+
9+
object UnitParameterHandler :
10+
ParameterHandler<Unit> {
11+
override fun parse(parameters: Parameters) = Unit
12+
override fun getParameters(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List<Parameter<*>> {
13+
return listOf()
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders
2+
3+
import com.papsign.ktor.openapigen.parameters.ParameterStyle
4+
5+
interface Builder<S> where S: ParameterStyle<S>, S: Enum<S> {
6+
val style: S
7+
val explode: Boolean
8+
fun build(key: String, parameters: Map<String, List<String>>): Any?
9+
}

0 commit comments

Comments
 (0)