Skip to content

Commit

Permalink
fix: nullability breaks object comparison (#202)
Browse files Browse the repository at this point in the history
  • Loading branch information
brizzbuzz authored Feb 19, 2022
1 parent c50f7f7 commit 64c3520
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 6 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
### Added

### Changed
- Fixed sealed typed collections schema generation

### Remove

---

## Released

## [2.1.1] - February 19th, 2022
### Changed
- Fixed sealed typed collections schema generation
- Nullability no longer breaks object schema comparison

## [2.1.0] - February 18th, 2022
### Added
- Ability to override serializer via custom route
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.1.0
project.version=2.1.1
# Kotlin
kotlin.code.style=official
# Gradle
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ object ObjectHandler : SchemaHandler {
.plus(clazz.generateUndeclaredFieldMap(cache))
.mapValues { (_, fieldSchema) ->
val fieldSlug = cache.filter { (_, vv) -> vv == fieldSchema }.keys.firstOrNull()
postProcessSchema(fieldSchema, fieldSlug ?: "Fine if blank, will be ignored")
postProcessSchema(fieldSchema, fieldSlug)
}
logger.debug("$slug contains $fieldMap")
val schema = ObjectSchema(fieldMap).adjustForRequiredParams(clazz)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,15 @@ interface SchemaHandler {
}
}

fun postProcessSchema(schema: ComponentSchema, slug: String): ComponentSchema = when (schema) {
is ObjectSchema -> ReferencedSchema(COMPONENT_SLUG.plus("/").plus(slug))
is EnumSchema -> ReferencedSchema(COMPONENT_SLUG.plus("/").plus(slug))
fun postProcessSchema(schema: ComponentSchema, slug: String?): ComponentSchema = when (schema) {
is ObjectSchema -> {
require(slug != null) { "Slug cannot be null for an object schema! $schema" }
ReferencedSchema(COMPONENT_SLUG.plus("/").plus(slug))
}
is EnumSchema -> {
require(slug != null) { "Slug cannot be null for an enum schema! $schema" }
ReferencedSchema(COMPONENT_SLUG.plus("/").plus(slug))
}
else -> schema
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ 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.nullableField
import io.bkbn.kompendium.core.util.nullableNestedObject
import io.bkbn.kompendium.core.util.overrideFieldInfo
import io.bkbn.kompendium.core.util.pathParsingTestModule
import io.bkbn.kompendium.core.util.polymorphicCollectionResponse
Expand Down Expand Up @@ -235,6 +236,9 @@ class KompendiumTest : DescribeSpec({
it("Can serialize a recursive type") {
openApiTestAllSerializers("simple_recursive.json") { simpleRecursive() }
}
it("Nullable fields do not lead to doom") {
openApiTestAllSerializers("nullable_fields.json") { nullableNestedObject() }
}
}
describe("Constraints") {
it("Can set a minimum and maximum integer value") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import io.bkbn.kompendium.core.fixtures.TestResponseInfo.defaultParam
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.formattedParam
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.minMaxString
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.nullableField
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.nullableNested
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.regexString
import io.bkbn.kompendium.core.fixtures.TestResponseInfo.requiredParam
import io.bkbn.kompendium.core.metadata.RequestInfo
Expand Down Expand Up @@ -415,6 +416,16 @@ fun Application.simpleRecursive() {
}
}

fun Application.nullableNestedObject() {
routing {
route("/nullable/nested") {
notarizedPost(nullableNested) {
call.respond(HttpStatusCode.OK)
}
}
}
}

fun Application.constrainedIntInfo() {
routing {
route("/test/constrained_int") {
Expand Down
119 changes: 119 additions & 0 deletions kompendium-core/src/test/resources/nullable_fields.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
{
"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": {
"/nullable/nested": {
"post": {
"tags": [],
"summary": "Has a bunch of nullable fields",
"description": "Should still work!",
"parameters": [],
"requestBody": {
"description": "Cool",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ProfileUpdateRequest"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/TestResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"ProfileUpdateRequest": {
"properties": {
"metadata": {
"$ref": "#/components/schemas/ProfileMetadataUpdateRequest"
},
"mood": {
"type": "string",
"nullable": true
},
"viewCount": {
"format": "int64",
"type": "integer",
"nullable": true
}
},
"required": [
"mood",
"viewCount",
"metadata"
],
"type": "object"
},
"ProfileMetadataUpdateRequest": {
"properties": {
"isPrivate": {
"type": "boolean",
"nullable": true
},
"otherThing": {
"type": "string",
"nullable": true
}
},
"required": [
"isPrivate",
"otherThing"
],
"type": "object"
},
"TestResponse": {
"properties": {
"c": {
"type": "string"
}
},
"required": [
"c"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,17 @@ data class ColumnSchema(
val mode: ColumnMode,
val subColumns: List<ColumnSchema> = emptyList()
)

@Serializable
public data class ProfileUpdateRequest(
public val mood: String?,
public val viewCount: Long?,
public val metadata: ProfileMetadataUpdateRequest?
)


@Serializable
public data class ProfileMetadataUpdateRequest(
public val isPrivate: Boolean?,
public val otherThing: String?
)
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ object TestResponseInfo {
responseInfo = simpleOkResponse()
)

val nullableNested = PostInfo<Unit, ProfileUpdateRequest, TestResponse>(
summary = "Has a bunch of nullable fields",
description = "Should still work!",
requestInfo = RequestInfo(
description = "Cool"
),
responseInfo = simpleOkResponse()
)

val minMaxInt = GetInfo<Unit, MinMaxInt>(
summary = "Constrained int field",
description = "Cool stuff",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,24 @@ data class ObjectSchema(
val required: List<String>? = null
) : TypedSchema {
override val type = "object"

override fun equals(other: Any?): Boolean {
if (other !is ObjectSchema) return false
if (properties != other.properties) return false
if (description != other.description) return false
// TODO Going to need some way to differentiate nullable vs non-nullable reference schemas 😬
// if (nullable != other.nullable) return false
if (required != other.required) return false
return true
}

override fun hashCode(): Int {
var result = properties.hashCode()
result = 31 * result + (default?.hashCode() ?: 0)
result = 31 * result + (description?.hashCode() ?: 0)
result = 31 * result + (nullable?.hashCode() ?: 0)
result = 31 * result + (required?.hashCode() ?: 0)
result = 31 * result + type.hashCode()
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,4 @@ object BasicModels {
val d: Boolean
)
}

0 comments on commit 64c3520

Please sign in to comment.