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

io.javalin.plugin.openapi.dsl.openApiBuilderDsl.kt Maven / Gradle / Ivy

The newest version!
/**
 * This file contains helper methods which convert javalin concepts
 * to OpenApi documentation.
 */
package io.javalin.plugin.openapi.dsl

import io.javalin.core.*
import io.javalin.core.routing.PathSegment
import io.javalin.core.event.HandlerMetaInfo
import io.javalin.core.routing.flattenMultipleSegments
import io.javalin.http.HandlerType
import io.javalin.plugin.openapi.CreateSchemaOptions
import io.javalin.plugin.openapi.JavalinOpenApi
import io.javalin.plugin.openapi.annotations.HttpMethod
import io.javalin.plugin.openapi.annotations.PathInfo
import io.javalin.plugin.openapi.external.schema
import io.swagger.v3.oas.models.Components
import io.swagger.v3.oas.models.Operation
import io.swagger.v3.oas.models.PathItem
import io.swagger.v3.oas.models.Paths
import org.slf4j.LoggerFactory
import java.util.*

private val logger = LoggerFactory.getLogger(JavalinOpenApi::class.java)

fun overridePaths(
        handlerMetaInfoList: List,
        overridenPaths: List
): List {
    return overridenPaths.plus(handlerMetaInfoList.filter { handler ->
        overridenPaths.none { overridenHandler ->
            PathParser(
                overridenHandler.path,
                true
            ).matches(handler.path) && overridenHandler.httpMethod == handler.httpMethod
        }
    })
}

fun Components.applyMetaInfoList(handlerMetaInfoList: List, options: CreateSchemaOptions) {
    handlerMetaInfoList
            .map { it.extractDocumentation(options) }
            .filter { it.isIgnored != true }
            .flatMap { it.componentsUpdaterList }
            .applyAllUpdates(this)
}

fun Paths.applyMetaInfoList(handlerMetaInfoList: List, options: CreateSchemaOptions) {
    handlerMetaInfoList
            .filter { it.extractDocumentation(options).isIgnored != true }
            .groupBy { it.path }
            .forEach { (url, metaInfos) ->
                val pathParser = PathParser(url, true)
                updatePath(pathParser.getOpenApiUrl()) {
                    applyMetaInfoList(options, pathParser, metaInfos)
                }
            }
}

internal fun PathParser.getOpenApiUrl(): String {
    val segmentsString = segments.joinToString("/") { it.asOpenApiUrlPart() }
    return "/$segmentsString"
}

private fun PathSegment.asOpenApiUrlPart(): String {
    return when (this) {
        is PathSegment.Normal -> this.content
        /*
         * At the moment, OpenApi does not support wildcards. So we just leave it as it is.
         * Once it is implemented we can change this.
         */
        is PathSegment.Wildcard -> "*"
        is PathSegment.Parameter -> "{${this.name}}"
        is PathSegment.MultipleSegments -> this.innerSegments.joinToString("") { it.asOpenApiUrlPart() }
    }
}

fun PathItem.applyMetaInfoList(
        options: CreateSchemaOptions,
        path: PathParser,
        handlerMetaInfoList: List
) {
    handlerMetaInfoList
            .forEach { metaInfo ->
                val pathItemHttpMethod = metaInfo.httpMethod.asPathItemHttpMethod() ?: return@forEach
                operation(pathItemHttpMethod, Operation().apply {
                    applyMetaInfo(options, path, metaInfo)
                })
            }
}

