feat: Multi Serialization Support (#134)

This commit is contained in:
Ryan Brink
2022-01-03 21:05:30 -05:00
committed by GitHub
parent c6ed261fe4
commit da104d0a63
56 changed files with 475 additions and 326 deletions

View File

@ -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

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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) {

View File

@ -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 {

View File

@ -80,7 +80,7 @@
},
"required": [
"org",
"amazingField",
"amazing_field",
"tables"
],
"type": "object"

View File

@ -62,7 +62,7 @@
}
},
"required": [
"fieldName",
"field_name",
"b",
"aaa"
],

View File

@ -45,7 +45,7 @@
}
},
"required": [
"b"
"real_name"
],
"type": "object"
}

View File

@ -82,7 +82,7 @@
}
},
"required": [
"fieldName",
"field_name",
"b",
"aaa"
],

View File

@ -82,7 +82,7 @@
}
},
"required": [
"fieldName",
"field_name",
"b",
"aaa"
],

View File

@ -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")
}

View File

@ -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,

View File

@ -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?
)

View File

@ -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,

View File

@ -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()
)

View File

@ -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?
)

View File

@ -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

View File

@ -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
)

View File

@ -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,

View File

@ -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,

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -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"

View File

@ -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"

View File

@ -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,

View File

@ -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

View File

@ -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)
}
}

View File

@ -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())
}
}

View File

@ -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())
}
}

View File

@ -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())
}
}

View File

@ -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

View File

@ -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,

View File

@ -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()
}

View File

@ -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 {

View File

@ -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
)

View File

@ -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(

View File

@ -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>(

View File

@ -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>)

View File

@ -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!"))
}
}
}
}
}

View File

@ -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!"))
}
}
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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"
)
)
)
}