fix: nullable enum support (#234)
This commit is contained in:
@ -12,6 +12,10 @@
|
||||
|
||||
## Released
|
||||
|
||||
## [2.3.2] - March 30th, 2022
|
||||
### Changed
|
||||
- Fixed bug where nullable enum fields caused runtime exceptions
|
||||
|
||||
## [2.3.1] - March 5th, 2022
|
||||
### Changed
|
||||
- Can now apply `@FreeFormObject` to top level types
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Kompendium
|
||||
project.version=2.3.1
|
||||
project.version=2.3.2
|
||||
# Kotlin
|
||||
kotlin.code.style=official
|
||||
# Gradle
|
||||
|
@ -41,6 +41,7 @@ import io.bkbn.kompendium.core.util.notarizedOptionsModule
|
||||
import io.bkbn.kompendium.core.util.notarizedPatchModule
|
||||
import io.bkbn.kompendium.core.util.notarizedPostModule
|
||||
import io.bkbn.kompendium.core.util.notarizedPutModule
|
||||
import io.bkbn.kompendium.core.util.nullableEnumField
|
||||
import io.bkbn.kompendium.core.util.nullableField
|
||||
import io.bkbn.kompendium.core.util.nullableNestedObject
|
||||
import io.bkbn.kompendium.core.util.overrideFieldInfo
|
||||
@ -245,6 +246,9 @@ class KompendiumTest : DescribeSpec({
|
||||
it("Nullable fields do not lead to doom") {
|
||||
openApiTestAllSerializers("nullable_fields.json") { nullableNestedObject() }
|
||||
}
|
||||
it("Can have a nullable enum as a member field") {
|
||||
openApiTestAllSerializers("nullable_enum_field.json") { nullableEnumField() }
|
||||
}
|
||||
}
|
||||
describe("Constraints") {
|
||||
it("Can set a minimum and maximum integer value") {
|
||||
|
@ -429,6 +429,16 @@ fun Application.nullableNestedObject() {
|
||||
}
|
||||
}
|
||||
|
||||
fun Application.nullableEnumField() {
|
||||
routing {
|
||||
route("/nullable/enum") {
|
||||
notarizedGet(TestResponseInfo.nullableEnumField) {
|
||||
call.respond(HttpStatusCode.OK)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Application.constrainedIntInfo() {
|
||||
routing {
|
||||
route("/test/constrained_int") {
|
||||
|
73
kompendium-core/src/test/resources/nullable_enum_field.json
Normal file
73
kompendium-core/src/test/resources/nullable_enum_field.json
Normal file
@ -0,0 +1,73 @@
|
||||
{
|
||||
"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": {
|
||||
"/nullable/enum": {
|
||||
"get": {
|
||||
"tags": [],
|
||||
"summary": "Has a nullable enum field",
|
||||
"description": "should still work!",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful endeavor",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NullableEnum"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"deprecated": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"NullableEnum": {
|
||||
"properties": {
|
||||
"a": {
|
||||
"$ref": "#/components/schemas/TestEnum"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"TestEnum": {
|
||||
"enum": [
|
||||
"YES",
|
||||
"NO"
|
||||
],
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"securitySchemes": {}
|
||||
},
|
||||
"security": [],
|
||||
"tags": []
|
||||
}
|
@ -63,6 +63,15 @@ data class TestRequest(
|
||||
@Serializable
|
||||
data class TestResponse(val c: String)
|
||||
|
||||
@Serializable
|
||||
enum class TestEnum {
|
||||
YES,
|
||||
NO
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class NullableEnum(val a: TestEnum? = null)
|
||||
|
||||
data class TestGeneric<T>(val messy: String, val potato: T)
|
||||
|
||||
data class TestCreatedResponse(val id: Int, val c: String)
|
||||
|
@ -185,6 +185,12 @@ object TestResponseInfo {
|
||||
responseInfo = simpleOkResponse()
|
||||
)
|
||||
|
||||
val nullableEnumField = GetInfo<Unit, NullableEnum>(
|
||||
summary = "Has a nullable enum field",
|
||||
description = "should still work!",
|
||||
responseInfo = simpleOkResponse()
|
||||
)
|
||||
|
||||
val minMaxInt = GetInfo<Unit, MinMaxInt>(
|
||||
summary = "Constrained int field",
|
||||
description = "Cool stuff",
|
||||
|
@ -5,10 +5,27 @@ import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class EnumSchema(
|
||||
val `enum`: Set<String>,
|
||||
val enum: Set<String>,
|
||||
override val default: @Contextual Any? = null,
|
||||
override val description: String? = null,
|
||||
override val nullable: Boolean? = null
|
||||
) : TypedSchema {
|
||||
override val type: String = "string"
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is EnumSchema) return false
|
||||
if (enum != other.enum) return false
|
||||
// TODO Going to need some way to differentiate nullable vs non-nullable reference schemas 😬
|
||||
// if (nullable != other.nullable) return false
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = enum.hashCode()
|
||||
result = 31 * result + (default?.hashCode() ?: 0)
|
||||
result = 31 * result + (description?.hashCode() ?: 0)
|
||||
result = 31 * result + (nullable?.hashCode() ?: 0)
|
||||
result = 31 * result + type.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ private fun Application.mainModule() {
|
||||
swagger(pageTitle = "Swaggerlicious")
|
||||
// Kompendium infers the route path from the Ktor Route. This will show up as the root path `/`
|
||||
notarizedGet(simpleGetExample) {
|
||||
call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString()))
|
||||
call.respond(HttpStatusCode.OK, BasicResponse(c = UUID.randomUUID().toString(), d = null))
|
||||
}
|
||||
notarizedDelete(simpleDeleteRequest) {
|
||||
call.respond(HttpStatusCode.NoContent)
|
||||
@ -87,15 +87,15 @@ private fun Application.mainModule() {
|
||||
notarizedGet(simpleGetExampleWithParameters) {
|
||||
val a = call.parameters["a"] ?: error("Unable to read expected path parameter")
|
||||
val b = call.request.queryParameters["b"]?.toInt() ?: error("Unable to read expected query parameter")
|
||||
call.respond(HttpStatusCode.OK, BasicResponse(c = "$a: $b"))
|
||||
call.respond(HttpStatusCode.OK, BasicResponse(c = "$a: $b", d = BasicModels.BasicEnum.NO))
|
||||
}
|
||||
}
|
||||
route("/create") {
|
||||
notarizedPost(simplePostRequest) {
|
||||
val request = call.receive<BasicRequest>()
|
||||
when (request.d) {
|
||||
true -> call.respond(HttpStatusCode.OK, BasicResponse(c = "So it is true!"))
|
||||
false -> call.respond(HttpStatusCode.OK, BasicResponse(c = "Oh, I knew it!"))
|
||||
true -> call.respond(HttpStatusCode.OK, BasicResponse(c = "So it is true!", d = null))
|
||||
false -> call.respond(HttpStatusCode.OK, BasicResponse(c = "Oh, I knew it!", d = BasicModels.BasicEnum.YES))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,7 +115,7 @@ object BasicPlaygroundToC {
|
||||
responseInfo = ResponseInfo(
|
||||
status = HttpStatusCode.OK,
|
||||
description = "This means everything went as expected!",
|
||||
examples = mapOf("demo" to BasicResponse(c = "52c099d7-8642-46cc-b34e-22f39b923cf4"))
|
||||
examples = mapOf("demo" to BasicResponse(c = "52c099d7-8642-46cc-b34e-22f39b923cf4", BasicModels.BasicEnum.NO))
|
||||
),
|
||||
tags = setOf("Simple")
|
||||
)
|
||||
@ -131,7 +131,7 @@ object BasicPlaygroundToC {
|
||||
responseInfo = ResponseInfo(
|
||||
status = HttpStatusCode.OK,
|
||||
description = "This means everything went as expected!",
|
||||
examples = mapOf("demo" to BasicResponse(c = "52c099d7-8642-46cc-b34e-22f39b923cf4"))
|
||||
examples = mapOf("demo" to BasicResponse(c = "52c099d7-8642-46cc-b34e-22f39b923cf4", BasicModels.BasicEnum.YES))
|
||||
),
|
||||
tags = setOf("Parameters")
|
||||
)
|
||||
@ -149,7 +149,7 @@ object BasicPlaygroundToC {
|
||||
responseInfo = ResponseInfo(
|
||||
status = HttpStatusCode.OK,
|
||||
description = "This means everything went as expected!",
|
||||
examples = mapOf("demo" to BasicResponse(c = "So it is true!"))
|
||||
examples = mapOf("demo" to BasicResponse(c = "So it is true!", null))
|
||||
),
|
||||
tags = setOf("Simple")
|
||||
)
|
||||
@ -169,8 +169,15 @@ object BasicPlaygroundToC {
|
||||
}
|
||||
|
||||
object BasicModels {
|
||||
|
||||
@Serializable
|
||||
data class BasicResponse(val c: String)
|
||||
enum class BasicEnum {
|
||||
YES,
|
||||
NO
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class BasicResponse(val c: String, val d: BasicEnum? = null)
|
||||
|
||||
@Serializable
|
||||
data class BasicParameters(
|
||||
|
@ -40,8 +40,8 @@ private fun Application.mainModule() {
|
||||
notarizedPost(BasicPlaygroundToC.simplePostRequest) {
|
||||
val request = call.receive<BasicModels.BasicRequest>()
|
||||
when (request.d) {
|
||||
true -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "So it is true!"))
|
||||
false -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "Oh, I knew it!"))
|
||||
true -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "So it is true!", null))
|
||||
false -> call.respond(HttpStatusCode.OK, BasicModels.BasicResponse(c = "Oh, I knew it!", null))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user