fix: formatting custom SimpleSchema (fixes #198) (#208)

This commit is contained in:
Gennadi Kudrjavtsev
2022-02-25 15:52:01 +02:00
committed by GitHub
parent f919a6a4b1
commit 31fa5e40c8
11 changed files with 214 additions and 22 deletions

View File

@ -2,6 +2,7 @@
## Unreleased ## Unreleased
- Fixed support Location classes located in other non-location classes - Fixed support Location classes located in other non-location classes
- Fixed formatting of a custom SimpleSchema
### Added ### Added

View File

@ -82,22 +82,18 @@ fun FormattedSchema.scanForConstraints(prop: KProperty1<*, *>): FormattedSchema
} }
fun SimpleSchema.scanForConstraints(prop: KProperty1<*, *>): SimpleSchema { fun SimpleSchema.scanForConstraints(prop: KProperty1<*, *>): SimpleSchema {
val minLength = prop.findAnnotation<MinLength>() val minLength = prop.findAnnotation<MinLength>()?.length ?: this.minLength
val maxLength = prop.findAnnotation<MaxLength>() val maxLength = prop.findAnnotation<MaxLength>()?.length ?: this.maxLength
val pattern = prop.findAnnotation<Pattern>() val pattern = prop.findAnnotation<Pattern>()?.pattern ?: this.pattern
val format = prop.findAnnotation<Format>() val format = prop.findAnnotation<Format>()?.format ?: this.format
val nullable = if (prop.returnType.isMarkedNullable) true else this.nullable
var schema = this return copy(
nullable = nullable,
if (prop.returnType.isMarkedNullable) { minLength = minLength,
schema = schema.copy(nullable = true) maxLength = maxLength,
} pattern = pattern,
format = format
return schema.copy(
minLength = minLength?.length,
maxLength = maxLength?.length,
pattern = pattern?.pattern,
format = format?.format
) )
} }

View File

