Skip to content

Commit 650c4d4

Browse files
authored
Merge pull request #32 from SerVB/reworked-model
Add header parameters support
2 parents be025c9 + 646f366 commit 650c4d4

File tree

11 files changed

+106
-7
lines changed

11 files changed

+106
-7
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.papsign.ktor.openapigen.annotations.parameters
2+
3+
import com.papsign.ktor.openapigen.model.operation.ParameterLocation
4+
import com.papsign.ktor.openapigen.parameters.HeaderParamStyle
5+
6+
@Target(AnnotationTarget.FIELD, AnnotationTarget.PROPERTY, AnnotationTarget.VALUE_PARAMETER)
7+
@Retention(AnnotationRetention.RUNTIME)
8+
@APIParam(ParameterLocation.header)
9+
annotation class HeaderParam(
10+
val description: String,
11+
val style: HeaderParamStyle = HeaderParamStyle.simple,
12+
val explode: Boolean = true,
13+
val allowEmptyValues: Boolean = false,
14+
val deprecated: Boolean = false
15+
)

src/main/kotlin/com/papsign/ktor/openapigen/annotations/parameters/ParamAnnotationUtil.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ package com.papsign.ktor.openapigen.annotations.parameters
22

33
import kotlin.reflect.full.findAnnotation
44

5+
internal val HeaderParam.apiParam: APIParam
6+
get() = annotationClass.findAnnotation()!!
7+
58
internal val QueryParam.apiParam: APIParam
69
get() = annotationClass.findAnnotation()!!
710

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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.header.simple.SimpleBuilderFactory
6+
7+
8+
enum class HeaderParamStyle(override val factory: BuilderFactory<Builder<HeaderParamStyle>, HeaderParamStyle>): ParameterStyle<HeaderParamStyle> {
9+
simple(SimpleBuilderFactory)
10+
}

src/main/kotlin/com/papsign/ktor/openapigen/parameters/handlers/ModularParameterHander.kt

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.papsign.ktor.openapigen.parameters.handlers
22

