All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.papsign.ktor.openapigen.route.OpenAPIRoute.kt Maven / Gradle / Ivy

package com.papsign.ktor.openapigen.route

import com.papsign.ktor.openapigen.classLogger
import com.papsign.ktor.openapigen.content.type.*
import com.papsign.ktor.openapigen.content.type.ktor.KtorContentProvider
import com.papsign.ktor.openapigen.exceptions.OpenAPINoParserException
import com.papsign.ktor.openapigen.exceptions.OpenAPINoSerializerException
import com.papsign.ktor.openapigen.modules.CachingModuleProvider
import com.papsign.ktor.openapigen.modules.OpenAPIModule
import com.papsign.ktor.openapigen.modules.ofType
import com.papsign.ktor.openapigen.modules.openapi.HandlerModule
import com.papsign.ktor.openapigen.openAPIGen
import com.papsign.ktor.openapigen.parameters.handlers.ParameterHandler
import com.papsign.ktor.openapigen.parameters.util.buildParameterHandler
import com.papsign.ktor.openapigen.route.response.Responder
import com.papsign.ktor.openapigen.validation.ValidationHandler
import io.ktor.http.ContentType
import io.ktor.server.application.ApplicationCall
import io.ktor.server.application.call
import io.ktor.server.request.contentType
import io.ktor.server.routing.Route
import io.ktor.server.routing.accept
import io.ktor.server.routing.application
import io.ktor.server.routing.contentType
import io.ktor.util.pipeline.PipelineContext
import io.ktor.util.reflect.TypeInfo
import kotlin.reflect.KType
import kotlin.reflect.KTypeProjection
import kotlin.reflect.KVariance
import kotlin.reflect.full.createType

abstract class OpenAPIRoute>(val ktorRoute: Route, val provider: CachingModuleProvider) {
    private val log = classLogger()

    abstract fun child(route: Route = this.ktorRoute): T

    fun 

handle( paramsType: KType, responseType: KType, bodyType: KType, pass: suspend OpenAPIRoute<*>.(pipeline: PipelineContext, responder: Responder, P, B) -> Unit ) { val parameterHandler = buildParameterHandler

(paramsType) provider.registerModule(parameterHandler, ParameterHandler::class.createType(listOf(KTypeProjection(KVariance.INVARIANT, paramsType)))) val apiGen = application.openAPIGen provider.ofType().forEach { it.configure(apiGen, provider) } val BHandler = ValidationHandler.build(bodyType) val PHandler = ValidationHandler.build(paramsType) ktorRoute.apply { getAcceptMap(responseType).let { if (it.isNotEmpty()) it else listOf(ContentType.Any to listOf(SelectedSerializer(KtorContentProvider))) }.forEach { (acceptType, serializers) -> val responder = ContentTypeResponder(serializers.getResponseSerializer(acceptType), acceptType) accept(acceptType) { if (bodyType.classifier == Unit::class) { handle { @Suppress("UNCHECKED_CAST") val params: P = if (paramsType.classifier == Unit::class) Unit as P else parameterHandler.parse(call.parameters, call.request.headers) @Suppress("UNCHECKED_CAST") pass(this, responder, PHandler.handle(params), Unit as B) } } else { getContentTypesMap(bodyType).forEach { (contentType, parsers) -> contentType(contentType) { handle { val receive: B = parsers.getBodyParser(call.request.contentType()).parseBody(bodyType, this) @Suppress("UNCHECKED_CAST") val params: P = if (paramsType.classifier == Unit::class) Unit as P else parameterHandler.parse(call.parameters, call.request.headers) pass(this, responder, PHandler.handle(params), BHandler.handle(receive)) } } } } } } } } fun List.getResponseSerializer(contentType: ContentType): ResponseSerializer { if (size > 1) log.warn("Multiple equal serializers for Accept $contentType: ${map { it.module::class.simpleName }}, selecting first ${first().module::class.simpleName}") return firstOrNull()?.module ?: throw OpenAPINoSerializerException(contentType) } fun List.getBodyParser(contentType: ContentType): BodyParser { if (size > 1) log.warn("Multiple equal parsers for Content-Type $contentType: ${map { it.module::class.simpleName }}, selecting first ${first().module::class.simpleName}") return firstOrNull()?.module ?: throw OpenAPINoParserException(contentType) } fun getContentTypesMap(type: KType) = mapContentTypes { module.getParseableContentTypes(type) } fun getAcceptMap(type: KType) = mapContentTypes { module.getSerializableContentTypes(type) } inline fun mapContentTypes(noinline fn: T.() -> List): List>> { return provider.ofType().flatMap { parser -> parser.fn().map { Pair(it, parser) } }.groupBy { it.first }.mapValues { it.value.map { it.second } }.map { Pair(it.key, it.value) }.sortedBy { val ct = it.first when { ct.contentSubtype != "*" -> 1000 ct.contentType != "*" -> 10000 else -> 100000 } - ct.parameters.size // edge case already, no need to sort by potential wildcards too, if you do this you are already looking for problems } } } val OpenAPIRoute<*>.application get() = ktorRoute.application