fix: (#194) to support Location classes located in other non-location classes (e.g. inside Object) (#207)

* Safely stop Location path calculation on first non-location parent

* added a test

* changelog

* added result file and renamed test models

Co-authored-by: Gennadi Kudrjavtsev <gennadi@agrello.org>
This commit is contained in:
Gennadi Kudrjavtsev
2022-02-23 17:24:57 +02:00
committed by GitHub
parent 019b2cf4db
commit f919a6a4b1
8 changed files with 149 additions and 21 deletions

View File

@ -1,6 +1,7 @@
# Changelog # Changelog
## Unreleased ## Unreleased
- Fixed support Location classes located in other non-location classes
### Added ### Added

View File

@ -50,7 +50,7 @@ object LocationMethodParser : IMethodParser {
fun KClass<*>.calculateLocationPath(suffix: String = ""): String { fun KClass<*>.calculateLocationPath(suffix: String = ""): String {
val locationAnnotation = this.findAnnotation<Location>() val locationAnnotation = this.findAnnotation<Location>()
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" } require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
val parent = this.java.declaringClass?.kotlin val parent = this.java.declaringClass?.kotlin?.takeIf { it.hasAnnotation<Location>() }
val newSuffix = locationAnnotation.path.plus(suffix) val newSuffix = locationAnnotation.path.plus(suffix)
return when (parent) { return when (parent) {
null -> newSuffix null -> newSuffix

View File

@ -5,6 +5,7 @@ import io.bkbn.kompendium.locations.util.locationsConfig
import io.bkbn.kompendium.locations.util.notarizedDeleteNestedLocation import io.bkbn.kompendium.locations.util.notarizedDeleteNestedLocation
import io.bkbn.kompendium.locations.util.notarizedDeleteSimpleLocation import io.bkbn.kompendium.locations.util.notarizedDeleteSimpleLocation
import io.bkbn.kompendium.locations.util.notarizedGetNestedLocation import io.bkbn.kompendium.locations.util.notarizedGetNestedLocation
import io.bkbn.kompendium.locations.util.notarizedGetNestedLocationFromNonLocationClass
import io.bkbn.kompendium.locations.util.notarizedGetSimpleLocation import io.bkbn.kompendium.locations.util.notarizedGetSimpleLocation
import io.bkbn.kompendium.locations.util.notarizedPostNestedLocation import io.bkbn.kompendium.locations.util.notarizedPostNestedLocation
import io.bkbn.kompendium.locations.util.notarizedPostSimpleLocation import io.bkbn.kompendium.locations.util.notarizedPostSimpleLocation
@ -70,5 +71,12 @@ class KompendiumLocationsTest : DescribeSpec({
notarizedDeleteNestedLocation() notarizedDeleteNestedLocation()
} }
} }
it("Can notarize a get with a nested location nested in a non-location class") {
// act
openApiTestAllSerializers("notarized_get_nested_location_from_non_location_class.json") {
locationsConfig()
notarizedGetNestedLocationFromNonLocationClass()
}
}
} }
}) })

View File

@ -10,5 +10,13 @@ data class SimpleLoc(@Param(ParamType.PATH) val name: String) {
data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc) data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc)
} }
object NonLocationObject {
@Location("/test/{name}")
data class SimpleLoc(@Param(ParamType.PATH) val name: String) {
@Location("/nesty")
data class NestedLoc(@Param(ParamType.QUERY) val isCool: Boolean, val parent: SimpleLoc)
}
}
data class SimpleResponse(val result: Boolean) data class SimpleResponse(val result: Boolean)
data class SimpleRequest(val input: String) data class SimpleRequest(val input: String)

View File

@ -95,3 +95,13 @@ fun Application.notarizedDeleteNestedLocation() {
} }
} }
} }
fun Application.notarizedGetNestedLocationFromNonLocationClass() {
routing {
route("/test") {
notarizedGet(TestResponseInfo.testGetNestedLocationFromNonLocationClass) {
call.respondText { "hey dude ‼️ congratz on the get request" }
}
}
}
}

View File

