feat: Multi Serialization Support (#134)
This commit is contained in:
@ -6,6 +6,8 @@
|
||||
- Support for including parameter examples via `MethodInfo`
|
||||
|
||||
### Changed
|
||||
- Kompendium now leverages the chosen API serializer. Supports Jackson, Gson and Kotlinx Serialization
|
||||
- Fixed bug where overridden field names were not reflected in serialized object and required array
|
||||
|
||||
### Remove
|
||||
|
||||
|
@ -4,18 +4,18 @@ plugins {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// VERSIONS
|
||||
val ktorVersion: String by project
|
||||
val kotestVersion: String by project
|
||||
|
||||
// IMPLEMENTATION
|
||||
|
||||
api(projects.kompendiumOas)
|
||||
api(projects.kompendiumAnnotations)
|
||||
|
||||
val ktorVersion: String by project
|
||||
val kotestVersion: String by project
|
||||
implementation(group = "io.ktor", name = "ktor-server-core", version = ktorVersion)
|
||||
implementation(group = "io.ktor", name = "ktor-html-builder", version = ktorVersion)
|
||||
|
||||
implementation(group = "com.fasterxml.jackson.module", name = "jackson-module-kotlin", version = "2.13.0")
|
||||
|
||||
// TEST FIXTURES
|
||||
|
||||
testFixturesApi(group = "io.kotest", name = "kotest-runner-junit5-jvm", version = kotestVersion)
|
||||
|
@ -1,8 +1,5 @@
|
||||
package io.bkbn.kompendium.core
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import io.bkbn.kompendium.core.metadata.SchemaMap
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.schema.TypedSchema
|
||||
@ -12,7 +9,7 @@ import io.ktor.application.ApplicationFeature
|
||||
import io.ktor.application.call
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.request.path
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.util.AttributeKey
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
@ -28,13 +25,6 @@ class Kompendium(val config: Configuration) {
|
||||
fun addCustomTypeSchema(clazz: KClass<*>, schema: TypedSchema) {
|
||||
cache = cache.plus(clazz.simpleName!! to schema)
|
||||
}
|
||||
|
||||
// TODO Add tests for this!!
|
||||
var om: ObjectMapper = ObjectMapper()
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.enable(SerializationFeature.INDENT_OUTPUT)
|
||||
|
||||
fun specToJson(): String = om.writeValueAsString(spec)
|
||||
}
|
||||
|
||||
companion object Feature : ApplicationFeature<Application, Configuration, Kompendium> {
|
||||
@ -44,8 +34,7 @@ class Kompendium(val config: Configuration) {
|
||||
|
||||
pipeline.intercept(ApplicationCallPipeline.Call) {
|
||||
if (call.request.path() == configuration.specRoute) {
|
||||
call.respondText { configuration.specToJson() }
|
||||
call.response.status(HttpStatusCode.OK)
|
||||
call.respond(HttpStatusCode.OK, configuration.spec)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -199,8 +199,13 @@ object Kontent {
|
||||
logger.debug("$slug contains $fieldMap")
|
||||
var schema = ObjectSchema(fieldMap.plus(undeclaredFieldMap))
|
||||
val requiredParams = clazz.primaryConstructor?.parameters?.filterNot { it.isOptional } ?: emptyList()
|
||||
// todo de-dup this logic
|
||||
if (requiredParams.isNotEmpty()) {
|
||||
schema = schema.copy(required = requiredParams.map { it.name!! })
|
||||
schema = schema.copy(required = requiredParams.map { param ->
|
||||
clazz.memberProperties.first { it.name == param.name }.findAnnotation<Field>()
|
||||
?.let { field -> field.name.ifBlank { param.name!! } }
|
||||
?: param.name!!
|
||||
})
|
||||
}
|
||||
logger.debug("$slug schema: $schema")
|
||||
newCache.plus(slug to schema)
|
||||
@ -335,8 +340,13 @@ object Kontent {
|
||||
val requiredParams = clazz.primaryConstructor?.parameters?.filterNot { it.isOptional } ?: emptyList()
|
||||
var schema = this
|
||||
|
||||
// todo dedup this
|
||||
if (requiredParams.isNotEmpty()) {
|
||||
schema = schema.copy(required = requiredParams.map { it.name!! })
|
||||
schema = schema.copy(required = requiredParams.map { param ->
|
||||
clazz.memberProperties.first { it.name == param.name }.findAnnotation<Field>()
|
||||
?.let { field -> field.name.ifBlank { param.name!! } }
|
||||
?: param.name!!
|
||||
})
|
||||
}
|
||||
|
||||
if (prop.returnType.isMarkedNullable) {
|
||||
|
@ -72,12 +72,12 @@ object MethodParser {
|
||||
responseType: KType,
|
||||
responseInfo: ResponseInfo<*>?,
|
||||
feature: Kompendium
|
||||
): Map<Int, Response<*>> = responseType.toResponseSpec(responseInfo, feature)?.let { mapOf(it) }.orEmpty()
|
||||
): Map<Int, Response> = responseType.toResponseSpec(responseInfo, feature)?.let { mapOf(it) }.orEmpty()
|
||||
|
||||
private fun parseExceptions(
|
||||
exceptionInfo: Set<ExceptionInfo<*>>,
|
||||
feature: Kompendium,
|
||||
): Map<Int, Response<*>> = exceptionInfo.associate { info ->
|
||||
): Map<Int, Response> = exceptionInfo.associate { info ->
|
||||
feature.config.cache = generateKontent(info.responseType, feature.config.cache)
|
||||
val response = Response(
|
||||
description = info.description,
|
||||
@ -92,13 +92,14 @@ object MethodParser {
|
||||
* @param requestInfo request metadata
|
||||
* @return Will return a generated [Request] if requestInfo is not null
|
||||
*/
|
||||
private fun KType.toRequestSpec(requestInfo: RequestInfo<*>?, feature: Kompendium): Request<*>? =
|
||||
private fun KType.toRequestSpec(requestInfo: RequestInfo<*>?, feature: Kompendium): Request? =
|
||||
when (requestInfo) {
|
||||
null -> null
|
||||
else -> {
|
||||
Request(
|
||||
description = requestInfo.description,
|
||||
content = feature.resolveContent(this, requestInfo.mediaTypes, requestInfo.examples) ?: mapOf(),
|
||||
content = feature.resolveContent(this, requestInfo.mediaTypes, requestInfo.examples as Map<String, Any>)
|
||||
?: mapOf(),
|
||||
required = requestInfo.required
|
||||
)
|
||||
}
|
||||
@ -110,13 +111,13 @@ object MethodParser {
|
||||
* @param responseInfo response metadata
|
||||
* @return Will return a generated [Pair] if responseInfo is not null
|
||||
*/
|
||||
private fun KType.toResponseSpec(responseInfo: ResponseInfo<*>?, feature: Kompendium): Pair<Int, Response<*>>? =
|
||||
private fun KType.toResponseSpec(responseInfo: ResponseInfo<*>?, feature: Kompendium): Pair<Int, Response>? =
|
||||
when (responseInfo) {
|
||||
null -> null
|
||||
else -> {
|
||||
val specResponse = Response(
|
||||
description = responseInfo.description,
|
||||
content = feature.resolveContent(this, responseInfo.mediaTypes, responseInfo.examples)
|
||||
content = feature.resolveContent(this, responseInfo.mediaTypes, responseInfo.examples as Map<String, Any>)
|
||||
)
|
||||
Pair(responseInfo.status.value, specResponse)
|
||||
}
|
||||
@ -129,11 +130,11 @@ object MethodParser {
|
||||
* @param examples Mapping of named examples of valid bodies.
|
||||
* @return Named mapping of media types.
|
||||
*/
|
||||
private fun <F> Kompendium.resolveContent(
|
||||
private fun Kompendium.resolveContent(
|
||||
type: KType,
|
||||
mediaTypes: List<String>,
|
||||
examples: Map<String, F>
|
||||
): Map<String, MediaType<F>>? {
|
||||
examples: Map<String, Any>
|
||||
): Map<String, MediaType>? {
|
||||
val classifier = type.classifier as KClass<*>
|
||||
return if (type != Helpers.UNIT_TYPE && mediaTypes.isNotEmpty()) {
|
||||
mediaTypes.associateWith {
|
||||
|
@ -80,7 +80,7 @@
|
||||
},
|
||||
"required": [
|
||||
"org",
|
||||
"amazingField",
|
||||
"amazing_field",
|
||||
"tables"
|
||||
],
|
||||
"type": "object"
|
||||
|
@ -62,7 +62,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fieldName",
|
||||
"field_name",
|
||||
"b",
|
||||
"aaa"
|
||||
],
|
||||
|
@ -45,7 +45,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"b"
|
||||
"real_name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
|
@ -82,7 +82,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fieldName",
|
||||
"field_name",
|
||||
"b",
|
||||
"aaa"
|
||||
],
|
||||
|
@ -82,7 +82,7 @@
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"fieldName",
|
||||
"field_name",
|
||||
"b",
|
||||
"aaa"
|
||||
],
|
||||
|
@ -1,3 +1,8 @@
|
||||
plugins {
|
||||
id("io.bkbn.sourdough.library")
|
||||
kotlin("plugin.serialization") version "1.6.0"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.1")
|
||||
}
|
||||
|
@ -6,7 +6,9 @@ import io.bkbn.kompendium.oas.component.Components
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.path.Path
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OpenApiSpec(
|
||||
val openapi: String = "3.0.3",
|
||||
val info: Info,
|
||||
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.common
|
||||
|
||||
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
@Serializable
|
||||
data class ExternalDocumentation(
|
||||
@Serializable(with = UriSerializer::class)
|
||||
val url: URI,
|
||||
val description: String?
|
||||
)
|
||||
|
@ -1,5 +1,8 @@
|
||||
package io.bkbn.kompendium.oas.common
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Tag(
|
||||
val name: String,
|
||||
val description: String? = null,
|
||||
|
@ -1,7 +1,9 @@
|
||||
package io.bkbn.kompendium.oas.component
|
||||
|
||||
import io.bkbn.kompendium.oas.security.SecuritySchema
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Components(
|
||||
val securitySchemes: MutableMap<String, SecuritySchema> = mutableMapOf()
|
||||
)
|
||||
|
@ -1,9 +1,13 @@
|
||||
package io.bkbn.kompendium.oas.info
|
||||
|
||||
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
@Serializable
|
||||
data class Contact(
|
||||
var name: String,
|
||||
@Serializable(with = UriSerializer::class)
|
||||
var url: URI? = null,
|
||||
var email: String? = null // TODO Enforce email?
|
||||
)
|
||||
|
@ -1,11 +1,15 @@
|
||||
package io.bkbn.kompendium.oas.info
|
||||
|
||||
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
@Serializable
|
||||
data class Info(
|
||||
var title: String? = null,
|
||||
var version: String? = null,
|
||||
var description: String? = null,
|
||||
@Serializable(with = UriSerializer::class)
|
||||
var termsOfService: URI? = null,
|
||||
var contact: Contact? = null,
|
||||
var license: License? = null
|
||||
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.info
|
||||
|
||||
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
@Serializable
|
||||
data class License(
|
||||
var name: String,
|
||||
@Serializable(with = UriSerializer::class)
|
||||
var url: URI? = null
|
||||
)
|
||||
|
@ -2,7 +2,9 @@ package io.bkbn.kompendium.oas.path
|
||||
|
||||
import io.bkbn.kompendium.oas.payload.Parameter
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Path(
|
||||
var get: PathOperation? = null,
|
||||
var put: PathOperation? = null,
|
||||
|
@ -6,7 +6,9 @@ import io.bkbn.kompendium.oas.payload.Payload
|
||||
import io.bkbn.kompendium.oas.payload.Request
|
||||
import io.bkbn.kompendium.oas.payload.Response
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class PathOperation(
|
||||
var tags: Set<String> = emptySet(),
|
||||
var summary: String? = null,
|
||||
@ -14,9 +16,9 @@ data class PathOperation(
|
||||
var externalDocs: ExternalDocumentation? = null,
|
||||
var operationId: String? = null,
|
||||
var parameters: List<Parameter>? = null,
|
||||
var requestBody: Request<*>? = null,
|
||||
var requestBody: Request? = null,
|
||||
// TODO How to enforce `default` requirement 🧐
|
||||
var responses: Map<Int, Response<*>>? = null,
|
||||
var responses: Map<Int, Response>? = null,
|
||||
var callbacks: Map<String, Payload>? = null, // todo what is this?
|
||||
var deprecated: Boolean = false,
|
||||
var security: List<Map<String, List<String>>>? = null,
|
||||
|
@ -1,10 +1,14 @@
|
||||
package io.bkbn.kompendium.oas.payload
|
||||
|
||||
import io.bkbn.kompendium.oas.schema.ComponentSchema
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
data class MediaType<T>(
|
||||
@Serializable
|
||||
data class MediaType(
|
||||
val schema: ComponentSchema,
|
||||
val examples: Map<String, Example<T>>? = null
|
||||
val examples: Map<String, Example>? = null
|
||||
) {
|
||||
data class Example<T>(val value: T)
|
||||
@Serializable
|
||||
data class Example(val value: @Contextual Any)
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package io.bkbn.kompendium.oas.payload
|
||||
|
||||
import io.bkbn.kompendium.oas.schema.ComponentSchema
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Parameter(
|
||||
val name: String,
|
||||
val `in`: String, // TODO Enum? "query", "header", "path" or "cookie"
|
||||
@ -14,5 +17,6 @@ data class Parameter(
|
||||
val explode: Boolean? = null,
|
||||
val examples: Map<String, Example>? = null
|
||||
) {
|
||||
data class Example(val value: Any)
|
||||
@Serializable
|
||||
data class Example(val value: @Contextual Any)
|
||||
}
|
||||
|
@ -1,7 +1,10 @@
|
||||
package io.bkbn.kompendium.oas.payload
|
||||
|
||||
data class Request<T>(
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Request(
|
||||
val description: String?,
|
||||
val content: Map<String, MediaType<T>>,
|
||||
val content: Map<String, MediaType>,
|
||||
val required: Boolean = false
|
||||
) : Payload
|
||||
|
@ -1,8 +1,11 @@
|
||||
package io.bkbn.kompendium.oas.payload
|
||||
|
||||
data class Response<T>(
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class Response(
|
||||
val description: String? = null,
|
||||
val headers: Map<String, Payload>? = null,
|
||||
val content: Map<String, MediaType<T>>? = null,
|
||||
val content: Map<String, MediaType>? = null,
|
||||
val links: Map<String, Payload>? = null
|
||||
) : Payload
|
||||
|
@ -1,3 +1,6 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class AnyOfSchema(val anyOf: List<ComponentSchema>, override val description: String? = null) : ComponentSchema
|
||||
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ArraySchema(
|
||||
val items: ComponentSchema,
|
||||
override val default: Any? = null,
|
||||
override val default: @Contextual Any? = null,
|
||||
override val description: String? = null,
|
||||
override val nullable: Boolean? = null,
|
||||
// constraints
|
||||
|
@ -1,5 +1,10 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.JsonClassDiscriminator
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@JsonClassDiscriminator("component_type") // todo figure out a way to filter this
|
||||
sealed interface ComponentSchema {
|
||||
val description: String?
|
||||
get() = null
|
||||
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class DictionarySchema(
|
||||
val additionalProperties: ComponentSchema,
|
||||
override val default: Any? = null,
|
||||
override val default: @Contextual Any? = null,
|
||||
override val description: String? = null,
|
||||
override val nullable: Boolean? = null
|
||||
) : TypedSchema {
|
||||
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class EnumSchema(
|
||||
val `enum`: Set<String>,
|
||||
override val default: Any? = null,
|
||||
override val default: @Contextual Any? = null,
|
||||
override val description: String? = null,
|
||||
override val nullable: Boolean? = null
|
||||
) : TypedSchema {
|
||||
|
@ -1,15 +1,23 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import io.bkbn.kompendium.oas.serialization.NumberSerializer
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class FormattedSchema(
|
||||
val format: String,
|
||||
override val type: String,
|
||||
override val default: Any? = null,
|
||||
override val default: @Contextual Any? = null,
|
||||
override val description: String? = null,
|
||||
override val nullable: Boolean? = null,
|
||||
// Constraints
|
||||
@Serializable(with = NumberSerializer::class)
|
||||
val minimum: Number? = null,
|
||||
@Serializable(with = NumberSerializer::class)
|
||||
val maximum: Number? = null,
|
||||
val exclusiveMinimum: Boolean? = null,
|
||||
val exclusiveMaximum: Boolean? = null,
|
||||
@Serializable(with = NumberSerializer::class)
|
||||
val multipleOf: Number? = null,
|
||||
) : TypedSchema
|
||||
|
@ -1,5 +1,9 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class FreeFormSchema(
|
||||
override val nullable: Boolean? = null,
|
||||
// constraints
|
||||
@ -8,5 +12,5 @@ data class FreeFormSchema(
|
||||
) : TypedSchema {
|
||||
val additionalProperties: Boolean = true
|
||||
override val type: String = "object"
|
||||
override val default: Any? = null
|
||||
override val default: @Contextual Any? = null
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ObjectSchema(
|
||||
val properties: Map<String, ComponentSchema>,
|
||||
override val default: Any? = null,
|
||||
override val default: @Contextual Any? = null,
|
||||
override val description: String? = null,
|
||||
override val nullable: Boolean? = null,
|
||||
// constraints
|
||||
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.schema
|
||||
|
||||
import kotlinx.serialization.Contextual
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class SimpleSchema(
|
||||
override val type: String,
|
||||
override val default: Any? = null,
|
||||
override val default: @Contextual Any? = null,
|
||||
override val description: String? = null,
|
||||
override val nullable: Boolean? = null,
|
||||
// Constraints
|
||||
|
@ -1,11 +1,13 @@
|
||||
package io.bkbn.kompendium.oas.security
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.Locale
|
||||
|
||||
// TODO... is there even an official ktor api auth mechanism??
|
||||
|
||||
@Serializable
|
||||
@Suppress("UnusedPrivateMember")
|
||||
class ApiKeyAuth(val `in`: ApiKeyLocation, name: String) : SecuritySchema {
|
||||
class ApiKeyAuth(val `in`: ApiKeyLocation, val name: String) : SecuritySchema {
|
||||
val type: String = "apiKey"
|
||||
|
||||
enum class ApiKeyLocation {
|
||||
|
@ -1,5 +1,8 @@
|
||||
package io.bkbn.kompendium.oas.security
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class BasicAuth : SecuritySchema {
|
||||
val type: String = "http"
|
||||
val scheme: String = "basic"
|
||||
|
@ -1,5 +1,8 @@
|
||||
package io.bkbn.kompendium.oas.security
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class BearerAuth(val bearerFormat: String? = null): SecuritySchema {
|
||||
val type: String = "http"
|
||||
val scheme: String = "bearer"
|
||||
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.security
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OAuth(val description: String? = null, val flows: Flows) : SecuritySchema {
|
||||
val type: String = "oauth2"
|
||||
|
||||
@Serializable
|
||||
data class Flows(
|
||||
val implicit: Implicit? = null,
|
||||
val authorizationCode: AuthorizationCode? = null,
|
||||
@ -21,12 +25,14 @@ data class OAuth(val description: String? = null, val flows: Flows) : SecuritySc
|
||||
get() = emptyMap()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Implicit(
|
||||
override val authorizationUrl: String,
|
||||
override val refreshUrl: String? = null,
|
||||
override val scopes: Map<String, String> = emptyMap()
|
||||
) : Flow
|
||||
|
||||
@Serializable
|
||||
data class AuthorizationCode(
|
||||
override val authorizationUrl: String,
|
||||
override val tokenUrl: String? = null,
|
||||
@ -34,12 +40,14 @@ data class OAuth(val description: String? = null, val flows: Flows) : SecuritySc
|
||||
override val scopes: Map<String, String> = emptyMap()
|
||||
) : Flow
|
||||
|
||||
@Serializable
|
||||
data class Password(
|
||||
override val tokenUrl: String? = null,
|
||||
override val refreshUrl: String? = null,
|
||||
override val scopes: Map<String, String> = emptyMap()
|
||||
) : Flow
|
||||
|
||||
@Serializable
|
||||
data class ClientCredential(
|
||||
override val tokenUrl: String? = null,
|
||||
override val refreshUrl: String? = null,
|
||||
|
@ -1,3 +1,8 @@
|
||||
package io.bkbn.kompendium.oas.security
|
||||
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.JsonClassDiscriminator
|
||||
|
||||
@OptIn(ExperimentalSerializationApi::class)
|
||||
@JsonClassDiscriminator("schema_type") // todo figure out a way to filter this
|
||||
sealed interface SecuritySchema
|
||||
|
@ -0,0 +1,27 @@
|
||||
package io.bkbn.kompendium.oas.serialization
|
||||
|
||||
import kotlin.reflect.KClass
|
||||
import kotlinx.serialization.InternalSerializationApi
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import kotlinx.serialization.serializer
|
||||
|
||||
class AnySerializer<T : Any> : KSerializer<T> {
|
||||
override fun serialize(encoder: Encoder, value: T) {
|
||||
serialize(encoder, value, value::class as KClass<T>)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): T {
|
||||
error("Abandon all hope ye who enter 💀")
|
||||
}
|
||||
|
||||
override val descriptor: SerialDescriptor
|
||||
get() = TODO("Not yet implemented")
|
||||
|
||||
@OptIn(InternalSerializationApi::class)
|
||||
fun serialize(encoder: Encoder, obj: T, clazz: KClass<T>) {
|
||||
clazz.serializer().serialize(encoder, obj)
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package io.bkbn.kompendium.oas.serialization
|
||||
|
||||
import io.bkbn.kompendium.oas.schema.AnyOfSchema
|
||||
import io.bkbn.kompendium.oas.schema.ArraySchema
|
||||
import io.bkbn.kompendium.oas.schema.ComponentSchema
|
||||
import io.bkbn.kompendium.oas.schema.DictionarySchema
|
||||
import io.bkbn.kompendium.oas.schema.EnumSchema
|
||||
import io.bkbn.kompendium.oas.schema.FormattedSchema
|
||||
import io.bkbn.kompendium.oas.schema.FreeFormSchema
|
||||
import io.bkbn.kompendium.oas.schema.ObjectSchema
|
||||
import io.bkbn.kompendium.oas.schema.SimpleSchema
|
||||
import io.bkbn.kompendium.oas.security.ApiKeyAuth
|
||||
import io.bkbn.kompendium.oas.security.BasicAuth
|
||||
import io.bkbn.kompendium.oas.security.BearerAuth
|
||||
import io.bkbn.kompendium.oas.security.OAuth
|
||||
import io.bkbn.kompendium.oas.security.SecuritySchema
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
|
||||
object KompendiumSerializersModule {
|
||||
|
||||
val module = SerializersModule {
|
||||
polymorphic(ComponentSchema::class) {
|
||||
subclass(SimpleSchema::class, SimpleSchema.serializer())
|
||||
subclass(FormattedSchema::class, FormattedSchema.serializer())
|
||||
subclass(ObjectSchema::class, ObjectSchema.serializer())
|
||||
subclass(AnyOfSchema::class, AnyOfSchema.serializer())
|
||||
subclass(ArraySchema::class, ArraySchema.serializer())
|
||||
subclass(DictionarySchema::class, DictionarySchema.serializer())
|
||||
subclass(EnumSchema::class, EnumSchema.serializer())
|
||||
subclass(FreeFormSchema::class, FreeFormSchema.serializer())
|
||||
}
|
||||
polymorphic(SecuritySchema::class) {
|
||||
subclass(ApiKeyAuth::class, ApiKeyAuth.serializer())
|
||||
subclass(BasicAuth::class, BasicAuth.serializer())
|
||||
subclass(BearerAuth::class, BearerAuth.serializer())
|
||||
subclass(OAuth::class, OAuth.serializer())
|
||||
}
|
||||
contextual(Any::class, AnySerializer())
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package io.bkbn.kompendium.oas.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerializationException
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
|
||||
object NumberSerializer : KSerializer<Number> {
|
||||
override fun deserialize(decoder: Decoder): Number = try {
|
||||
decoder.decodeDouble()
|
||||
} catch (_: SerializationException) {
|
||||
decoder.decodeInt()
|
||||
}
|
||||
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("Number", PrimitiveKind.DOUBLE)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Number) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package io.bkbn.kompendium.oas.serialization
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.net.URI
|
||||
|
||||
object UriSerializer : KSerializer<URI> {
|
||||
override fun deserialize(decoder: Decoder): URI = URI.create(decoder.decodeString())
|
||||
|
||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("URI", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: URI) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
package io.bkbn.kompendium.oas.server
|
||||
|
||||
import io.bkbn.kompendium.oas.serialization.UriSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
@Serializable
|
||||
data class Server(
|
||||
@Serializable(with = UriSerializer::class)
|
||||
val url: URI,
|
||||
val description: String? = null,
|
||||
var variables: Map<String, ServerVariable>? = null
|
||||
|
@ -1,5 +1,8 @@
|
||||
package io.bkbn.kompendium.oas.server
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class ServerVariable(
|
||||
val `enum`: Set<String>, // todo enforce not empty
|
||||
val default: String,
|
||||
|
@ -18,10 +18,23 @@ dependencies {
|
||||
implementation(group = "io.ktor", name = "ktor-auth", version = ktorVersion)
|
||||
implementation(group = "io.ktor", name = "ktor-auth-jwt", version = ktorVersion)
|
||||
implementation(group = "io.ktor", name = "ktor-serialization", version = ktorVersion)
|
||||
implementation(group = "io.ktor", name = "ktor-jackson", version = ktorVersion)
|
||||
implementation(group = "io.ktor", name = "ktor-gson", version = ktorVersion)
|
||||
implementation(group = "io.ktor", name = "ktor-locations", version = ktorVersion)
|
||||
implementation(group = "io.ktor", name = "ktor-webjars", version = ktorVersion)
|
||||
|
||||
// Logging
|
||||
implementation("org.apache.logging.log4j:log4j-api-kotlin:1.1.0")
|
||||
implementation("org.apache.logging.log4j:log4j-api:2.17.0")
|
||||
implementation("org.apache.logging.log4j:log4j-core:2.17.0")
|
||||
implementation("org.slf4j:slf4j-api:1.7.32")
|
||||
implementation("org.slf4j:slf4j-simple:1.7.32")
|
||||
|
||||
|
||||
implementation(group = "org.jetbrains.kotlinx", "kotlinx-serialization-json", version = "1.3.1")
|
||||
|
||||
implementation(group = "joda-time", name = "joda-time", version = "2.10.13")
|
||||
}
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
@ -7,12 +7,8 @@ import io.bkbn.kompendium.core.Notarized.notarizedGet
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.info.Contact
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.info.License
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import io.bkbn.kompendium.playground.AuthPlaygroundToC.simpleAuthenticatedGet
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -27,7 +23,6 @@ import io.ktor.serialization.json
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
||||
@ -44,10 +39,10 @@ fun main() {
|
||||
// Application Module
|
||||
private fun Application.mainModule() {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
json(json = Util.kotlinxConfig)
|
||||
}
|
||||
install(Kompendium) {
|
||||
spec = AuthMetadata.spec
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
install(Authentication) {
|
||||
// We can leverage the security config name to prevent typos
|
||||
@ -73,36 +68,6 @@ private fun Application.mainModule() {
|
||||
}
|
||||
}
|
||||
|
||||
object AuthMetadata {
|
||||
val spec = OpenApiSpec(
|
||||
info = Info(
|
||||
title = "Simple API with documented Authentication",
|
||||
version = "1.33.7",
|
||||
description = "Wow isn't this cool?",
|
||||
termsOfService = URI("https://example.com"),
|
||||
contact = Contact(
|
||||
name = "Homer Simpson",
|
||||
email = "chunkylover53@aol.com",
|
||||
url = URI("https://gph.is/1NPUDiM")
|
||||
),
|
||||
license = License(
|
||||
name = "MIT",
|
||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||
)
|
||||
),
|
||||
servers = mutableListOf(
|
||||
Server(
|
||||
url = URI("https://myawesomeapi.com"),
|
||||
description = "Production instance of my API"
|
||||
),
|
||||
Server(
|
||||
url = URI("https://staging.myawesomeapi.com"),
|
||||
description = "Where the fun stuff happens"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// This is where we define the available security configurations for our app
|
||||
object SecurityConfigurations {
|
||||
val basic = object : BasicAuthConfiguration {
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.bkbn.kompendium.playground
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import io.bkbn.kompendium.annotations.Field
|
||||
import io.bkbn.kompendium.annotations.Param
|
||||
import io.bkbn.kompendium.annotations.ParamType
|
||||
@ -13,18 +15,15 @@ import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.info.Contact
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.info.License
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||
import io.bkbn.kompendium.playground.BasicModels.BasicParameters
|
||||
import io.bkbn.kompendium.playground.BasicModels.BasicResponse
|
||||
import io.bkbn.kompendium.playground.BasicModels.BasicRequest
|
||||
import io.bkbn.kompendium.playground.BasicModels.BasicResponse
|
||||
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleDeleteRequest
|
||||
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleGetExample
|
||||
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simpleGetExampleWithParameters
|
||||
import io.bkbn.kompendium.playground.BasicPlaygroundToC.simplePostRequest
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -37,8 +36,9 @@ import io.ktor.routing.routing
|
||||
import io.ktor.serialization.json
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
@ -57,11 +57,11 @@ fun main() {
|
||||
private fun Application.mainModule() {
|
||||
// Installs Simple JSON Content Negotiation
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
json(json = Util.kotlinxConfig)
|
||||
}
|
||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||
install(Kompendium) {
|
||||
spec = BasicMetadata.spec
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
// Configures the routes for our API
|
||||
routing {
|
||||
@ -161,38 +161,6 @@ object BasicPlaygroundToC {
|
||||
)
|
||||
}
|
||||
|
||||
// Contains the root metadata for our server. This is all the stuff that is defined once
|
||||
// and cannot be inferred from the Ktor application
|
||||
object BasicMetadata {
|
||||
val spec = OpenApiSpec(
|
||||
info = Info(
|
||||
title = "Simple Demo API",
|
||||
version = "1.33.7",
|
||||
description = "Wow isn't this cool?",
|
||||
termsOfService = URI("https://example.com"),
|
||||
contact = Contact(
|
||||
name = "Homer Simpson",
|
||||
email = "chunkylover53@aol.com",
|
||||
url = URI("https://gph.is/1NPUDiM")
|
||||
),
|
||||
license = License(
|
||||
name = "MIT",
|
||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||
)
|
||||
),
|
||||
servers = mutableListOf(
|
||||
Server(
|
||||
url = URI("https://myawesomeapi.com"),
|
||||
description = "Production instance of my API"
|
||||
),
|
||||
Server(
|
||||
url = URI("https://staging.myawesomeapi.com"),
|
||||
description = "Where the fun stuff happens"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
object BasicModels {
|
||||
@Serializable
|
||||
data class BasicResponse(val c: String)
|
||||
@ -207,6 +175,9 @@ object BasicModels {
|
||||
|
||||
@Serializable
|
||||
data class BasicRequest(
|
||||
@JsonProperty("best_field")
|
||||
@SerializedName("best_field")
|
||||
@SerialName("best_field")
|
||||
@Field(description = "This is a super important field!!", name = "best_field")
|
||||
val d: Boolean
|
||||
)
|
||||
|
@ -23,16 +23,12 @@ import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.info.Contact
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.info.License
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedParams
|
||||
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedRequest
|
||||
import io.bkbn.kompendium.playground.ConstrainedModels.ConstrainedResponse
|
||||
import io.bkbn.kompendium.playground.ConstrainedPlaygroundToC.simpleConstrainedGet
|
||||
import io.bkbn.kompendium.playground.ConstrainedPlaygroundToC.simpleConstrainedPost
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -45,7 +41,6 @@ import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import java.net.URI
|
||||
|
||||
fun main() {
|
||||
embeddedServer(
|
||||
@ -59,11 +54,11 @@ fun main() {
|
||||
private fun Application.mainModule() {
|
||||
// Installs Simple JSON Content Negotiation
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
json(json = Util.kotlinxConfig)
|
||||
}
|
||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||
install(Kompendium) {
|
||||
spec = ConstrainedMetadata.spec
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
// Configures the routes for our API
|
||||
routing {
|
||||
@ -104,36 +99,6 @@ object ConstrainedPlaygroundToC {
|
||||
)
|
||||
}
|
||||
|
||||
object ConstrainedMetadata {
|
||||
val spec = OpenApiSpec(
|
||||
info = Info(
|
||||
title = "Simple Demo API",
|
||||
version = "1.33.7",
|
||||
description = "Wow isn't this cool?",
|
||||
termsOfService = URI("https://example.com"),
|
||||
contact = Contact(
|
||||
name = "Homer Simpson",
|
||||
email = "chunkylover53@aol.com",
|
||||
url = URI("https://gph.is/1NPUDiM")
|
||||
),
|
||||
license = License(
|
||||
name = "MIT",
|
||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||
)
|
||||
),
|
||||
servers = mutableListOf(
|
||||
Server(
|
||||
url = URI("https://myawesomeapi.com"),
|
||||
description = "Production instance of my API"
|
||||
),
|
||||
Server(
|
||||
url = URI("https://staging.myawesomeapi.com"),
|
||||
description = "Where the fun stuff happens"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
object ConstrainedModels {
|
||||
@Serializable
|
||||
data class ConstrainedResponse(
|
||||
|
@ -6,12 +6,8 @@ import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.info.Contact
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.info.License
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import io.bkbn.kompendium.playground.ExceptionPlaygroundToC.simpleGetExample
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -25,7 +21,6 @@ import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlin.reflect.typeOf
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
import java.time.LocalDateTime
|
||||
|
||||
// Application Entrypoint
|
||||
@ -41,11 +36,11 @@ fun main() {
|
||||
private fun Application.mainModule() {
|
||||
// Installs Simple JSON Content Negotiation
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
json(json = Util.kotlinxConfig)
|
||||
}
|
||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||
install(Kompendium) {
|
||||
spec = ExceptionMetadata.spec
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
install(StatusPages) {
|
||||
exception<ExceptionModels.BadUserException> {
|
||||
@ -70,36 +65,6 @@ private fun Application.mainModule() {
|
||||
}
|
||||
}
|
||||
|
||||
object ExceptionMetadata {
|
||||
val spec = OpenApiSpec(
|
||||
info = Info(
|
||||
title = "Simple Demo API with notarized exceptions",
|
||||
version = "1.33.7",
|
||||
description = "Wow isn't this cool?",
|
||||
termsOfService = URI("https://example.com"),
|
||||
contact = Contact(
|
||||
name = "Homer Simpson",
|
||||
email = "chunkylover53@aol.com",
|
||||
url = URI("https://gph.is/1NPUDiM")
|
||||
),
|
||||
license = License(
|
||||
name = "MIT",
|
||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||
)
|
||||
),
|
||||
servers = mutableListOf(
|
||||
Server(
|
||||
url = URI("https://myawesomeapi.com"),
|
||||
description = "Production instance of my API"
|
||||
),
|
||||
Server(
|
||||
url = URI("https://staging.myawesomeapi.com"),
|
||||
description = "Where the fun stuff happens"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// This is a table of contents to hold all the metadata for our various API endpoints
|
||||
object ExceptionPlaygroundToC {
|
||||
private val simpleException = ExceptionInfo<ExceptionModels.ExceptionResponse>(
|
||||
|
@ -5,12 +5,8 @@ import io.bkbn.kompendium.core.Notarized.notarizedGet
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.info.Contact
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.info.License
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import io.bkbn.kompendium.playground.GenericPlaygroundToC.simpleGenericGet
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -22,7 +18,6 @@ import io.ktor.serialization.json
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
||||
@ -40,11 +35,11 @@ fun main() {
|
||||
private fun Application.mainModule() {
|
||||
// Installs Simple JSON Content Negotiation
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
json(json = Util.kotlinxConfig)
|
||||
}
|
||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||
install(Kompendium) {
|
||||
spec = GenericMetadata.spec
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
routing {
|
||||
redoc(pageTitle = "Simple API Docs")
|
||||
@ -70,38 +65,6 @@ object GenericPlaygroundToC {
|
||||
)
|
||||
}
|
||||
|
||||
// Contains the root metadata for our server. This is all the stuff that is defined once
|
||||
// and cannot be inferred from the Ktor application
|
||||
object GenericMetadata {
|
||||
val spec = OpenApiSpec(
|
||||
info = Info(
|
||||
title = "Simple Demo API with Generic Data",
|
||||
version = "1.33.7",
|
||||
description = "Wow isn't this cool?",
|
||||
termsOfService = URI("https://example.com"),
|
||||
contact = Contact(
|
||||
name = "Homer Simpson",
|
||||
email = "chunkylover53@aol.com",
|
||||
url = URI("https://gph.is/1NPUDiM")
|
||||
),
|
||||
license = License(
|
||||
name = "MIT",
|
||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||
)
|
||||
),
|
||||
servers = mutableListOf(
|
||||
Server(
|
||||
url = URI("https://myawesomeapi.com"),
|
||||
description = "Production instance of my API"
|
||||
),
|
||||
Server(
|
||||
url = URI("https://staging.myawesomeapi.com"),
|
||||
description = "Where the fun stuff happens"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
object GenericModels {
|
||||
@Serializable
|
||||
data class Foosy<T, K>(val test: T, val otherThing: List<K>)
|
||||
|
@ -0,0 +1,49 @@
|
||||
package io.bkbn.kompendium.playground
|
||||
|
||||
import io.bkbn.kompendium.core.Kompendium
|
||||
import io.bkbn.kompendium.core.Notarized.notarizedPost
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
import io.ktor.features.ContentNegotiation
|
||||
import io.ktor.gson.gson
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.route
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
|
||||
fun main() {
|
||||
embeddedServer(
|
||||
Netty,
|
||||
port = 8081,
|
||||
module = Application::mainModule
|
||||
).start(wait = true)
|
||||
}
|
||||
|
||||
private fun Application.mainModule() {
|
||||
install(ContentNegotiation) {
|
||||
gson {
|
||||
setPrettyPrinting()
|
||||
}
|
||||
}
|
||||
install(Kompendium) {
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
routing {
|
||||
redoc()
|
||||
route("/create") {
|
||||
notarizedPost(BasicPlaygroundToC.simplePostRequest) {
|
||||
val request = call.receive<BasicModels.BasicRequest>()
|
||||
when (request.d) {
|
||||
true -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "So it is true!"))
|
||||
false -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "Oh, I knew it!"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
package io.bkbn.kompendium.playground
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.SerializationFeature
|
||||
import io.bkbn.kompendium.core.Kompendium
|
||||
import io.bkbn.kompendium.core.Notarized.notarizedPost
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
import io.ktor.features.ContentNegotiation
|
||||
import io.ktor.http.HttpStatusCode
|
||||
import io.ktor.jackson.jackson
|
||||
import io.ktor.request.receive
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.routing.route
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
|
||||
fun main() {
|
||||
embeddedServer(
|
||||
Netty,
|
||||
port = 8081,
|
||||
module = Application::mainModule
|
||||
).start(wait = true)
|
||||
}
|
||||
|
||||
private fun Application.mainModule() {
|
||||
install(ContentNegotiation) {
|
||||
jackson {
|
||||
enable(SerializationFeature.INDENT_OUTPUT)
|
||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
}
|
||||
}
|
||||
install(Kompendium) {
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
routing {
|
||||
redoc()
|
||||
route("/create") {
|
||||
notarizedPost(BasicPlaygroundToC.simplePostRequest) {
|
||||
val request = call.receive<BasicModels.BasicRequest>()
|
||||
when (request.d) {
|
||||
true -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "So it is true!"))
|
||||
false -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "Oh, I knew it!"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,14 +7,10 @@ import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.locations.NotarizedLocation.notarizedGet
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.info.Contact
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.info.License
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import io.bkbn.kompendium.playground.LocationsToC.ohBoiUCrazy
|
||||
import io.bkbn.kompendium.playground.LocationsToC.testLocation
|
||||
import io.bkbn.kompendium.playground.LocationsToC.testNestLocation
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -28,7 +24,6 @@ import io.ktor.serialization.json
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
||||
@ -44,10 +39,10 @@ fun main() {
|
||||
|
||||
private fun Application.mainModule() {
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
json(json = Util.kotlinxConfig)
|
||||
}
|
||||
install(Kompendium) {
|
||||
spec = LocationMetadata.spec
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
install(Locations)
|
||||
routing {
|
||||
@ -118,38 +113,6 @@ data class TestLocations(
|
||||
}
|
||||
}
|
||||
|
||||
// Contains the root metadata for our server. This is all the stuff that is defined once
|
||||
// and cannot be inferred from the Ktor application
|
||||
object LocationMetadata {
|
||||
val spec = OpenApiSpec(
|
||||
info = Info(
|
||||
title = "Simple Demo Leveraging Ktor Locations",
|
||||
version = "1.33.7",
|
||||
description = "Wow isn't this cool?",
|
||||
termsOfService = URI("https://example.com"),
|
||||
contact = Contact(
|
||||
name = "Homer Simpson",
|
||||
email = "chunkylover53@aol.com",
|
||||
url = URI("https://gph.is/1NPUDiM")
|
||||
),
|
||||
license = License(
|
||||
name = "MIT",
|
||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||
)
|
||||
),
|
||||
servers = mutableListOf(
|
||||
Server(
|
||||
url = URI("https://myawesomeapi.com"),
|
||||
description = "Production instance of my API"
|
||||
),
|
||||
Server(
|
||||
url = URI("https://staging.myawesomeapi.com"),
|
||||
description = "Where the fun stuff happens"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
object LocationModels {
|
||||
@Serializable
|
||||
data class ExampleResponse(val c: String)
|
||||
|
@ -5,12 +5,8 @@ import io.bkbn.kompendium.core.Notarized.notarizedGet
|
||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.routes.redoc
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.info.Contact
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.info.License
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import io.bkbn.kompendium.playground.PolymorphicPlaygroundToC.polymorphicExample
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
@ -22,7 +18,6 @@ import io.ktor.serialization.json
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.net.URI
|
||||
|
||||
/**
|
||||
* Application entrypoint. Run this and head on over to `localhost:8081/docs`
|
||||
@ -39,11 +34,11 @@ fun main() {
|
||||
private fun Application.mainModule() {
|
||||
// Installs Simple JSON Content Negotiation
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
json(json = Util.kotlinxConfig)
|
||||
}
|
||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||
install(Kompendium) {
|
||||
spec = PolymorphicMetadata.spec
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
// Configures the routes for our API
|
||||
routing {
|
||||
@ -70,36 +65,6 @@ object PolymorphicPlaygroundToC {
|
||||
)
|
||||
}
|
||||
|
||||
object PolymorphicMetadata {
|
||||
val spec = OpenApiSpec(
|
||||
info = Info(
|
||||
title = "Simple Demo API with Polymorphic Models",
|
||||
version = "1.33.7",
|
||||
description = "Wow isn't this cool?",
|
||||
termsOfService = URI("https://example.com"),
|
||||
contact = Contact(
|
||||
name = "Homer Simpson",
|
||||
email = "chunkylover53@aol.com",
|
||||
url = URI("https://gph.is/1NPUDiM")
|
||||
),
|
||||
license = License(
|
||||
name = "MIT",
|
||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||
)
|
||||
),
|
||||
servers = mutableListOf(
|
||||
Server(
|
||||
url = URI("https://myawesomeapi.com"),
|
||||
description = "Production instance of my API"
|
||||
),
|
||||
Server(
|
||||
url = URI("https://staging.myawesomeapi.com"),
|
||||
description = "Where the fun stuff happens"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
object PolymorphicModels {
|
||||
sealed interface SlammaJamma
|
||||
|
||||
|
@ -2,6 +2,7 @@ package io.bkbn.kompendium.playground
|
||||
|
||||
import io.bkbn.kompendium.core.Kompendium
|
||||
import io.bkbn.kompendium.core.Notarized.notarizedGet
|
||||
import io.bkbn.kompendium.playground.util.Util
|
||||
import io.bkbn.kompendium.swagger.swaggerUI
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
@ -32,12 +33,12 @@ fun main() {
|
||||
private fun Application.mainModule() {
|
||||
// Installs Simple JSON Content Negotiation
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
json(json = Util.kotlinxConfig)
|
||||
}
|
||||
install(Webjars)
|
||||
// Installs the Kompendium Plugin and sets up baseline server metadata
|
||||
install(Kompendium) {
|
||||
spec = BasicMetadata.spec
|
||||
spec = Util.baseSpec
|
||||
}
|
||||
// Configures the routes for our API
|
||||
routing {
|
||||
|
@ -0,0 +1,48 @@
|
||||
package io.bkbn.kompendium.playground.util
|
||||
|
||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||
import io.bkbn.kompendium.oas.info.Contact
|
||||
import io.bkbn.kompendium.oas.info.Info
|
||||
import io.bkbn.kompendium.oas.info.License
|
||||
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||
import io.bkbn.kompendium.oas.server.Server
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.net.URI
|
||||
|
||||
object Util {
|
||||
val kotlinxConfig = Json {
|
||||
classDiscriminator = "class"
|
||||
serializersModule = KompendiumSerializersModule.module
|
||||
prettyPrint = true
|
||||
explicitNulls = false
|
||||
encodeDefaults = true
|
||||
}
|
||||
|
||||
val baseSpec = OpenApiSpec(
|
||||
info = Info(
|
||||
title = "Simple Demo API",
|
||||
version = "1.33.7",
|
||||
description = "Wow isn't this cool?",
|
||||
termsOfService = URI("https://example.com"),
|
||||
contact = Contact(
|
||||
name = "Homer Simpson",
|
||||
email = "chunkylover53@aol.com",
|
||||
url = URI("https://gph.is/1NPUDiM")
|
||||
),
|
||||
license = License(
|
||||
name = "MIT",
|
||||
url = URI("https://github.com/bkbnio/kompendium/blob/main/LICENSE")
|
||||
)
|
||||
),
|
||||
servers = mutableListOf(
|
||||
Server(
|
||||
url = URI("https://myawesomeapi.com"),
|
||||
description = "Production instance of my API"
|
||||
),
|
||||
Server(
|
||||
url = URI("https://staging.myawesomeapi.com"),
|
||||
description = "Where the fun stuff happens"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user