From 31fa5e40c8ae470fa9f1b40b8bd37f44631ccc77 Mon Sep 17 00:00:00 2001 From: Gennadi Kudrjavtsev Date: Fri, 25 Feb 2022 15:52:01 +0200 Subject: [PATCH] fix: formatting custom SimpleSchema (fixes #198) (#208) --- CHANGELOG.md | 1 + .../core/constraint/ConstraintScanner.kt | 26 +++---- .../io/bkbn/kompendium/core/KompendiumTest.kt | 21 ++++++ .../bkbn/kompendium/core/util/TestModules.kt | 13 ++++ .../resources/formatted_date_time_string.json | 70 +++++++++++++++++++ .../resources/formatted_no_format_string.json | 69 ++++++++++++++++++ .../kompendium/core/fixtures/TestHelpers.kt | 19 +++-- .../kompendium/core/fixtures/TestModels.kt | 5 ++ .../kompendium/core/fixtures/TestModules.kt | 3 +- .../core/fixtures/TestResponseInfo.kt | 7 ++ .../playground/CustomTypePlayground.kt | 2 +- 11 files changed, 214 insertions(+), 22 deletions(-) create mode 100644 kompendium-core/src/test/resources/formatted_date_time_string.json create mode 100644 kompendium-core/src/test/resources/formatted_no_format_string.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 738922301..d28541152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased - Fixed support Location classes located in other non-location classes +- Fixed formatting of a custom SimpleSchema ### Added diff --git a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/constraint/ConstraintScanner.kt b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/constraint/ConstraintScanner.kt index 6c30df2f3..a43c092f3 100644 --- a/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/constraint/ConstraintScanner.kt +++ b/kompendium-core/src/main/kotlin/io/bkbn/kompendium/core/constraint/ConstraintScanner.kt @@ -82,22 +82,18 @@ fun FormattedSchema.scanForConstraints(prop: KProperty1<*, *>): FormattedSchema } fun SimpleSchema.scanForConstraints(prop: KProperty1<*, *>): SimpleSchema { - val minLength = prop.findAnnotation() - val maxLength = prop.findAnnotation() - val pattern = prop.findAnnotation() - val format = prop.findAnnotation() + val minLength = prop.findAnnotation()?.length ?: this.minLength + val maxLength = prop.findAnnotation()?.length ?: this.maxLength + val pattern = prop.findAnnotation()?.pattern ?: this.pattern + val format = prop.findAnnotation()?.format ?: this.format + val nullable = if (prop.returnType.isMarkedNullable) true else this.nullable - var schema = this - - if (prop.returnType.isMarkedNullable) { - schema = schema.copy(nullable = true) - } - - return schema.copy( - minLength = minLength?.length, - maxLength = maxLength?.length, - pattern = pattern?.pattern, - format = format?.format + return copy( + nullable = nullable, + minLength = minLength, + maxLength = maxLength, + pattern = pattern, + format = format ) } diff --git a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt index 096a34bb8..6446bcfa2 100644 --- a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt +++ b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/KompendiumTest.kt @@ -11,6 +11,7 @@ import io.bkbn.kompendium.core.fixtures.docs import io.bkbn.kompendium.core.util.complexType import io.bkbn.kompendium.core.util.constrainedDoubleInfo 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.defaultParameter 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.withExamples 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.kotest.core.spec.style.DescribeSpec import io.ktor.application.call @@ -75,6 +78,7 @@ import io.ktor.serialization.json import io.ktor.server.testing.withTestApplication import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import java.time.Instant class KompendiumTest : DescribeSpec({ describe("Notarized Open API Metadata Tests") { @@ -275,6 +279,23 @@ class KompendiumTest : DescribeSpec({ 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") { it("Can create a free-form field") { openApiTestAllSerializers("free_form_object.json") { freeFormObject() } diff --git a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt index 3436d8e9e..e14fa204b 100644 --- a/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt +++ b/kompendium-core/src/test/kotlin/io/bkbn/kompendium/core/util/TestModules.kt @@ -9,6 +9,7 @@ import io.bkbn.kompendium.core.Notarized.notarizedPost import io.bkbn.kompendium.core.Notarized.notarizedPut import io.bkbn.kompendium.core.fixtures.Bibbity 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.Gibbity 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.TestResponse 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.defaultParam 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.routing.route import io.ktor.routing.routing +import java.time.Instant fun Application.notarizedGetWithNotarizedException() { 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() { routing { route("/test/required_param") { diff --git a/kompendium-core/src/test/resources/formatted_date_time_string.json b/kompendium-core/src/test/resources/formatted_date_time_string.json new file mode 100644 index 000000000..fd8d44e58 --- /dev/null +++ b/kompendium-core/src/test/resources/formatted_date_time_string.json @@ -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": [] +} diff --git a/kompendium-core/src/test/resources/formatted_no_format_string.json b/kompendium-core/src/test/resources/formatted_no_format_string.json new file mode 100644 index 000000000..6d9309755 --- /dev/null +++ b/kompendium-core/src/test/resources/formatted_no_format_string.json @@ -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": [] +} diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt index 0b5163f29..817d1ae5c 100644 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt +++ b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestHelpers.kt @@ -1,5 +1,6 @@ package io.bkbn.kompendium.core.fixtures +import io.bkbn.kompendium.core.Kompendium import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule import io.kotest.assertions.json.shouldEqualJson 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 */ fun openApiTestAllSerializers(snapshotName: String, moduleFunction: Application.() -> Unit) { - openApiTest(snapshotName, SupportedSerializer.KOTLINX, moduleFunction) - openApiTest(snapshotName, SupportedSerializer.JACKSON, moduleFunction) - openApiTest(snapshotName, SupportedSerializer.GSON, moduleFunction) + openApiTestAllSerializers(snapshotName, 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( snapshotName: String, serializer: SupportedSerializer, - moduleFunction: Application.() -> Unit + moduleFunction: Application.() -> Unit, + kompendiumConfigurer: Kompendium.Configuration.() -> Unit ) { withApplication(createTestEnvironment()) { moduleFunction(application.apply { - kompendium() + kompendium(kompendiumConfigurer) docs() when (serializer) { SupportedSerializer.KOTLINX -> { diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt index cbff7f4ee..14f30d89c 100644 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt +++ b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModels.kt @@ -21,6 +21,7 @@ import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import java.math.BigDecimal import java.math.BigInteger +import java.time.Instant import java.util.UUID data class TestSimpleModel(val a: String, val b: Int) @@ -106,6 +107,10 @@ data class RegexString( val a: String ) +data class DateTimeString( + val a: Instant +) + data class MinMaxArray( @MinItems(1) @MaxItems(10) diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModules.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModules.kt index ec9ec6e49..b9cf20add 100644 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModules.kt +++ b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestModules.kt @@ -27,8 +27,9 @@ fun Application.jacksonConfigModule() { } } -fun Application.kompendium() { +fun Application.kompendium(configurer: Kompendium.Configuration.() -> Unit = {}) { install(Kompendium) { spec = defaultSpec() + configurer() } } diff --git a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt index 03a2a1da9..b6998923f 100644 --- a/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt +++ b/kompendium-core/src/testFixtures/kotlin/io/bkbn/kompendium/core/fixtures/TestResponseInfo.kt @@ -226,6 +226,13 @@ object TestResponseInfo { responseInfo = simpleOkResponse() ) + + val dateTimeString = GetInfo( + summary = "Date time string test", + description = "Cool stuff", + responseInfo = simpleOkResponse() + ) + val minMaxArray = GetInfo( summary = "required param", description = "Cool stuff", diff --git a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt index 1c4fe6860..d7e5e428c 100644 --- a/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt +++ b/kompendium-playground/src/main/kotlin/io/bkbn/kompendium/playground/CustomTypePlayground.kt @@ -43,7 +43,7 @@ private fun Application.mainModule() { install(Kompendium) { spec = Util.baseSpec // Tells Kompendium how to handle a specific type - addCustomTypeSchema(Instant::class, SimpleSchema("string")) + addCustomTypeSchema(Instant::class, SimpleSchema("string", format = "date-time")) } routing { redoc(pageTitle = "Custom overridden type Docs")