lots of stuff, no time to s-plain #5
@ -1,2 +1,3 @@
|
||||
kotlin 1.5.0-M2
|
||||
java openjdk-14.0.1
|
||||
gradle 7.0
|
||||
|
@ -1,3 +1,10 @@
|
||||
## [0.0.2] - April 12th, 2021
|
||||
|
||||
### Added
|
||||
|
||||
- Beginning of an implementation. Currently, able to generate a rough outline of the API at runtime, along with generating
|
||||
full data classes represented by JSON Schema.
|
||||
|
||||
## [0.0.1] - April 11th, 2021
|
||||
|
||||
### Added
|
||||
|
52
README.md
52
README.md
@ -2,10 +2,56 @@
|
||||
|
||||
## What is Kompendium
|
||||
|
||||
Kompendium is intended to be a non-intrusive OpenApi Specification generator for [Ktor](https://ktor.io).
|
||||
Non-invasive meaning that users will use only Ktor native functions when implementing their API, and will supplement
|
||||
with Kompendium code in order to generate the appropriate spec.
|
||||
Kompendium is intended to be a minimally invasive
|
||||
OpenApi Specification generator for
|
||||
[Ktor](https://ktor.io).
|
||||
Minimally invasive meaning that users will use only
|
||||
Ktor native functions when implementing their API,
|
||||
and will supplement with Kompendium code in order
|
||||
to generate the appropriate spec.
|
||||
|
||||
## Modules
|
||||
|
||||
TODO
|
||||
|
||||
## Examples
|
||||
|
||||
```kotlin
|
||||
// Minimal API Example
|
||||
fun Application.mainModule() {
|
||||
install(ContentNegotiation) {
|
||||
jackson()
|
||||
}
|
||||
routing {
|
||||
route("/test") {
|
||||
route("/{id}") {
|
||||
notarizedGet(testIdGetInfo) {
|
||||
call.respondText("get by id")
|
||||
}
|
||||
}
|
||||
route("/single") {
|
||||
notarizedGet(testSingleGetInfo) {
|
||||
call.respondText("get single")
|
||||
}
|
||||
notarizedPost<A, B, C>(testSinglePostInfo) {
|
||||
call.respondText("test post")
|
||||
}
|
||||
notarizedPut<A, B, D>(testSinglePutInfo) {
|
||||
call.respondText { "hey" }
|
||||
}
|
||||
}
|
||||
}
|
||||
route("/openapi.json") {
|
||||
get {
|
||||
call.respond(openApiSpec.copy(
|
||||
info = OpenApiSpecInfo(
|
||||
title = "Test API",
|
||||
version = "1.3.3.7",
|
||||
description = "An amazing, fully-ish 😉 generated API spec"
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions-snapshots/gradle-7.1-20210328220041+0000-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -6,6 +6,7 @@ plugins {
|
||||
dependencies {
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation(libs.bundles.ktor)
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||
testImplementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.12.0")
|
||||
|
@ -1,12 +1,128 @@
|
||||
package org.leafygreens.kompendium
|
||||
|
||||
import org.leafygreens.kompendium.models.OpenApiSpec
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecInfo
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.createRouteFromPath
|
||||
import io.ktor.routing.method
|
||||
import io.ktor.util.pipeline.PipelineInterceptor
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import org.leafygreens.kompendium.annotations.KompendiumField
|
||||
import org.leafygreens.kompendium.annotations.KompendiumInternal
|
||||
import org.leafygreens.kompendium.models.oas.ArraySchema
|
||||
import org.leafygreens.kompendium.models.oas.FormatSchema
|
||||
import org.leafygreens.kompendium.models.oas.ObjectSchema
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponentSchema
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
|
||||
import org.leafygreens.kompendium.models.oas.SimpleSchema
|
||||
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||
import org.leafygreens.kompendium.util.Helpers.calculatePath
|
||||
import org.leafygreens.kompendium.util.Helpers.putPairIfAbsent
|
||||
|
||||
class Kompendium {
|
||||
val spec = OpenApiSpec(
|
||||
object Kompendium {
|
||||
val openApiSpec = OpenApiSpec(
|
||||
info = OpenApiSpecInfo(),
|
||||
servers = mutableListOf(),
|
||||
paths = mutableMapOf()
|
||||
)
|
||||
|
||||
fun Route.notarizedGet(info: MethodInfo, body: PipelineInterceptor<Unit, ApplicationCall>): Route {
|
||||
val path = calculatePath()
|
||||
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||
openApiSpec.paths[path]?.get = OpenApiSpecPathItemOperation(
|
||||
summary = info.summary,
|
||||
description = info.description,
|
||||
tags = info.tags
|
||||
)
|
||||
return method(HttpMethod.Get) { handle(body) }
|
||||
}
|
||||
|
||||
inline fun <reified TQ : Any, reified TP : Any, reified TR : Any> Route.notarizedPost(
|
||||
info: MethodInfo,
|
||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>
|
||||
): Route = generateComponentSchemas<TQ, TP, TR>(info, body) { i, b ->
|
||||
val path = calculatePath()
|
||||
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||
openApiSpec.paths[path]?.post = OpenApiSpecPathItemOperation(
|
||||
summary = i.summary,
|
||||
description = i.description,
|
||||
tags = i.tags
|
||||
)
|
||||
return method(HttpMethod.Post) { handle(b) }
|
||||
}
|
||||
|
||||
inline fun <reified TQ : Any, reified TP : Any, reified TR : Any> Route.notarizedPut(
|
||||
info: MethodInfo,
|
||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
|
||||
): Route = generateComponentSchemas<TQ, TP, TR>(info, body) { i, b ->
|
||||
val path = calculatePath()
|
||||
openApiSpec.paths.getOrPut(path) { OpenApiSpecPathItem() }
|
||||
openApiSpec.paths[path]?.put = OpenApiSpecPathItemOperation(
|
||||
summary = i.summary,
|
||||
description = i.description,
|
||||
tags = i.tags
|
||||
)
|
||||
return method(HttpMethod.Put) { handle(b) }
|
||||
}
|
||||
|
||||
@OptIn(KompendiumInternal::class)
|
||||
inline fun <reified TQ : Any, reified TP : Any, reified TR : Any> generateComponentSchemas(
|
||||
info: MethodInfo,
|
||||
noinline body: PipelineInterceptor<Unit, ApplicationCall>,
|
||||
block: (MethodInfo, PipelineInterceptor<Unit, ApplicationCall>) -> Route
|
||||
): Route {
|
||||
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TQ::class))
|
||||
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TR::class))
|
||||
openApiSpec.components.schemas.putPairIfAbsent(objectSchemaPair(TP::class))
|
||||
return block.invoke(info, body)
|
||||
}
|
||||
|
||||
@KompendiumInternal
|
||||
// TODO Investigate a caching mechanism to reduce overhead... then just reference once created
|
||||
fun objectSchemaPair(clazz: KClass<*>): Pair<String, ObjectSchema> {
|
||||
val o = objectSchema(clazz)
|
||||
return Pair(clazz.qualifiedName!!, o)
|
||||
}
|
||||
|
||||
private fun objectSchema(clazz: KClass<*>): ObjectSchema =
|
||||
ObjectSchema(properties = clazz.memberProperties.associate { prop ->
|
||||
val field = prop.javaField?.type?.kotlin
|
||||
val anny = prop.findAnnotation<KompendiumField>()
|
||||
val schema = when (field) {
|
||||
List::class -> listFieldSchema(prop)
|
||||
else -> fieldToSchema(field as KClass<*>)
|
||||
}
|
||||
|
||||
val name = anny?.let {
|
||||
anny.name
|
||||
} ?: prop.name
|
||||
|
||||
Pair(name, schema)
|
||||
})
|
||||
|
||||
private fun listFieldSchema(prop: KProperty<*>): ArraySchema {
|
||||
val listType = ((prop.javaField?.genericType
|
||||
as ParameterizedType).actualTypeArguments.first()
|
||||
as Class<*>).kotlin
|
||||
return ArraySchema(fieldToSchema(listType))
|
||||
}
|
||||
|
||||
@OptIn(KompendiumInternal::class)
|
||||
private fun fieldToSchema(field: KClass<*>): OpenApiSpecComponentSchema = when (field) {
|
||||
Int::class -> FormatSchema("int32", "integer")
|
||||
Long::class -> FormatSchema("int64", "integer")
|
||||
Double::class -> FormatSchema("double", "number")
|
||||
Float::class -> FormatSchema("float", "number")
|
||||
String::class -> SimpleSchema("string")
|
||||
Boolean::class -> SimpleSchema("boolean")
|
||||
else -> objectSchema(field)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
package org.leafygreens.kompendium.annotations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
annotation class KompendiumField(val name: String)
|
@ -0,0 +1,18 @@
|
||||
package org.leafygreens.kompendium.annotations
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@RequiresOptIn(
|
||||
level = RequiresOptIn.Level.WARNING,
|
||||
message = "This API internal to Kompendium and should not be used. It could be removed or changed without notice."
|
||||
)
|
||||
@Experimental(level = Experimental.Level.WARNING)
|
||||
@Target(
|
||||
AnnotationTarget.CLASS,
|
||||
AnnotationTarget.TYPEALIAS,
|
||||
AnnotationTarget.FUNCTION,
|
||||
AnnotationTarget.PROPERTY,
|
||||
AnnotationTarget.FIELD,
|
||||
AnnotationTarget.CONSTRUCTOR,
|
||||
AnnotationTarget.PROPERTY_SETTER
|
||||
)
|
||||
annotation class KompendiumInternal
|
@ -1,14 +0,0 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
|
||||
data class OpenApiSpec(
|
||||
val openapi: String = "3.0.3",
|
||||
val info: OpenApiSpecInfo? = null,
|
||||
// TODO Needs to default to server object with url of `/`
|
||||
val servers: MutableList<OpenApiSpecServer>? = null,
|
||||
val paths: MutableMap<String, OpenApiSpecPathItem>? = null,
|
||||
val components: OpenApiSpecComponents? = null,
|
||||
// todo needs to reference objects in the components -> security scheme 🤔
|
||||
val security: List<Map<String, List<String>>>? = null,
|
||||
val tags: List<OpenApiSpecTag>? = null,
|
||||
val externalDocs: OpenApiSpecExternalDocumentation? = null
|
||||
)
|
@ -1,6 +0,0 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
|
||||
// TODO I *think* the only thing I need here is the security https://swagger.io/specification/#components-object
|
||||
data class OpenApiSpecComponents(
|
||||
val securitySchemes: Map<String, OpenApiSpecSchema>
|
||||
)
|
@ -1,16 +0,0 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
|
||||
data class OpenApiSpecPathItem(
|
||||
val summary: String? = null,
|
||||
val description: String? = null,
|
||||
val get: OpenApiSpecPathItemOperation? = null,
|
||||
val put: OpenApiSpecPathItemOperation? = null,
|
||||
val post: OpenApiSpecPathItemOperation? = null,
|
||||
val delete: OpenApiSpecPathItemOperation? = null,
|
||||
val options: OpenApiSpecPathItemOperation? = null,
|
||||
val head: OpenApiSpecPathItemOperation? = null,
|
||||
val patch: OpenApiSpecPathItemOperation? = null,
|
||||
val trace: OpenApiSpecPathItemOperation? = null,
|
||||
val servers: List<OpenApiSpecServer>? = null,
|
||||
val parameters: List<OpenApiSpecReferencable>? = null
|
||||
)
|
@ -1,19 +0,0 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
|
||||
data class OpenApiSpecPathItemOperation(
|
||||
val tags: Set<String> = emptySet(),
|
||||
val summary: String? = null,
|
||||
val description: String? = null,
|
||||
val externalDocs: OpenApiSpecExternalDocumentation? = null,
|
||||
val operationId: String? = null,
|
||||
val parameters: List<OpenApiSpecReferencable>? = null,
|
||||
val requestBody: OpenApiSpecReferencable? = null,
|
||||
// TODO How to enforce `default` requirement 🧐
|
||||
val responses: Map<String, OpenApiSpecReferencable>? = null,
|
||||
val callbacks: Map<String, OpenApiSpecReferencable>? = null,
|
||||
val deprecated: Boolean = false,
|
||||
// todo big yikes... also needs to reference objects in the security scheme 🤔
|
||||
val security: List<Map<String, List<String>>>? = null,
|
||||
val servers: List<OpenApiSpecServer>? = null,
|
||||
val `x-codegen-request-body-name`: String? = null
|
||||
)
|
@ -0,0 +1,3 @@
|
||||
package org.leafygreens.kompendium.models.meta
|
||||
|
||||
data class MethodInfo(val summary: String, val description: String? = null, val tags: Set<String> = emptySet())
|
@ -0,0 +1,14 @@
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
data class OpenApiSpec(
|
||||
val openapi: String = "3.0.3",
|
||||
val info: OpenApiSpecInfo,
|
||||
// TODO Needs to default to server object with url of `/`
|
||||
val servers: MutableList<OpenApiSpecServer> = mutableListOf(),
|
||||
val paths: MutableMap<String, OpenApiSpecPathItem> = mutableMapOf(),
|
||||
val components: OpenApiSpecComponents = OpenApiSpecComponents(),
|
||||
// todo needs to reference objects in the components -> security scheme 🤔
|
||||
val security: MutableList<Map<String, List<String>>> = mutableListOf(),
|
||||
val tags: MutableList<OpenApiSpecTag> = mutableListOf(),
|
||||
val externalDocs: OpenApiSpecExternalDocumentation? = null
|
||||
)
|
@ -0,0 +1,14 @@
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
// TODO Enum for type?
|
||||
sealed class OpenApiSpecComponentSchema(open val type: String)
|
||||
|
||||
data class ObjectSchema(
|
||||
val properties: Map<String, OpenApiSpecComponentSchema>
|
||||
) : OpenApiSpecComponentSchema("object")
|
||||
|
||||
data class SimpleSchema(override val type: String) : OpenApiSpecComponentSchema(type)
|
||||
|
||||
data class FormatSchema(val format: String, override val type: String) : OpenApiSpecComponentSchema(type)
|
||||
|
||||
data class ArraySchema(val items: OpenApiSpecComponentSchema) : OpenApiSpecComponentSchema("array")
|
@ -0,0 +1,7 @@
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
// TODO I *think* the only thing I need here is the security https://swagger.io/specification/#components-object
|
||||
data class OpenApiSpecComponents(
|
||||
val schemas: MutableMap<String, OpenApiSpecComponentSchema> = mutableMapOf(),
|
||||
val securitySchemes: MutableMap<String, OpenApiSpecSchema> = mutableMapOf()
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
import java.net.URI
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
import java.net.URI
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
import java.net.URI
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
import java.net.URI
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
data class OpenApiSpecLink(
|
||||
val operationRef: String?, // todo mutually exclusive with operationId
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
// TODO Oof -> https://swagger.io/specification/#media-type-object
|
||||
data class OpenApiSpecMediaType(
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
import java.net.URI
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
data class OpenApiSpecOAuthFlows(
|
||||
val implicit: OpenApiSpecOAuthFlow?,
|
@ -0,0 +1,14 @@
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
data class OpenApiSpecPathItem(
|
||||
var get: OpenApiSpecPathItemOperation? = null,
|
||||
var put: OpenApiSpecPathItemOperation? = null,
|
||||
var post: OpenApiSpecPathItemOperation? = null,
|
||||
var delete: OpenApiSpecPathItemOperation? = null,
|
||||
var options: OpenApiSpecPathItemOperation? = null,
|
||||
var head: OpenApiSpecPathItemOperation? = null,
|
||||
var patch: OpenApiSpecPathItemOperation? = null,
|
||||
var trace: OpenApiSpecPathItemOperation? = null,
|
||||
var servers: List<OpenApiSpecServer>? = null,
|
||||
var parameters: List<OpenApiSpecReferencable>? = null
|
||||
)
|
@ -0,0 +1,19 @@
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
data class OpenApiSpecPathItemOperation(
|
||||
var tags: Set<String> = emptySet(),
|
||||
var summary: String? = null,
|
||||
var description: String? = null,
|
||||
var externalDocs: OpenApiSpecExternalDocumentation? = null,
|
||||
var operationId: String? = null,
|
||||
var parameters: List<OpenApiSpecReferencable>? = null,
|
||||
var requestBody: OpenApiSpecReferencable? = null,
|
||||
// TODO How to enforce `default` requirement 🧐
|
||||
var responses: Map<String, OpenApiSpecReferencable>? = null,
|
||||
var callbacks: Map<String, OpenApiSpecReferencable>? = null,
|
||||
var deprecated: Boolean = false,
|
||||
// todo big yikes... also needs to reference objects in the security scheme 🤔
|
||||
var security: List<Map<String, List<String>>>? = null,
|
||||
var servers: List<OpenApiSpecServer>? = null,
|
||||
var `x-codegen-request-body-name`: String? = null
|
||||
)
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
sealed class OpenApiSpecReferencable
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
sealed class OpenApiSpecSchema
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
import java.net.URI
|
||||
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
data class OpenApiSpecServerVariable(
|
||||
val `enum`: Set<String>, // todo enforce not empty
|
@ -1,4 +1,4 @@
|
||||
package org.leafygreens.kompendium.models
|
||||
package org.leafygreens.kompendium.models.oas
|
||||
|
||||
data class OpenApiSpecTag(
|
||||
val name: String,
|
@ -0,0 +1,21 @@
|
||||
package org.leafygreens.kompendium.util
|
||||
|
||||
import io.ktor.routing.PathSegmentConstantRouteSelector
|
||||
import io.ktor.routing.PathSegmentParameterRouteSelector
|
||||
import io.ktor.routing.RootRouteSelector
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.util.InternalAPI
|
||||
|
||||
object Helpers {
|
||||
|
||||
@OptIn(InternalAPI::class)
|
||||
fun Route.calculatePath(tail: String = ""): String = when (selector) {
|
||||
is RootRouteSelector -> tail
|
||||
is PathSegmentParameterRouteSelector -> parent?.calculatePath("/$selector$tail") ?: "/{$selector}$tail"
|
||||
is PathSegmentConstantRouteSelector -> parent?.calculatePath("/$selector$tail") ?: "/$selector$tail"
|
||||
else -> error("unknown selector type $selector")
|
||||
}
|
||||
|
||||
fun <K, V> MutableMap<K, V>.putPairIfAbsent(pair: Pair<K, V>) = putIfAbsent(pair.first, pair.second)
|
||||
|
||||
}
|
@ -7,8 +7,7 @@ internal class KompendiumTest {
|
||||
|
||||
@Test
|
||||
fun `Kompendium can be instantiated with no details`() {
|
||||
val kompendium = Kompendium()
|
||||
assertEquals(kompendium.spec.openapi, "3.0.3", "Kompendium has a default spec version of 3.0.3")
|
||||
assertEquals(Kompendium.openApiSpec.openapi, "3.0.3", "Kompendium has a default spec version of 3.0.3")
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,26 +2,26 @@ package org.leafygreens.kompendium.util
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import org.leafygreens.kompendium.models.OpenApiSpec
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecComponents
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecExternalDocumentation
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecInfo
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecInfoContact
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecInfoLicense
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecMediaType
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecOAuthFlow
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecOAuthFlows
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecParameter
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecPathItem
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecPathItemOperation
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecRequest
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecResponse
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecSchemaArray
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecSchemaRef
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecSchemaSecurity
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecSchemaString
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecServer
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecTag
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpec
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecComponents
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecExternalDocumentation
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoContact
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfoLicense
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecMediaType
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecOAuthFlow
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecOAuthFlows
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecParameter
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItem
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecPathItemOperation
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecRequest
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecResponse
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaArray
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaRef
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaSecurity
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecSchemaString
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecServer
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecTag
|
||||
|
||||
object TestData {
|
||||
fun getFileSnapshot(fileName: String): String {
|
||||
@ -61,7 +61,7 @@ object TestData {
|
||||
url = URI("http://petstore.swagger.io/v2")
|
||||
)
|
||||
),
|
||||
tags = listOf(
|
||||
tags = mutableListOf(
|
||||
OpenApiSpecTag(
|
||||
name = "pet",
|
||||
description = "Everything about your Pets",
|
||||
@ -201,7 +201,7 @@ object TestData {
|
||||
)
|
||||
),
|
||||
components = OpenApiSpecComponents(
|
||||
securitySchemes = mapOf(
|
||||
securitySchemes = mutableMapOf(
|
||||
"petstore_auth" to OpenApiSpecSchemaSecurity(
|
||||
type = "oauth2",
|
||||
flows = OpenApiSpecOAuthFlows(
|
||||
@ -219,7 +219,8 @@ object TestData {
|
||||
name = "api_key",
|
||||
`in` = "header"
|
||||
)
|
||||
)
|
||||
),
|
||||
schemas = mutableMapOf()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -153,6 +153,7 @@
|
||||
}
|
||||
},
|
||||
"components" : {
|
||||
"schemas" : { },
|
||||
"securitySchemes" : {
|
||||
"petstore_auth" : {
|
||||
"type" : "oauth2",
|
||||
@ -173,6 +174,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"security" : [ ],
|
||||
"tags" : [ {
|
||||
"name" : "pet",
|
||||
"description" : "Everything about your Pets",
|
||||
|
@ -1,5 +1,4 @@
|
||||
plugins {
|
||||
kotlin("kapt")
|
||||
application
|
||||
}
|
||||
|
||||
@ -8,7 +7,6 @@ dependencies {
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation(projects.kompendiumCore)
|
||||
kapt(projects.kompendiumProcessor)
|
||||
|
||||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.bundles.logging)
|
||||
|
@ -2,47 +2,85 @@ package org.leafygreens.kompendium.playground
|
||||
|
||||
import io.ktor.application.Application
|
||||
import io.ktor.application.call
|
||||
import io.ktor.application.install
|
||||
import io.ktor.features.ContentNegotiation
|
||||
import io.ktor.jackson.jackson
|
||||
import io.ktor.response.respond
|
||||
import io.ktor.response.respondText
|
||||
import io.ktor.routing.get
|
||||
import io.ktor.routing.route
|
||||
import io.ktor.routing.routing
|
||||
import io.ktor.server.engine.embeddedServer
|
||||
import io.ktor.server.netty.Netty
|
||||
import org.leafygreens.kompendium.annotations.KompendiumContact
|
||||
import org.leafygreens.kompendium.annotations.KompendiumInfo
|
||||
import org.leafygreens.kompendium.annotations.KompendiumLicense
|
||||
import org.leafygreens.kompendium.annotations.KompendiumModule
|
||||
import org.leafygreens.kompendium.annotations.KompendiumServers
|
||||
import org.leafygreens.kompendium.Kompendium.notarizedGet
|
||||
import org.leafygreens.kompendium.Kompendium.notarizedPost
|
||||
import org.leafygreens.kompendium.Kompendium.notarizedPut
|
||||
import org.leafygreens.kompendium.Kompendium.openApiSpec
|
||||
import org.leafygreens.kompendium.annotations.KompendiumField
|
||||
import org.leafygreens.kompendium.models.meta.MethodInfo
|
||||
import org.leafygreens.kompendium.models.oas.OpenApiSpecInfo
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testIdGetInfo
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSingleGetInfo
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePostInfo
|
||||
import org.leafygreens.kompendium.playground.KompendiumTOC.testSinglePutInfo
|
||||
|
||||
@KompendiumInfo(
|
||||
title = "Test API",
|
||||
version = "0.0.1",
|
||||
description = "An API for testing"
|
||||
)
|
||||
@KompendiumContact(
|
||||
name = "Homer Simpson",
|
||||
url = "https://en.wikipedia.org/wiki/The_Simpsons",
|
||||
email = "chunkylover53@aol.com"
|
||||
)
|
||||
@KompendiumLicense(
|
||||
name = "DOH",
|
||||
url = "https://opensource.org/licenses/DOH"
|
||||
)
|
||||
@KompendiumServers(urls = [ "https://thesimpsonsquoteapi.glitch.me/quotes" ])
|
||||
fun main() {
|
||||
embeddedServer(
|
||||
Netty,
|
||||
port = 8080,
|
||||
port = 8081,
|
||||
module = Application::mainModule
|
||||
).start(wait = true)
|
||||
}
|
||||
|
||||
@KompendiumModule
|
||||
data class A(val a: String, val aa: Int, val aaa: List<Long>)
|
||||
data class B(
|
||||
@KompendiumField(name = "AYY")
|
||||
val a: A,
|
||||
val b: Double,
|
||||
)
|
||||
data class C(val c: String)
|
||||
|
||||
data class D(val a: A, val b: B, val c: C)
|
||||
|
||||
object KompendiumTOC {
|
||||
val testIdGetInfo = MethodInfo("Get Test", "Test for getting", tags = setOf("test", "example", "get"))
|
||||
val testSingleGetInfo = MethodInfo("Another get test", "testing more")
|
||||
val testSinglePostInfo = MethodInfo("Test post endpoint", "Post your tests here!")
|
||||
val testSinglePutInfo = MethodInfo("Test put endpoint", "Put your tests here!")
|
||||
}
|
||||
|
||||
fun Application.mainModule() {
|
||||
install(ContentNegotiation) {
|
||||
jackson()
|
||||
}
|
||||
routing {
|
||||
route("/") {
|
||||
route("/test") {
|
||||
route("/{id}") {
|
||||
notarizedGet(testIdGetInfo) {
|
||||
call.respondText("get by id")
|
||||
}
|
||||
}
|
||||
route("/single") {
|
||||
notarizedGet(testSingleGetInfo) {
|
||||
call.respondText("get single")
|
||||
}
|
||||
notarizedPost<A, B, C>(testSinglePostInfo) {
|
||||
call.respondText("test post")
|
||||
}
|
||||
notarizedPut<A, B, D>(testSinglePutInfo) {
|
||||
call.respondText { "hey" }
|
||||
}
|
||||
}
|
||||
}
|
||||
route("/openapi.json") {
|
||||
get {
|
||||
call.respondText("hi")
|
||||
call.respond(openApiSpec.copy(
|
||||
info = OpenApiSpecInfo(
|
||||
title = "Test API",
|
||||
version = "1.3.3.7",
|
||||
description = "An amazing, fully-ish 😉 generated API spec"
|
||||
)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,33 +0,0 @@
|
||||
plugins {
|
||||
kotlin("kapt")
|
||||
`java-library`
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
implementation(projects.kompendiumCore)
|
||||
implementation("com.google.auto.service:auto-service:1.0")
|
||||
kapt("com.google.auto.service:auto-service:1.0")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test")
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
|
||||
}
|
||||
//
|
||||
//publishing {
|
||||
// repositories {
|
||||
// maven {
|
||||
// name = "GithubPackages"
|
||||
// url = uri("https://maven.pkg.github.com/lg-backbone/kompendium")
|
||||
// credentials {
|
||||
// username = System.getenv("GITHUB_ACTOR")
|
||||
// password = System.getenv("GITHUB_TOKEN")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// publications {
|
||||
// create<MavenPublication>("kompendium") {
|
||||
// from(components["kotlin"])
|
||||
// }
|
||||
// }
|
||||
//}
|
@ -1,112 +0,0 @@
|
||||
package org.leafygreens.kompendium.processor
|
||||
|
||||
import com.google.auto.service.AutoService
|
||||
import java.net.URI
|
||||
import javax.annotation.processing.AbstractProcessor
|
||||
import javax.annotation.processing.Processor
|
||||
import javax.annotation.processing.RoundEnvironment
|
||||
import javax.annotation.processing.SupportedSourceVersion
|
||||
import javax.lang.model.SourceVersion
|
||||
import javax.lang.model.element.TypeElement
|
||||
import org.leafygreens.kompendium.Kompendium
|
||||
import org.leafygreens.kompendium.annotations.KompendiumContact
|
||||
import org.leafygreens.kompendium.annotations.KompendiumInfo
|
||||
import org.leafygreens.kompendium.annotations.KompendiumLicense
|
||||
import org.leafygreens.kompendium.annotations.KompendiumModule
|
||||
import org.leafygreens.kompendium.annotations.KompendiumServers
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecInfoContact
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecInfoLicense
|
||||
import org.leafygreens.kompendium.models.OpenApiSpecServer
|
||||
|
||||
@AutoService(Processor::class)
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
class KompendiumProcessor : AbstractProcessor() {
|
||||
|
||||
private val kompendium = Kompendium()
|
||||
|
||||
companion object {
|
||||
const val KAPT_KOTLIN_GENERATED_OPTION_NAME = "kapt.kotlin.generated"
|
||||
}
|
||||
|
||||
override fun getSupportedAnnotationTypes(): MutableSet<String> {
|
||||
return mutableSetOf(
|
||||
KompendiumInfo::class.java.canonicalName,
|
||||
KompendiumContact::class.java.canonicalName,
|
||||
KompendiumLicense::class.java.canonicalName,
|
||||
KompendiumServers::class.java.canonicalName,
|
||||
KompendiumModule::class.java.canonicalName
|
||||
)
|
||||
}
|
||||
|
||||
// TODO Throw error if more than 1 info, contact, etc.?
|
||||
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean {
|
||||
roundEnv?.getElementsAnnotatedWith(KompendiumInfo::class.java)?.forEach {
|
||||
val info = it.getAnnotation(KompendiumInfo::class.java)
|
||||
processKompendiumInfo(info)
|
||||
}
|
||||
roundEnv?.getElementsAnnotatedWith(KompendiumContact::class.java)?.forEach {
|
||||
val contact = it.getAnnotation(KompendiumContact::class.java)
|
||||
processKompendiumContact(contact)
|
||||
}
|
||||
roundEnv?.getElementsAnnotatedWith(KompendiumLicense::class.java)?.forEach {
|
||||
val license = it.getAnnotation(KompendiumLicense::class.java)
|
||||
processKompendiumLicense(license)
|
||||
}
|
||||
roundEnv?.getElementsAnnotatedWith(KompendiumServers::class.java)?.forEach {
|
||||
val servers = it.getAnnotation(KompendiumServers::class.java)
|
||||
processKompendiumServers(servers)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun processKompendiumInfo(info: KompendiumInfo) {
|
||||
kompendium.spec.info?.apply {
|
||||
this.title = info.title
|
||||
this.version = info.version
|
||||
info.description.blankToNull()?.let { desc ->
|
||||
this.description = desc
|
||||
}
|
||||
info.termsOfService.blankToNull()?.let { tos ->
|
||||
this.termsOfService = URI(tos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processKompendiumContact(contact: KompendiumContact) {
|
||||
kompendium.spec.info?.apply {
|
||||
this.contact = OpenApiSpecInfoContact(
|
||||
name = contact.name
|
||||
).apply {
|
||||
contact.url.blankToNull()?.let { url ->
|
||||
this.url = URI(url)
|
||||
}
|
||||
contact.email.blankToNull()?.let { email ->
|
||||
this.email = email
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processKompendiumLicense(license: KompendiumLicense) {
|
||||
kompendium.spec.info?.apply {
|
||||
this.license = OpenApiSpecInfoLicense(
|
||||
name = license.name
|
||||
).apply {
|
||||
license.url.blankToNull()?.let { url ->
|
||||
this.url = URI(url)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processKompendiumServers(servers: KompendiumServers) {
|
||||
servers.urls.forEach { url ->
|
||||
kompendium.spec.servers?.add(OpenApiSpecServer(URI(url)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.blankToNull(): String? = ifBlank {
|
||||
null
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
rootProject.name = "kompendium"
|
||||
include("kompendium-core")
|
||||
include("kompendium-processor")
|
||||
include("kompendium-playground")
|
||||
|
||||
// Feature Previews
|
||||
|
Reference in New Issue
Block a user