Skip to content

Commit

Permalink
fix: free form annotation can be applied to top level type (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
brizzbuzz authored Mar 5, 2022
1 parent 2364aaa commit 5fe9fff
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 31 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@

## Released

## [2.3.1] - March 5th, 2022
### Changed
- Can now apply `@FreeFormObject` to top level types

## [2.3.0] - March 1st, 2022
### Added
- Brand new SwaggerUI support as a KTor plugin with WebJar under the hood and flexible configuration
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Kompendium
project.version=2.3.0
project.version=2.3.1
# Kotlin
kotlin.code.style=official
# Gradle
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package io.bkbn.kompendium.annotations

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.PROPERTY)
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS)
annotation class FreeFormObject
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import kotlin.reflect.KProperty1
import kotlin.reflect.KType
import kotlin.reflect.full.createType
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.hasAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
import org.slf4j.LoggerFactory
Expand All @@ -44,19 +45,24 @@ object ObjectHandler : SchemaHandler {
// Only analyze if component has not already been stored in the cache
if (!cache.containsKey(slug)) {
logger.debug("$slug was not found in cache, generating now")
// 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())
val typeMap: TypeMap = clazz.typeParameters.zip(type.arguments).toMap()
val fieldMap = clazz.generateFieldMap(typeMap, cache)
.plus(clazz.generateUndeclaredFieldMap(cache))
.mapValues { (_, fieldSchema) ->
val fieldSlug = cache.filter { (_, vv) -> vv == fieldSchema }.keys.firstOrNull()
postProcessSchema(fieldSchema, fieldSlug)
}
logger.debug("$slug contains $fieldMap")
val schema = ObjectSchema(fieldMap).adjustForRequiredParams(clazz)
logger.debug("$slug schema: $schema")
cache[slug] = schema
// 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
cache[type.getSimpleSlug()] = ReferencedSchema(type.getReferenceSlug())
val typeMap: TypeMap = clazz.typeParameters.zip(type.arguments).toMap()
val fieldMap = clazz.generateFieldMap(typeMap, cache)
.plus(clazz.generateUndeclaredFieldMap(cache))
.mapValues { (_, fieldSchema) ->
val fieldSlug = cache.filter { (_, vv) -> vv == fieldSchema }.keys.firstOrNull()
postProcessSchema(fieldSchema, fieldSlug)
}
logger.debug("$slug contains $fieldMap")
val schema = ObjectSchema(fieldMap).adjustForRequiredParams(clazz)
logger.debug("$slug schema: $schema")
cache[slug] = schema
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import io.bkbn.kompendium.core.util.exampleParams
import io.bkbn.kompendium.core.util.exclusiveMinMax
import io.bkbn.kompendium.core.util.formattedParam
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.genericPolymorphicResponse
import io.bkbn.kompendium.core.util.genericPolymorphicResponseMultipleImpls
Expand Down Expand Up @@ -302,6 +303,9 @@ class KompendiumTest : DescribeSpec({
}
describe("Free Form") {
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() }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
routing {
route("/test/required_param") {
Expand Down
70 changes: 70 additions & 0 deletions kompendium-core/src/test/resources/free_form_field.json
Original file line number Diff line number Diff line change
@@ -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": "[email protected]"
},
"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": []
}
18 changes: 3 additions & 15 deletions kompendium-core/src/test/resources/free_form_object.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/FreeFormData"
"additionalProperties": true,
"type": "object"
}
}
}
Expand All @@ -49,20 +50,7 @@
}
},
"components": {
"schemas": {
"FreeFormData": {
"properties": {
"data": {
"additionalProperties": true,
"type": "object"
}
},
"required": [
"data"
],
"type": "object"
}
},
"schemas": {},
"securitySchemes": {}
},
"security": [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@ data class FreeFormData(
val data: JsonElement
)

@FreeFormObject
object AnythingGoesMan

data class MinMaxFreeForm(
@FreeFormObject
@MinProperties(5)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,13 @@ object TestResponseInfo {
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",
description = "Cool stuff",
responseInfo = simpleOkResponse()
Expand Down

0 comments on commit 5fe9fff

Please sign in to comment.