@ -1,6 +1,5 @@
package io.bkbn.kompendium.locations.util package io.bkbn.kompendium.locations.util
import io.bkbn.kompendium.core.metadata.method.MethodInfo
import io.bkbn.kompendium.core.metadata.RequestInfo import io.bkbn.kompendium.core.metadata.RequestInfo
import io.bkbn.kompendium.core.metadata.ResponseInfo import io.bkbn.kompendium.core.metadata.ResponseInfo
import io.bkbn.kompendium.core.metadata.method.DeleteInfo import io.bkbn.kompendium.core.metadata.method.DeleteInfo
@ -86,4 +85,13 @@ object TestResponseInfo {
description = "A successful endeavor" description = "A successful endeavor"
) )
) )
val testGetNestedLocationFromNonLocationClass = GetInfo<NonLocationObject.SimpleLoc.NestedLoc, SimpleResponse>(
summary = "Location Test",
description = "A cool test",
responseInfo = ResponseInfo(
status = HttpStatusCode.OK,
description = "A successful endeavor"
)
)
} }

View File

@ -0,0 +1,88 @@
{
"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/test/{name}/nesty": {
"get": {
"tags": [],
"summary": "Location Test",
"description": "A cool test",
"parameters": [
{
"name": "isCool",
"in": "query",
"schema": {
"type": "boolean"
},
"required": true,
"deprecated": false
},
{
"name": "name",
"in": "path",
"schema": {
"type": "string"
},
"required": true,
"deprecated": false
}
],
"responses": {
"200": {
"description": "A successful endeavor",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SimpleResponse"
}
}
}
}
},
"deprecated": false
}
}
},
"components": {
"schemas": {
"SimpleResponse": {
"properties": {
"result": {
"type": "boolean"
}
},
"required": [
"result"
],
"type": "object"
}
},
"securitySchemes": {}
},
"security": [],
"tags": []
}

View File

@ -69,7 +69,7 @@ private fun Application.mainModule() {
} }
private object LocationsToC { private object LocationsToC {
val testLocation = GetInfo<TestLocations, LocationModels.ExampleResponse>( val testLocation = GetInfo<TestLocationsParent.TestLocations, LocationModels.ExampleResponse>(
summary = "Shallow", summary = "Shallow",
description = "Ez Pz Lemon Squeezy", description = "Ez Pz Lemon Squeezy",
responseInfo = ResponseInfo( responseInfo = ResponseInfo(
@ -77,7 +77,7 @@ private object LocationsToC {
description = "Great!" description = "Great!"
) )
) )
val testNestLocation = GetInfo<TestLocations.NestedTestLocations, LocationModels.ExampleResponse>( val testNestLocation = GetInfo<TestLocationsParent.TestLocations.NestedTestLocations, LocationModels.ExampleResponse>(
summary = "Nested", summary = "Nested",
description = "Gettin' scary", description = "Gettin' scary",
responseInfo = ResponseInfo( responseInfo = ResponseInfo(
@ -85,7 +85,7 @@ private object LocationsToC {
description = "Hmmm" description = "Hmmm"
) )
) )
val ohBoiUCrazy = GetInfo<TestLocations.NestedTestLocations.OhBoiUCrazy, LocationModels.ExampleResponse>( val ohBoiUCrazy = GetInfo<TestLocationsParent.TestLocations.NestedTestLocations.OhBoiUCrazy, LocationModels.ExampleResponse>(
summary = "Example Deeply Nested", summary = "Example Deeply Nested",
description = "We deep now", description = "We deep now",
responseInfo = ResponseInfo( responseInfo = ResponseInfo(
@ -99,17 +99,21 @@ private object LocationsToC {
// For more info make sure to read through the Ktor location docs // For more info make sure to read through the Ktor location docs
// Additionally, make sure to note that even though we define the locations here, we still must annotate fields // Additionally, make sure to note that even though we define the locations here, we still must annotate fields
// with KompendiumParam!!! // with KompendiumParam!!!
object TestLocationsParent {
@Location("test/{name}") @Location("test/{name}")
data class TestLocations( data class TestLocations(
@Param(ParamType.PATH) @Param(ParamType.PATH)
val name: String, val name: String,
) { ) {
@Location("/spaghetti") @Location("/spaghetti")
data class NestedTestLocations( data class NestedTestLocations(
@Param(ParamType.QUERY) @Param(ParamType.QUERY)
val idk: Int, val idk: Int,
val parent: TestLocations val parent: TestLocations
) { ) {
@Location("/hehe/{madness}") @Location("/hehe/{madness}")
data class OhBoiUCrazy( data class OhBoiUCrazy(
@Param(ParamType.PATH) @Param(ParamType.PATH)
@ -118,6 +122,7 @@ data class TestLocations(
) )
} }
} }
}
object LocationModels { object LocationModels {
@Serializable @Serializable