lots of stuff, no time to s-plain (#5)

This commit is contained in:
Ryan Brink
2021-04-12 21:15:10 -04:00
committed by GitHub
parent 492933d728
commit a7505483c4
40 changed files with 410 additions and 287 deletions

View File

@ -1,2 +1,3 @@
kotlin 1.5.0-M2
java openjdk-14.0.1
gradle 7.0

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package org.leafygreens.kompendium.annotations
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
annotation class KompendiumField(val name: String)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
import java.net.URI

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
import java.net.URI

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
import java.net.URI

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
import java.net.URI

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
data class OpenApiSpecOAuthFlows(
val implicit: OpenApiSpecOAuthFlow?,

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
sealed class OpenApiSpecReferencable

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
sealed class OpenApiSpecSchema

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
import java.net.URI

View File

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

View File

@ -1,4 +1,4 @@
package org.leafygreens.kompendium.models
package org.leafygreens.kompendium.models.oas
data class OpenApiSpecTag(
val name: String,

View File

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

View File

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

View File

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

View File

@ -153,6 +153,7 @@
}
},
"components" : {
"schemas" : { },
"securitySchemes" : {
"petstore_auth" : {
"type" : "oauth2",
@ -173,6 +174,7 @@
}
}
},
"security" : [ ],
"tags" : [ {
"name" : "pet",
"description" : "Everything about your Pets",

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
rootProject.name = "kompendium"
include("kompendium-core")
include("kompendium-processor")
include("kompendium-playground")
// Feature Previews