Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.lightningkite.lightningserver.jsonschema.OpenApi.kt Maven / Gradle / Ivy
Go to download
A set of tools to fill in/replace what Ktor is lacking in.
package com.lightningkite.lightningserver.jsonschema
import com.lightningkite.lightningserver.core.ContentType
import com.lightningkite.lightningserver.http.HttpMethod
import com.lightningkite.lightningserver.humanize
import com.lightningkite.lightningserver.serialization.Serialization
import com.lightningkite.lightningserver.serialization.encodeToFormData
import com.lightningkite.lightningserver.settings.generalSettings
import com.lightningkite.lightningserver.typed.ApiEndpoint
import com.lightningkite.lightningserver.typed.Documentable
import com.lightningkite.lightningserver.typed.docGroup
import com.lightningkite.lightningserver.typed.functionName
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonPrimitive
@Serializable
data class OpenApiRoot(
val openapi: String,
val info: OpenApiInfo = OpenApiInfo(),
val paths: Map = mapOf(),
val components: OpenApiComponents = OpenApiComponents(),
val servers: List = listOf(),
val security: List>> = listOf(),
)
@Serializable
data class OpenApiSecurity(
val type: OpenApiSecurityType = OpenApiSecurityType.apiKey,
val description: String? = null,
val name: String? = null,
@SerialName("in") val inside: OpenApiParameterType? = null,
val scheme: String? = null,
val bearerFormat: String? = null,
)
@Serializable
enum class OpenApiSecurityType {
apiKey, http, oauth2, openIdConnect
}
@Serializable
data class OpenApiServer(
val url: String = "",
val description: String = "",
)
@Serializable
data class OpenApiComponents(
val schemas: Map = mapOf(),
val securitySchemes: Map = mapOf(),
)
@Serializable
data class OpenApiPath(
val parameters: List = listOf(),
val get: OpenApiOperation? = null,
val post: OpenApiOperation? = null,
val put: OpenApiOperation? = null,
val patch: OpenApiOperation? = null,
val delete: OpenApiOperation? = null,
)
@Serializable
data class OpenApiOperation(
val summary: String = "",
val description: String = "",
val tags: List = listOf(),
val operationId: String = "",
val parameters: List = listOf(),
val requestBody: OpenApiRequestBody? = null,
val responses: Map = mapOf(),
)
@Serializable
data class OpenApiRequestBody(
val description: String? = null,
val content: Map = mapOf(),
val required: Boolean = false,
)
@Serializable
data class OpenApiMediaType(
val schema: JsonSchemaType,
val example: JsonElement = JsonNull,
val examples: Map = mapOf(),
)
@Serializable
data class OpenApiExample(
val summary: String = "",
val description: String = "",
val value: JsonElement = JsonNull,
)
@Serializable
data class OpenApiResponse(
val description: String = "",
val content: Map = mapOf(),
)
@Serializable
data class OpenApiParameter(
val name: String,
@SerialName("in") val inside: OpenApiParameterType = OpenApiParameterType.cookie,
val schema: JsonSchemaType = JsonSchemaType(),
val description: String = "",
val required: Boolean = false,
val allowEmptyValue: Boolean = false,
)
@Serializable
enum class OpenApiParameterType {
query, header, path, cookie
}
@Serializable
data class OpenApiInfo(
val title: String = "",
val version: String = "",
val description: String? = null,
val termsOfService: String? = null,
val contact: OpenApiContact? = null,
val license: OpenApiLicense? = null,
)
@Serializable
data class OpenApiLicense(
val name: String = "",
val url: String = "",
)
@Serializable
data class OpenApiContact(
val name: String = "",
val url: String = "",
val email: String = "",
)
private fun make(type: KSerializer, item: T): Map {
return mapOf(
"application/json" to OpenApiExample(
value = Serialization.json.encodeToJsonElement(type, item)
),
// "text/csv" to OpenApiExample(
// value = JsonPrimitive(Serialization.csv.encodeToString(type, item))
// ),
// ContentType.Application.FormUrlEncoded.toString() to OpenApiExample(
// value = JsonPrimitive(Serialization.properties.encodeToFormData(type, item))
// )
)
}
private fun ApiEndpoint.openApi(builder: JsonSchemaBuilder): OpenApiOperation =
OpenApiOperation(
summary = (this.docGroup?.let { it.humanize() + " " } ?: "") + " - " + summary,
description = description,
tags = listOfNotNull(this.docGroup),
operationId = (this.docGroup ?: "") + "_" + this.functionName,
parameters = listOf(),
requestBody = if (this.route.method == HttpMethod.GET) null else if (this.inputType == Unit.serializer()) null else OpenApiRequestBody(
content = mapOf(
ContentType.Application.Json.toString() to OpenApiMediaType(
schema = builder[this.inputType.descriptor],
example = examples.firstOrNull()
?.let { example -> Serialization.json.encodeToJsonElement(inputType, example.input) }
?: JsonNull,
// examples = examples.groupBy { it.name }.flatMap {
// if (it.value.size == 1) it.value else it.value.mapIndexed { index, it ->
// it.copy(
// name = it.name + " " + index.plus(1)
// )
// }
// }.associate { example ->
// example.name to OpenApiExample(
// example.name,
// value = Serialization.json.encodeToJsonElement(inputType, example.input)
// )
// }
)
),
required = true
),
responses = mapOf(
successCode.code.toString() to (if (this.outputType == Unit.serializer()) OpenApiResponse(
"Success",
mapOf()
) else OpenApiResponse(
description = "Success",
content = mapOf(
ContentType.Application.Json.toString() to OpenApiMediaType(
schema = builder[this.outputType.descriptor],
example = examples.firstOrNull()
?.let { example -> Serialization.json.encodeToJsonElement(outputType, example.output) }
?: JsonNull,
// examples = examples.groupBy { it.name }.flatMap {
// if (it.value.size == 1) it.value else it.value.mapIndexed { index, it ->
// it.copy(name = it.name + " " + index.plus(1))
// }
// }.associate { example ->
// example.name to OpenApiExample(
// example.name,
// value = Serialization.json.encodeToJsonElement(outputType, example.output)
// )
// }
)
)
))
// TODO: Error codes
)
)
val openApiDescription: OpenApiRoot by lazy {
val builder = JsonSchemaBuilder(Serialization.json, "#/components/schemas/", useNullableProperty = true)
Documentable.endpoints.flatMap {
sequenceOf(it.inputType, it.outputType) + it.routeTypes.values.asSequence()
}.distinct().forEach { builder.get(it.descriptor) }
OpenApiRoot(
openapi = "3.0.2",
info = OpenApiInfo(
title = generalSettings().projectName,
version = "current",
),
components = OpenApiComponents(
schemas = builder.definitions,
securitySchemes = mapOf(
"header" to OpenApiSecurity(
type = OpenApiSecurityType.http,
description = "Authorization Header",
scheme = "bearer",
bearerFormat = "JWT",
),
"param" to OpenApiSecurity(
type = OpenApiSecurityType.apiKey,
description = "Parameter",
name = "jwt",
inside = OpenApiParameterType.query
),
"cookie" to OpenApiSecurity(
type = OpenApiSecurityType.apiKey,
description = "Cookie",
name = "Authorization",
inside = OpenApiParameterType.cookie
)
)
),
servers = listOf(
OpenApiServer(url = generalSettings().publicUrl, description = "Current Server")
),
security = listOf(
mapOf(),
mapOf("header" to listOf()),
mapOf("param" to listOf()),
mapOf("cookie" to listOf()),
),
paths = Documentable.endpoints.filter { it.route.method != HttpMethod.GET || it.inputType == Unit.serializer() }
.groupBy {
it.path.toString()
}.mapValues {
OpenApiPath(
parameters = it.value.first().routeTypes.map {
OpenApiParameter(
name = it.key,
inside = OpenApiParameterType.path,
description = it.key,
required = true,
schema = builder[it.value.descriptor],
allowEmptyValue = false
)
},
get = it.value.find { it.route.method == HttpMethod.GET }?.openApi(builder),
post = it.value.find { it.route.method == HttpMethod.POST }?.openApi(builder),
put = it.value.find { it.route.method == HttpMethod.PUT }?.openApi(builder),
patch = it.value.find { it.route.method == HttpMethod.PATCH }?.openApi(builder),
delete = it.value.find { it.route.method == HttpMethod.DELETE }?.openApi(builder),
)
}
)
}