@ -11,6 +11,7 @@ import io.bkbn.kompendium.core.fixtures.docs
import io.bkbn.kompendium.core.util.complexType import io.bkbn.kompendium.core.util.complexType
import io.bkbn.kompendium.core.util.constrainedDoubleInfo import io.bkbn.kompendium.core.util.constrainedDoubleInfo
import io.bkbn.kompendium.core.util.constrainedIntInfo import io.bkbn.kompendium.core.util.constrainedIntInfo
import io.bkbn.kompendium.core.util.dateTimeString
import io.bkbn.kompendium.core.util.defaultField import io.bkbn.kompendium.core.util.defaultField
import io.bkbn.kompendium.core.util.defaultParameter import io.bkbn.kompendium.core.util.defaultParameter
import io.bkbn.kompendium.core.util.exampleParams import io.bkbn.kompendium.core.util.exampleParams
@ -59,6 +60,8 @@ import io.bkbn.kompendium.core.util.uniqueArray
import io.bkbn.kompendium.core.util.withDefaultParameter import io.bkbn.kompendium.core.util.withDefaultParameter
import io.bkbn.kompendium.core.util.withExamples import io.bkbn.kompendium.core.util.withExamples
import io.bkbn.kompendium.core.util.withOperationId import io.bkbn.kompendium.core.util.withOperationId
import io.bkbn.kompendium.oas.schema.FormattedSchema
import io.bkbn.kompendium.oas.schema.SimpleSchema
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
import io.kotest.core.spec.style.DescribeSpec import io.kotest.core.spec.style.DescribeSpec
import io.ktor.application.call import io.ktor.application.call
@ -75,6 +78,7 @@ import io.ktor.serialization.json
import io.ktor.server.testing.withTestApplication import io.ktor.server.testing.withTestApplication
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.time.Instant
class KompendiumTest : DescribeSpec({ class KompendiumTest : DescribeSpec({
describe("Notarized Open API Metadata Tests") { describe("Notarized Open API Metadata Tests") {
@ -275,6 +279,23 @@ class KompendiumTest : DescribeSpec({
openApiTestAllSerializers("min_max_free_form.json") { minMaxFreeForm() } openApiTestAllSerializers("min_max_free_form.json") { minMaxFreeForm() }
} }
} }
describe("Formats") {
it("Can set a format on a simple type schema") {
openApiTestAllSerializers("formatted_date_time_string.json", { dateTimeString() }) {
addCustomTypeSchema(Instant::class, SimpleSchema("string", format = "date-time"))
}
}
it("Can set a format on formatted type schema") {
openApiTestAllSerializers("formatted_date_time_string.json", { dateTimeString() }) {
addCustomTypeSchema(Instant::class, FormattedSchema("date-time", "string"))
}
}
it("Can bypass a format on a simple type schema") {
openApiTestAllSerializers("formatted_no_format_string.json", { dateTimeString() }) {
addCustomTypeSchema(Instant::class, SimpleSchema("string"))
}
}
}
describe("Free Form") { describe("Free Form") {
it("Can create a free-form field") { it("Can create a free-form field") {
openApiTestAllSerializers("free_form_object.json") { freeFormObject() } openApiTestAllSerializers("free_form_object.json") { freeFormObject() }

View File

@ -9,6 +9,7 @@ import io.bkbn.kompendium.core.Notarized.notarizedPost
import io.bkbn.kompendium.core.Notarized.notarizedPut import io.bkbn.kompendium.core.Notarized.notarizedPut
import io.bkbn.kompendium.core.fixtures.Bibbity import io.bkbn.kompendium.core.fixtures.Bibbity
import io.bkbn.kompendium.core.fixtures.ComplexGibbit import io.bkbn.kompendium.core.fixtures.ComplexGibbit
import io.bkbn.kompendium.core.fixtures.DateTimeString
import io.bkbn.kompendium.core.fixtures.DefaultParameter import io.bkbn.kompendium.core.fixtures.DefaultParameter
import io.bkbn.kompendium.core.fixtures.Gibbity import io.bkbn.kompendium.core.fixtures.Gibbity
import io.bkbn.kompendium.core.fixtures.Mysterious import io.bkbn.kompendium.core.fixtures.Mysterious
@ -19,6 +20,7 @@ import io.bkbn.kompendium.core.fixtures.TestNested
import io.bkbn.kompendium.core.fixtures.TestRequest import io.bkbn.kompendium.core.fixtures.TestRequest
import io.bkbn.kompendium.core.fixtures.TestResponse import io.bkbn.kompendium.core.fixtures.TestResponse
import io.bkbn.kompendium.core.fixtures.TestResponseInfo import io.bkbn.kompendium.core.fixtures.TestResponseInfo
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.dateTimeString
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.defaultField import io.bkbn.kompendium.core.fixtures.TestResponseInfo.defaultField
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.defaultParam import io.bkbn.kompendium.core.fixtures.TestResponseInfo.defaultParam
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.formattedParam import io.bkbn.kompendium.core.fixtures.TestResponseInfo.formattedParam
@ -38,6 +40,7 @@ import io.ktor.response.respond
import io.ktor.response.respondText import io.ktor.response.respondText
import io.ktor.routing.route import io.ktor.routing.route
import io.ktor.routing.routing import io.ktor.routing.routing
import java.time.Instant
fun Application.notarizedGetWithNotarizedException() { fun Application.notarizedGetWithNotarizedException() {
routing { routing {
@ -526,6 +529,16 @@ fun Application.regexString() {
} }
} }
fun Application.dateTimeString() {
routing {
route("/test/date_time_format") {
notarizedGet(dateTimeString) {
call.respond(HttpStatusCode.OK, DateTimeString(Instant.now()))
}
}
}
}
fun Application.minMaxArray() { fun Application.minMaxArray() {
routing { routing {
route("/test/required_param") { route("/test/required_param") {

View 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/date_time_format": {
"get": {
"tags": [],
"summary": "Date time string test",
"description": "Cool stuff",
"parameters": [],
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DateTimeString"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"DateTimeString": {
"properties": {
"a": {
"type": "string",
"format": "date-time"
}
},
"required": [
"a"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -0,0 +1,69 @@
{
"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/date_time_format": {
"get": {
"tags": [],
"summary": "Date time string test",
"description": "Cool stuff",
"parameters": [],
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/DateTimeString"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"DateTimeString": {
"properties": {
"a": {
"type": "string"
}
},
"required": [
"a"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -1,5 +1,6 @@
package io.bkbn.kompendium.core.fixtures package io.bkbn.kompendium.core.fixtures
import io.bkbn.kompendium.core.Kompendium
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
import io.kotest.assertions.json.shouldEqualJson import io.kotest.assertions.json.shouldEqualJson
import io.kotest.assertions.ktor.shouldHaveStatus import io.kotest.assertions.ktor.shouldHaveStatus
@ -53,19 +54,27 @@ object TestHelpers {
* @param moduleFunction Initializer for the application to allow tests to pass the required Ktor modules * @param moduleFunction Initializer for the application to allow tests to pass the required Ktor modules
*/ */
fun openApiTestAllSerializers(snapshotName: String, moduleFunction: Application.() -> Unit) { fun openApiTestAllSerializers(snapshotName: String, moduleFunction: Application.() -> Unit) {
openApiTest(snapshotName, SupportedSerializer.KOTLINX, moduleFunction) openApiTestAllSerializers(snapshotName, moduleFunction) {}
openApiTest(snapshotName, SupportedSerializer.JACKSON, moduleFunction) }
openApiTest(snapshotName, SupportedSerializer.GSON, moduleFunction)
fun openApiTestAllSerializers(
snapshotName: String, moduleFunction: Application.() -> Unit,
kompendiumConfigurer: Kompendium.Configuration.() -> Unit
) {
openApiTest(snapshotName, SupportedSerializer.KOTLINX, moduleFunction, kompendiumConfigurer)
openApiTest(snapshotName, SupportedSerializer.JACKSON, moduleFunction, kompendiumConfigurer)
openApiTest(snapshotName, SupportedSerializer.GSON, moduleFunction, kompendiumConfigurer)
} }
private fun openApiTest( private fun openApiTest(
snapshotName: String, snapshotName: String,
serializer: SupportedSerializer, serializer: SupportedSerializer,
moduleFunction: Application.() -> Unit moduleFunction: Application.() -> Unit,
kompendiumConfigurer: Kompendium.Configuration.() -> Unit
) { ) {
withApplication(createTestEnvironment()) { withApplication(createTestEnvironment()) {
moduleFunction(application.apply { moduleFunction(application.apply {
kompendium() kompendium(kompendiumConfigurer)
docs() docs()
when (serializer) { when (serializer) {
SupportedSerializer.KOTLINX -> { SupportedSerializer.KOTLINX -> {

View File

@ -21,6 +21,7 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import java.math.BigDecimal import java.math.BigDecimal
import java.math.BigInteger import java.math.BigInteger
import java.time.Instant
import java.util.UUID import java.util.UUID
data class TestSimpleModel(val a: String, val b: Int) data class TestSimpleModel(val a: String, val b: Int)
@ -106,6 +107,10 @@ data class RegexString(
val a: String val a: String
) )
data class DateTimeString(
val a: Instant
)
data class MinMaxArray( data class MinMaxArray(
@MinItems(1) @MinItems(1)
@MaxItems(10) @MaxItems(10)

View File

@ -27,8 +27,9 @@ fun Application.jacksonConfigModule() {
} }
} }
fun Application.kompendium() { fun Application.kompendium(configurer: Kompendium.Configuration.() -> Unit = {}) {
install(Kompendium) { install(Kompendium) {
spec = defaultSpec() spec = defaultSpec()
configurer()
} }
} }

View File

@ -226,6 +226,13 @@ object TestResponseInfo {
responseInfo = simpleOkResponse() responseInfo = simpleOkResponse()
) )
val dateTimeString = GetInfo<Unit, DateTimeString>(
summary = "Date time string test",
description = "Cool stuff",
responseInfo = simpleOkResponse()
)
val minMaxArray = GetInfo<Unit, MinMaxArray>( val minMaxArray = GetInfo<Unit, MinMaxArray>(
summary = "required param", summary = "required param",
description = "Cool stuff", description = "Cool stuff",

View File

@ -43,7 +43,7 @@ private fun Application.mainModule() {
install(Kompendium) { install(Kompendium) {
spec = Util.baseSpec spec = Util.baseSpec
// Tells Kompendium how to handle a specific type // Tells Kompendium how to handle a specific type
addCustomTypeSchema(Instant::class, SimpleSchema("string")) addCustomTypeSchema(Instant::class, SimpleSchema("string", format = "date-time"))
} }
routing { routing {
redoc(pageTitle = "Custom overridden type Docs") redoc(pageTitle = "Custom overridden type Docs")