fun Operation.applyMetaInfo(options: CreateSchemaOptions, path: PathParser, metaInfo: HandlerMetaInfo) {
    val documentation = metaInfo.extractDocumentation(options)

    operationId = metaInfo.createDefaultOperationId(path)
    summary = metaInfo.createDefaultSummary(path)
    if (path.pathParamNames.isNotEmpty()) {
        path.pathParamNames.forEach { pathParamName ->
            updateParameter {
                name = pathParamName
                `in` = "path"
                required = true
                schema(String::class.java)
            }
        }
    }

    documentation.parameterUpdaterListMapping
            .values
            .forEach { updaters ->
                updateParameter { updaters.applyAllUpdates(this) }
            }

    if (documentation.hasRequestBodies()) {
        updateRequestBody {
            documentation.requestBodyList.applyAllUpdates(this)
        }
    }

    if (documentation.hasFormParameter() || documentation.hasFileUploads()) {
        updateRequestBody {
            applyDocumentedFormParameters(documentation.formParameterList, documentation.fileUploadList)
        }
    }

    if (documentation.hasResponses()) {
        updateResponses {
            documentation.responseUpdaterListMapping
                    .forEach { (name, updater) ->
                        updateResponse(name) {
                            updater.applyAllUpdates(this)
                        }
                    }
        }
    }

    documentation.operationUpdaterList.applyAllUpdates(this)
}

fun Paths.ensureDefaultResponse() {
    forEach { url, path ->
        path.readOperationsMap()
                .filter { (_, operation) ->
                    operation.responses.isNullOrEmpty()
                }
                .forEach { (method, operation) ->
                    operation.updateResponses {
                        updateResponse("200") {
                            this.description("Default response")
                        }
                    }

                    logger.warn(
                            "A default response was added to the documentation of $method $url"
                    )
                }
    }
}

val HandlerMetaInfo.pathInfo: PathInfo?
    get() = httpMethod.asAnnotationHttpMethod()?.let { method ->
        PathInfo(path, method)
    }

fun HandlerType.asAnnotationHttpMethod(): HttpMethod? = when (this) {
    HandlerType.GET -> HttpMethod.GET
    HandlerType.PUT -> HttpMethod.PUT
    HandlerType.POST -> HttpMethod.POST
    HandlerType.DELETE -> HttpMethod.DELETE
    HandlerType.OPTIONS -> HttpMethod.OPTIONS
    HandlerType.HEAD -> HttpMethod.HEAD
    HandlerType.PATCH -> HttpMethod.PATCH
    HandlerType.TRACE -> HttpMethod.TRACE
    else -> null
}

fun HandlerType.asPathItemHttpMethod(): PathItem.HttpMethod? = when (this) {
    HandlerType.GET -> PathItem.HttpMethod.GET
    HandlerType.PUT -> PathItem.HttpMethod.PUT
    HandlerType.POST -> PathItem.HttpMethod.POST
    HandlerType.DELETE -> PathItem.HttpMethod.DELETE
    HandlerType.OPTIONS -> PathItem.HttpMethod.OPTIONS
    HandlerType.HEAD -> PathItem.HttpMethod.HEAD
    HandlerType.PATCH -> PathItem.HttpMethod.PATCH
    HandlerType.TRACE -> PathItem.HttpMethod.TRACE
    else -> null
}

private fun HandlerMetaInfo.createDefaultOperationId(path: PathParser): String {
    val metaInfo = this
    val lowerCaseMethod = metaInfo.httpMethod.toString().lowercase(Locale.ROOT)
    val capitalizedPath = path.asReadableWords().joinToString("") { it.capitalize() }
    return lowerCaseMethod + capitalizedPath
}

private fun HandlerMetaInfo.createDefaultSummary(path: PathParser): String {
    val metaInfo = this
    val capitalizedMethod = metaInfo.httpMethod.toString().lowercase(Locale.ROOT).capitalize()
    return (listOf(capitalizedMethod) + path.asReadableWords()).joinToString(" ") { it.trim() }
}

private fun PathParser.asReadableWords(): List {
    val words = mutableListOf()
    segments.flattenMultipleSegments().forEach { segment ->
        when (segment) {
            is PathSegment.Normal -> words.add(segment.content.dashCaseToCamelCase())
            is PathSegment.Wildcard -> words.addAll(arrayOf("with", "wildcard"))
            is PathSegment.Parameter -> {
                words.add("with")
                words.add(segment.name.dashCaseToCamelCase())
            }
        }
    }
    return words
}

private fun String.dashCaseToCamelCase() = split("-")
        .map { it.lowercase(Locale.ROOT) }
        .mapIndexed { index, s -> if (index > 0) s.capitalize() else s }
        .joinToString("")




© 2015 - 2025 Weber Informatics LLC | Privacy Policy