From a7505483c458d16984521f1d2f88829389877371 Mon Sep 17 00:00:00 2001
From: Ryan Brink <5607577+rgbrizzlehizzle@users.noreply.github.com>
Date: Mon, 12 Apr 2021 21:15:10 -0400
Subject: [PATCH] lots of stuff, no time to s-plain (#5)
---
.tool-versions | 1 +
CHANGELOG.md | 7 +
README.md | 52 +++++++-
gradle/wrapper/gradle-wrapper.properties | 2 +-
kompendium-core/build.gradle.kts | 1 +
.../org/leafygreens/kompendium/Kompendium.kt | 124 +++++++++++++++++-
.../kompendium/annotations/KompendiumField.kt | 5 +
.../annotations/KompendiumInternal.kt | 18 +++
.../kompendium/models/OpenApiSpec.kt | 14 --
.../models/OpenApiSpecComponents.kt | 6 -
.../kompendium/models/OpenApiSpecPathItem.kt | 16 ---
.../models/OpenApiSpecPathItemOperation.kt | 19 ---
.../kompendium/models/meta/MethodInfo.kt | 3 +
.../kompendium/models/oas/OpenApiSpec.kt | 14 ++
.../models/oas/OpenApiSpecComponentSchema.kt | 14 ++
.../models/oas/OpenApiSpecComponents.kt | 7 +
.../OpenApiSpecExternalDocumentation.kt | 2 +-
.../models/{ => oas}/OpenApiSpecInfo.kt | 2 +-
.../{ => oas}/OpenApiSpecInfoContact.kt | 2 +-
.../{ => oas}/OpenApiSpecInfoLicense.kt | 2 +-
.../models/{ => oas}/OpenApiSpecLink.kt | 2 +-
.../models/{ => oas}/OpenApiSpecMediaType.kt | 2 +-
.../models/{ => oas}/OpenApiSpecOAuthFlow.kt | 2 +-
.../models/{ => oas}/OpenApiSpecOAuthFlows.kt | 2 +-
.../models/oas/OpenApiSpecPathItem.kt | 14 ++
.../oas/OpenApiSpecPathItemOperation.kt | 19 +++
.../{ => oas}/OpenApiSpecReferencable.kt | 16 +--
.../models/{ => oas}/OpenApiSpecSchema.kt | 16 +--
.../models/{ => oas}/OpenApiSpecServer.kt | 2 +-
.../{ => oas}/OpenApiSpecServerVariable.kt | 2 +-
.../models/{ => oas}/OpenApiSpecTag.kt | 2 +-
.../leafygreens/kompendium/util/Helpers.kt | 21 +++
.../leafygreens/kompendium/KompendiumTest.kt | 3 +-
.../leafygreens/kompendium/util/TestData.kt | 47 +++----
.../src/test/resources/petstore.json | 2 +
kompendium-playground/build.gradle.kts | 2 -
.../leafygreens/kompendium/playground/Main.kt | 86 ++++++++----
kompendium-processor/build.gradle.kts | 33 -----
.../processor/KompendiumProcessor.kt | 112 ----------------
settings.gradle.kts | 1 -
40 files changed, 410 insertions(+), 287 deletions(-)
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumField.kt
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumInternal.kt
delete mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpec.kt
delete mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecComponents.kt
delete mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecPathItem.kt
delete mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpecPathItemOperation.kt
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/meta/MethodInfo.kt
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpec.kt
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecComponentSchema.kt
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecComponents.kt
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecExternalDocumentation.kt (70%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecInfo.kt (85%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecInfoContact.kt (77%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecInfoLicense.kt (68%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecLink.kt (87%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecMediaType.kt (91%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecOAuthFlow.kt (80%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecOAuthFlows.kt (62%)
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecPathItem.kt
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/oas/OpenApiSpecPathItemOperation.kt
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecReferencable.kt (71%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecSchema.kt (62%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecServer.kt (78%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecServerVariable.kt (75%)
rename kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/{ => oas}/OpenApiSpecTag.kt (76%)
create mode 100644 kompendium-core/src/main/kotlin/org/leafygreens/kompendium/util/Helpers.kt
delete mode 100644 kompendium-processor/build.gradle.kts
delete mode 100644 kompendium-processor/src/main/kotlin/org/leafygreens/kompendium/processor/KompendiumProcessor.kt
diff --git a/.tool-versions b/.tool-versions
index a54bc706e..b76921ca0 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,2 +1,3 @@
kotlin 1.5.0-M2
java openjdk-14.0.1
+gradle 7.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16bbfd887..554f58abc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
diff --git a/README.md b/README.md
index abf8d2e0d..f68ca2027 100644
--- a/README.md
+++ b/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(testSinglePostInfo) {
+ call.respondText("test post")
+ }
+ notarizedPut(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"
+ )
+ ))
+ }
+ }
+ }
+}
+```
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 352ddde00..f371643ee 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -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
diff --git a/kompendium-core/build.gradle.kts b/kompendium-core/build.gradle.kts
index 665d3ea55..50c08531f 100644
--- a/kompendium-core/build.gradle.kts
+++ b/kompendium-core/build.gradle.kts
@@ -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")
diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt
index a5459bd6a..43e813eb3 100644
--- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt
+++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/Kompendium.kt
@@ -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): 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 Route.notarizedPost(
+ info: MethodInfo,
+ noinline body: PipelineInterceptor
+ ): Route = generateComponentSchemas(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 Route.notarizedPut(
+ info: MethodInfo,
+ noinline body: PipelineInterceptor,
+ ): Route = generateComponentSchemas(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 generateComponentSchemas(
+ info: MethodInfo,
+ noinline body: PipelineInterceptor,
+ block: (MethodInfo, PipelineInterceptor) -> 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 {
+ 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()
+ 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)
+ }
}
diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumField.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumField.kt
new file mode 100644
index 000000000..cc64f8da0
--- /dev/null
+++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumField.kt
@@ -0,0 +1,5 @@
+package org.leafygreens.kompendium.annotations
+
+@Retention(AnnotationRetention.RUNTIME)
+@Target(AnnotationTarget.PROPERTY)
+annotation class KompendiumField(val name: String)
diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumInternal.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumInternal.kt
new file mode 100644
index 000000000..2146e2350
--- /dev/null
+++ b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/annotations/KompendiumInternal.kt
@@ -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
diff --git a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpec.kt b/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpec.kt
deleted file mode 100644
index 7872cea12..000000000
--- a/kompendium-core/src/main/kotlin/org/leafygreens/kompendium/models/OpenApiSpec.kt
+++ /dev/null
@@ -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? = null,
- val paths: MutableMap? = null,
- val components: OpenApiSpecComponents? = null,
- // todo needs to reference objects in the components -> security scheme 🤔
- val security: List