fix: nested class name support

This commit is contained in:
Ryan Brink
2022-08-16 12:08:35 -06:00
committed by GitHub
parent 8ae74705ba
commit 0d4b1ddd03
7 changed files with 98 additions and 44 deletions

View File

@ -4,32 +4,18 @@ import io.bkbn.kompendium.core.attribute.KompendiumAttributes
import io.bkbn.kompendium.core.metadata.DeleteInfo import io.bkbn.kompendium.core.metadata.DeleteInfo
import io.bkbn.kompendium.core.metadata.GetInfo import io.bkbn.kompendium.core.metadata.GetInfo
import io.bkbn.kompendium.core.metadata.HeadInfo import io.bkbn.kompendium.core.metadata.HeadInfo
import io.bkbn.kompendium.core.metadata.MethodInfo
import io.bkbn.kompendium.core.metadata.MethodInfoWithRequest
import io.bkbn.kompendium.core.metadata.OptionsInfo import io.bkbn.kompendium.core.metadata.OptionsInfo
import io.bkbn.kompendium.core.metadata.PatchInfo import io.bkbn.kompendium.core.metadata.PatchInfo
import io.bkbn.kompendium.core.metadata.PostInfo import io.bkbn.kompendium.core.metadata.PostInfo
import io.bkbn.kompendium.core.metadata.PutInfo import io.bkbn.kompendium.core.metadata.PutInfo
import io.bkbn.kompendium.core.metadata.ResponseInfo
import io.bkbn.kompendium.core.util.Helpers.addToSpec import io.bkbn.kompendium.core.util.Helpers.addToSpec
import io.bkbn.kompendium.core.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.core.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.core.util.SpecConfig import io.bkbn.kompendium.core.util.SpecConfig
import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
import io.bkbn.kompendium.oas.OpenApiSpec
import io.bkbn.kompendium.oas.path.Path import io.bkbn.kompendium.oas.path.Path
import io.bkbn.kompendium.oas.path.PathOperation
import io.bkbn.kompendium.oas.payload.MediaType
import io.bkbn.kompendium.oas.payload.Parameter import io.bkbn.kompendium.oas.payload.Parameter
import io.bkbn.kompendium.oas.payload.Request
import io.bkbn.kompendium.oas.payload.Response
import io.ktor.server.application.ApplicationCallPipeline import io.ktor.server.application.ApplicationCallPipeline
import io.ktor.server.application.Hook import io.ktor.server.application.Hook
import io.ktor.server.application.createRouteScopedPlugin import io.ktor.server.application.createRouteScopedPlugin
import io.ktor.server.routing.Route import io.ktor.server.routing.Route
import kotlin.reflect.KClass
import kotlin.reflect.KType
object NotarizedRoute { object NotarizedRoute {

View File

@ -10,9 +10,10 @@ import io.bkbn.kompendium.core.metadata.PatchInfo
import io.bkbn.kompendium.core.metadata.PostInfo import io.bkbn.kompendium.core.metadata.PostInfo
import io.bkbn.kompendium.core.metadata.PutInfo import io.bkbn.kompendium.core.metadata.PutInfo
import io.bkbn.kompendium.core.metadata.ResponseInfo import io.bkbn.kompendium.core.metadata.ResponseInfo
import io.bkbn.kompendium.core.plugin.NotarizedRoute
import io.bkbn.kompendium.json.schema.SchemaGenerator import io.bkbn.kompendium.json.schema.SchemaGenerator
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
import io.bkbn.kompendium.oas.OpenApiSpec import io.bkbn.kompendium.oas.OpenApiSpec
import io.bkbn.kompendium.oas.path.Path import io.bkbn.kompendium.oas.path.Path
import io.bkbn.kompendium.oas.path.PathOperation import io.bkbn.kompendium.oas.path.PathOperation
@ -24,28 +25,6 @@ import kotlin.reflect.KType
object Helpers { object Helpers {
private const val COMPONENT_SLUG = "#/components/schemas"
fun KType.getSimpleSlug(): String = when {
this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>)
else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this")
}
fun KType.getReferenceSlug(): String = when {
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}"
}
/**
* Adapts a class with type parameters into a reference friendly string
*/
private fun genericNameAdapter(type: KType, clazz: KClass<*>): String {
val classNames = type.arguments
.map { it.type?.classifier as KClass<*> }
.map { it.simpleName }
return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-")
}
fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: SpecConfig) { fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: SpecConfig) {
SchemaGenerator.fromTypeOrUnit(this.response.responseType, spec.components.schemas)?.let { schema -> SchemaGenerator.fromTypeOrUnit(this.response.responseType, spec.components.schemas)?.let { schema ->
spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema

View File

@ -20,6 +20,7 @@ import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection
import io.bkbn.kompendium.core.util.TestModules.nestedGenericMultipleParamsCollection import io.bkbn.kompendium.core.util.TestModules.nestedGenericMultipleParamsCollection
import io.bkbn.kompendium.core.util.TestModules.nestedGenericResponse import io.bkbn.kompendium.core.util.TestModules.nestedGenericResponse
import io.bkbn.kompendium.core.util.TestModules.nestedTypeName
import io.bkbn.kompendium.core.util.TestModules.nonRequiredParam import io.bkbn.kompendium.core.util.TestModules.nonRequiredParam
import io.bkbn.kompendium.core.util.TestModules.polymorphicException import io.bkbn.kompendium.core.util.TestModules.polymorphicException
import io.bkbn.kompendium.core.util.TestModules.notarizedHead import io.bkbn.kompendium.core.util.TestModules.notarizedHead
@ -200,6 +201,9 @@ class KompendiumTest : DescribeSpec({
it("Can have a nullable reference without impacting base type") { it("Can have a nullable reference without impacting base type") {
openApiTestAllSerializers("T0041__nullable_reference.json") { nullableReference() } openApiTestAllSerializers("T0041__nullable_reference.json") { nullableReference() }
} }
it("Can handle nested type names") {
openApiTestAllSerializers("T0044__nested_type_name.json") { nestedTypeName() }
}
} }
describe("Constraints") { describe("Constraints") {
// TODO Assess strategies here // TODO Assess strategies here

View File

@ -12,6 +12,7 @@ import io.bkbn.kompendium.core.fixtures.Foosy
import io.bkbn.kompendium.core.fixtures.Gibbity import io.bkbn.kompendium.core.fixtures.Gibbity
import io.bkbn.kompendium.core.fixtures.ManyThings import io.bkbn.kompendium.core.fixtures.ManyThings
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
import io.bkbn.kompendium.core.fixtures.Nested
import io.bkbn.kompendium.core.fixtures.NullableEnum import io.bkbn.kompendium.core.fixtures.NullableEnum
import io.bkbn.kompendium.core.fixtures.NullableField import io.bkbn.kompendium.core.fixtures.NullableField
import io.bkbn.kompendium.core.fixtures.Page import io.bkbn.kompendium.core.fixtures.Page
@ -599,6 +600,8 @@ object TestModules {
) )
) )
fun Routing.nestedTypeName() = basicGetGenerator<Nested.Response>()
fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>() fun Routing.simpleRecursive() = basicGetGenerator<ColumnSchema>()
private inline fun <reified T> Routing.basicGetGenerator( private inline fun <reified T> Routing.basicGetGenerator(

View File

@ -0,0 +1,72 @@
{
"openapi": "3.1.0",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "Test API",
"version": "1.33.7",
"description": "An amazing, fully-ish 😉 generated API spec",
"termsOfService": "https://example.com",
"contact": {
"name": "Homer Simpson",
"url": "https://gph.is/1NPUDiM",
"email": "chunkylover53@aol.com"
},
"license": {
"name": "MIT",
"url": "https://github.com/bkbnio/kompendium/blob/main/LICENSE"
}
},
"servers": [
{
"url": "https://myawesomeapi.com",
"description": "Production instance of my API"
},
{
"url": "https://staging.myawesomeapi.com",
"description": "Where the fun stuff happens"
}
],
"paths": {
"/": {
"get": {
"tags": [],
"summary": "Great Summary!",
"description": "testing more",
"parameters": [],
"responses": {
"200": {
"description": "A Successful Endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/NestedResponse"
}
}
}
}
},
"deprecated": false
},
"parameters": []
}
},
"webhooks": {},
"components": {
"schemas": {
"NestedResponse": {
"type": "object",
"properties": {
"idk": {
"type": "boolean"
}
},
"required": [
"idk"
]
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -141,3 +141,8 @@ data class ManyThings(
data class Foosy<T, K>(val test: T, val otherThing: List<K>) data class Foosy<T, K>(val test: T, val otherThing: List<K>)
data class Barzo<G>(val result: G) data class Barzo<G>(val result: G)
object Nested {
@Serializable
data class Response(val idk: Boolean)
}

View File

@ -9,21 +9,26 @@ object Helpers {
fun KType.getSimpleSlug(): String = when { fun KType.getSimpleSlug(): String = when {
this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>) this.arguments.isNotEmpty() -> genericNameAdapter(this, classifier as KClass<*>)
else -> (classifier as KClass<*>).simpleName ?: error("Could not determine simple name for $this") else -> (classifier as KClass<*>).kompendiumSlug() ?: error("Could not determine simple name for $this")
} }
fun KType.getReferenceSlug(): String = when { fun KType.getReferenceSlug(): String = when {
arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}" arguments.isNotEmpty() -> "$COMPONENT_SLUG/${genericNameAdapter(this, classifier as KClass<*>)}"
else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).simpleName}" else -> "$COMPONENT_SLUG/${(classifier as KClass<*>).kompendiumSlug()}"
}
@Suppress("ReturnCount")
private fun KClass<*>.kompendiumSlug(): String? {
if (java.packageName == "java.lang") return simpleName
if (java.packageName == "java.util") return simpleName
val pkg = java.packageName
return qualifiedName?.replace(pkg, "")?.replace(".", "")
} }
/**
* Adapts a class with type parameters into a reference friendly string
*/
private fun genericNameAdapter(type: KType, clazz: KClass<*>): String { private fun genericNameAdapter(type: KType, clazz: KClass<*>): String {
val classNames = type.arguments val classNames = type.arguments
.map { it.type?.classifier as KClass<*> } .map { it.type?.classifier as KClass<*> }
.map { it.simpleName } .map { it.kompendiumSlug() }
return classNames.joinToString(separator = "-", prefix = "${clazz.simpleName}-") return classNames.joinToString(separator = "-", prefix = "${clazz.kompendiumSlug()}-")
} }
} }