fix: free form annotation can be applied to top level type (#219)
This commit is contained in:
@ -12,6 +12,10 @@
|
|||||||
|
|
||||||
## Released
|
## Released
|
||||||
|
|
||||||
|
## [2.3.1] - March 5th, 2022
|
||||||
|
### Changed
|
||||||
|
- Can now apply `@FreeFormObject` to top level types
|
||||||
|
|
||||||
## [2.3.0] - March 1st, 2022
|
## [2.3.0] - March 1st, 2022
|
||||||
### Added
|
### Added
|
||||||
- Brand new SwaggerUI support as a KTor plugin with WebJar under the hood and flexible configuration
|
- Brand new SwaggerUI support as a KTor plugin with WebJar under the hood and flexible configuration
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Kompendium
|
# Kompendium
|
||||||
project.version=2.3.0
|
project.version=2.3.1
|
||||||
# Kotlin
|
# Kotlin
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Gradle
|
# Gradle
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
package io.bkbn.kompendium.annotations
|
package io.bkbn.kompendium.annotations
|
||||||
|
|
||||||
@Retention(AnnotationRetention.RUNTIME)
|
@Retention(AnnotationRetention.RUNTIME)
|
||||||
@Target(AnnotationTarget.PROPERTY)
|
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
|
||||||
annotation class FreeFormObject
|
annotation class FreeFormObject
|
||||||
|
@ -24,6 +24,7 @@ import kotlin.reflect.KProperty1
|
|||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.full.createType
|
import kotlin.reflect.full.createType
|
||||||
import kotlin.reflect.full.findAnnotation
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.full.hasAnnotation
|
||||||
import kotlin.reflect.full.memberProperties
|
import kotlin.reflect.full.memberProperties
|
||||||
import kotlin.reflect.jvm.javaField
|
import kotlin.reflect.jvm.javaField
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
@ -44,6 +45,10 @@ object ObjectHandler : SchemaHandler {
|
|||||||
// Only analyze if component has not already been stored in the cache
|
// Only analyze if component has not already been stored in the cache
|
||||||
if (!cache.containsKey(slug)) {
|
if (!cache.containsKey(slug)) {
|
||||||
logger.debug("$slug was not found in cache, generating now")
|
logger.debug("$slug was not found in cache, generating now")
|
||||||
|
// check if free form object
|
||||||
|
if (clazz.hasAnnotation<FreeFormObject>()) {
|
||||||
|
cache[type.getSimpleSlug()] = FreeFormSchema()
|
||||||
|
} else {
|
||||||
// todo this should be some kind of empty schema at this point, then throw error if not updated eventually
|
// todo this should be some kind of empty schema at this point, then throw error if not updated eventually
|
||||||
cache[type.getSimpleSlug()] = ReferencedSchema(type.getReferenceSlug())
|
cache[type.getSimpleSlug()] = ReferencedSchema(type.getReferenceSlug())
|
||||||
val typeMap: TypeMap = clazz.typeParameters.zip(type.arguments).toMap()
|
val typeMap: TypeMap = clazz.typeParameters.zip(type.arguments).toMap()
|
||||||
@ -59,6 +64,7 @@ object ObjectHandler : SchemaHandler {
|
|||||||
cache[slug] = schema
|
cache[slug] = schema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Associates each member with a Pair of prop name to property schema
|
* Associates each member with a Pair of prop name to property schema
|
||||||
|
@ -18,6 +18,7 @@ import io.bkbn.kompendium.core.util.exampleParams
|
|||||||
import io.bkbn.kompendium.core.util.exclusiveMinMax
|
import io.bkbn.kompendium.core.util.exclusiveMinMax
|
||||||
import io.bkbn.kompendium.core.util.formattedParam
|
import io.bkbn.kompendium.core.util.formattedParam
|
||||||
import io.bkbn.kompendium.core.util.formattedType
|
import io.bkbn.kompendium.core.util.formattedType
|
||||||
|
import io.bkbn.kompendium.core.util.freeFormField
|
||||||
import io.bkbn.kompendium.core.util.freeFormObject
|
import io.bkbn.kompendium.core.util.freeFormObject
|
||||||
import io.bkbn.kompendium.core.util.genericPolymorphicResponse
|
import io.bkbn.kompendium.core.util.genericPolymorphicResponse
|
||||||
import io.bkbn.kompendium.core.util.genericPolymorphicResponseMultipleImpls
|
import io.bkbn.kompendium.core.util.genericPolymorphicResponseMultipleImpls
|
||||||
@ -302,6 +303,9 @@ class KompendiumTest : DescribeSpec({
|
|||||||
}
|
}
|
||||||
describe("Free Form") {
|
describe("Free Form") {
|
||||||
it("Can create a free-form field") {
|
it("Can create a free-form field") {
|
||||||
|
openApiTestAllSerializers("free_form_field.json") { freeFormField() }
|
||||||
|
}
|
||||||
|
it("Can create a top-level free form object") {
|
||||||
openApiTestAllSerializers("free_form_object.json") { freeFormObject() }
|
openApiTestAllSerializers("free_form_object.json") { freeFormObject() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -579,6 +579,16 @@ fun Application.multipleOfDouble() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Application.freeFormField() {
|
||||||
|
routing {
|
||||||
|
route("/test/required_param") {
|
||||||
|
notarizedGet(TestResponseInfo.freeFormField) {
|
||||||
|
call.respond(HttpStatusCode.OK, TestResponse("hi"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Application.freeFormObject() {
|
fun Application.freeFormObject() {
|
||||||
routing {
|
routing {
|
||||||
route("/test/required_param") {
|
route("/test/required_param") {
|
||||||
|
70
kompendium-core/src/test/resources/free_form_field.json
Normal file
70
kompendium-core/src/test/resources/free_form_field.json
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"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": {
|
||||||
|
"/test/required_param": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "required param",
|
||||||
|
"description": "Cool stuff",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A successful endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/FreeFormData"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"FreeFormData": {
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -38,7 +38,8 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/FreeFormData"
|
"additionalProperties": true,
|
||||||
|
"type": "object"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -49,20 +50,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"components": {
|
"components": {
|
||||||
"schemas": {
|
"schemas": {},
|
||||||
"FreeFormData": {
|
|
||||||
"properties": {
|
|
||||||
"data": {
|
|
||||||
"additionalProperties": true,
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"required": [
|
|
||||||
"data"
|
|
||||||
],
|
|
||||||
"type": "object"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"securitySchemes": {}
|
"securitySchemes": {}
|
||||||
},
|
},
|
||||||
"security": [],
|
"security": [],
|
||||||
|
@ -141,6 +141,9 @@ data class FreeFormData(
|
|||||||
val data: JsonElement
|
val data: JsonElement
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@FreeFormObject
|
||||||
|
object AnythingGoesMan
|
||||||
|
|
||||||
data class MinMaxFreeForm(
|
data class MinMaxFreeForm(
|
||||||
@FreeFormObject
|
@FreeFormObject
|
||||||
@MinProperties(5)
|
@MinProperties(5)
|
||||||
|
@ -258,7 +258,13 @@ object TestResponseInfo {
|
|||||||
responseInfo = simpleOkResponse()
|
responseInfo = simpleOkResponse()
|
||||||
)
|
)
|
||||||
|
|
||||||
val freeFormObject = GetInfo<Unit, FreeFormData>(
|
val freeFormField = GetInfo<Unit, FreeFormData>(
|
||||||
|
summary = "required param",
|
||||||
|
description = "Cool stuff",
|
||||||
|
responseInfo = simpleOkResponse()
|
||||||
|
)
|
||||||
|
|
||||||
|
val freeFormObject = GetInfo<Unit, AnythingGoesMan>(
|
||||||
summary = "required param",
|
summary = "required param",
|
||||||
description = "Cool stuff",
|
description = "Cool stuff",
|
||||||
responseInfo = simpleOkResponse()
|
responseInfo = simpleOkResponse()
|
||||||
|
Reference in New Issue
Block a user