33
import com.papsign.ktor.openapigen.OpenAPIGen
4+
import com.papsign.ktor.openapigen.annotations.parameters.HeaderParam
45
import com.papsign.ktor.openapigen.annotations.parameters.PathParam
56
import com.papsign.ktor.openapigen.annotations.parameters.QueryParam
67
import com.papsign.ktor.openapigen.annotations.parameters.apiParam
@@ -11,6 +12,7 @@ import com.papsign.ktor.openapigen.modules.ModuleProvider
1112
import com.papsign.ktor.openapigen.modules.ofClass
1213
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
1314
import com.papsign.ktor.openapigen.schema.builder.provider.FinalSchemaBuilderProviderModule
15+
import io.ktor.http.Headers
1416
import io.ktor.http.Parameters
1517
import io.ktor.util.toMap
1618
import kotlin.reflect.KFunction
@@ -21,8 +23,8 @@ import kotlin.reflect.full.withNullability
2123
class ModularParameterHander<T>(val parsers: Map<KParameter, Builder<*>>, val constructor: KFunction<T>) :
2224
ParameterHandler<T> {
2325

24-
override fun parse(parameters: Parameters): T {
25-
return constructor.callBy(parsers.mapValues { it.value.build(it.key.name!!, parameters.toMap()) })
26+
override fun parse(parameters: Parameters, headers: Headers): T {
27+
return constructor.callBy(parsers.mapValues { it.value.build(it.key.name!!, parameters.toMap() + headers.toMap()) })
2628
}
2729

2830
override fun getParameters(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List<ParameterModel<*>> {
@@ -40,6 +42,17 @@ class ModularParameterHander<T>(val parsers: Map<KParameter, Builder<*>>, val co
4042
}
4143
}
4244

45+
fun HeaderParam.createParam(param: KParameter): ParameterModel<*> {
46+
val parser = parsers[param]!!
47+
return createParam(param, apiParam.`in`) {
48+
it.description = description
49+
it.allowEmptyValue = allowEmptyValues
50+
it.deprecated = deprecated
51+
it.style = parser.style
52+
it.explode = parser.explode
53+
}
54+
}
55+
4356
fun QueryParam.createParam(param: KParameter): ParameterModel<*> {
4457
val parser = parsers[param]!!
4558
return createParam(param, apiParam.`in`) {
@@ -62,13 +75,14 @@ class ModularParameterHander<T>(val parsers: Map<KParameter, Builder<*>>, val co
6275
}
6376

6477
return constructor.parameters.map {
78+
it.findAnnotation<HeaderParam>()?.createParam(it) ?:
6579
it.findAnnotation<PathParam>()?.createParam(it) ?:
6680
it.findAnnotation<QueryParam>()?.createParam(it) ?:
6781
error("API routes with ${constructor.returnType} must have parameters annotated with one of ${paramAnnotationClasses.map { it.simpleName }}")
6882
}
6983
}
7084

7185
companion object {
72-
private val paramAnnotationClasses = hashSetOf(PathParam::class, QueryParam::class)
86+
private val paramAnnotationClasses = hashSetOf(HeaderParam::class, PathParam::class, QueryParam::class)
7387
}
7488
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package com.papsign.ktor.openapigen.parameters.handlers
22

33
import com.papsign.ktor.openapigen.modules.providers.ParameterProvider
4+
import io.ktor.http.Headers
45
import io.ktor.http.Parameters
56

67
interface ParameterHandler<T>: ParameterProvider {
7-
fun parse(parameters: Parameters): T
8+
fun parse(parameters: Parameters, headers: Headers): T
89
}

src/main/kotlin/com/papsign/ktor/openapigen/parameters/handlers/UnitParameterHandler.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ package com.papsign.ktor.openapigen.parameters.handlers
33
import com.papsign.ktor.openapigen.OpenAPIGen
44
import com.papsign.ktor.openapigen.model.operation.ParameterModel
55
import com.papsign.ktor.openapigen.modules.ModuleProvider
6+
import io.ktor.http.Headers
67
import io.ktor.http.Parameters
78

89
object UnitParameterHandler :
910
ParameterHandler<Unit> {
10-
override fun parse(parameters: Parameters) = Unit
11+
override fun parse(parameters: Parameters, headers: Headers) = Unit
1112
override fun getParameters(apiGen: OpenAPIGen, provider: ModuleProvider<*>): List<ParameterModel<*>> {
1213
return listOf()
1314
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.header.simple
2+
3+
import com.papsign.ktor.openapigen.parameters.HeaderParamStyle
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
5+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelector
6+
import com.papsign.ktor.openapigen.parameters.parsers.converters.Converter
7+
import com.papsign.ktor.openapigen.parameters.parsers.converters.ConverterFactory
8+
import kotlin.reflect.KType
9+
10+
class SimpleBuilder(val type: KType, override val explode: Boolean): Builder<HeaderParamStyle> {
11+
override val style: HeaderParamStyle = HeaderParamStyle.simple
12+
private val converter: Converter = ConverterFactory.buildConverterForced(type)
13+
14+
override fun build(key: String, parameters: Map<String, List<String>>): Any? {
15+
val value = parameters[key]?.let { it[0] } ?: return null
16+
val adjusted = if (explode) value.replace('=', ',') else value
17+
return converter.convert(adjusted)
18+
}
19+
20+
companion object: BuilderSelector<SimpleBuilder> {
21+
override fun canHandle(type: KType, explode: Boolean): Boolean {
22+
return true
23+
}
24+
25+
override fun create(type: KType, explode: Boolean): SimpleBuilder {
26+
return SimpleBuilder(type, explode)
27+
}
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.papsign.ktor.openapigen.parameters.parsers.builders.header.simple
2+
3+
import com.papsign.ktor.openapigen.parameters.HeaderParamStyle
4+
import com.papsign.ktor.openapigen.parameters.parsers.builders.Builder
5+
import com.papsign.ktor.openapigen.parameters.parsers.builders.BuilderSelectorFactory
6+
7+
object SimpleBuilderFactory: BuilderSelectorFactory<Builder<HeaderParamStyle>, HeaderParamStyle>(
8+
SimpleBuilder
9+
)

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

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

3+
import com.papsign.ktor.openapigen.annotations.parameters.HeaderParam
34
import com.papsign.ktor.openapigen.annotations.parameters.PathParam
45
import com.papsign.ktor.openapigen.annotations.parameters.QueryParam
56
import com.papsign.ktor.openapigen.parameters.handlers.ModularParameterHander
@@ -18,6 +19,7 @@ inline fun <reified T : Any> buildParameterHandler(): ParameterHandler<T> {
1819
val constructor = t.primaryConstructor ?: error("API routes with ${t.simpleName} must have a primary constructor.")
1920
val parsers: Map<KParameter, Builder<*>> = constructor.parameters.associateWith { param ->
2021
val type = param.type
22+
param.findAnnotation<HeaderParam>()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?:
2123
param.findAnnotation<PathParam>()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?:
2224
param.findAnnotation<QueryParam>()?.let { a -> a.style.factory.buildBuilderForced(type, a.explode) } ?:
2325
error("Parameters must be annotated with @PathParam or @QueryParam")

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,15 @@ abstract class OpenAPIRoute<T : OpenAPIRoute<T>>(val ktorRoute: Route, val provi
5151
accept(acceptType) {
5252
if (Unit is B) {
5353
handle {
54-
val params: P = if (Unit is P) Unit else parameterHandler.parse(call.parameters)
54+
val params: P = if (Unit is P) Unit else parameterHandler.parse(call.parameters, call.request.headers)
5555
pass(this, responder, PHandler.handle(params), Unit)
5656
}
5757
} else {
5858
getContentTypesMap(B::class).forEach { (contentType, parsers) ->
5959
contentType(contentType) {
6060
handle {
6161
val receive: B = parsers.getBodyParser(call.request.contentType()).parseBody(B::class, this)
62-
val params: P = if (Unit is P) Unit else parameterHandler.parse(call.parameters)
62+
val params: P = if (Unit is P) Unit else parameterHandler.parse(call.parameters, call.request.headers)
6363
pass(this, responder, PHandler.handle(params), BHandler.handle(receive))
6464
}
6565
}

src/test/kotlin/TestServer.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import com.papsign.ktor.openapigen.OpenAPIGen
1212
import com.papsign.ktor.openapigen.annotations.Path
1313
import com.papsign.ktor.openapigen.annotations.Request
1414
import com.papsign.ktor.openapigen.annotations.Response
15+
import com.papsign.ktor.openapigen.annotations.parameters.HeaderParam
1516
import com.papsign.ktor.openapigen.annotations.parameters.PathParam
1617
import com.papsign.ktor.openapigen.annotations.type.`object`.example.ExampleProvider
1718
import com.papsign.ktor.openapigen.annotations.type.`object`.example.WithExample
@@ -148,6 +149,15 @@ object TestServer {
148149
respond(StringResponse(params.a))
149150
}
150151

152+
route("header") {
153+
get<NameParam, NameGreetingResponse>(
154+
info("Header Param Endpoint", "This is a Header Param Endpoint"),
155+
example = NameGreetingResponse("Hi, openapi!")
156+
) { params ->
157+
respond(NameGreetingResponse("Hi, ${params.`X-Name`}!"))
158+
}
159+
}
160+
151161
route("list") {
152162
get<StringParam, List<StringResponse>>(
153163
info("String Param Endpoint", "This is a String Param Endpoint"),
@@ -213,6 +223,11 @@ object TestServer {
213223
@Response("A String Response")
214224
data class StringResponse(val str: String)
215225

226+
data class NameParam(@HeaderParam("A simple Header Param") val `X-Name`: String)
227+
228+
@Response("A Response for header param example")
229+
data class NameGreetingResponse(val str: String)
230+
216231

217232
@Response("A String Response")
218233
@Request("A String Request")

0 commit comments

Comments
 (0)