feat: schema configurator to enable field name overrides and transient field omission (#302)
This commit is contained in:
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
- Support for @Transient annotation
|
||||||
|
- Support for @SerialName annotation on fields
|
||||||
|
- Supports for un-backed fields, by excluding them from the generated schema.
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package io.bkbn.kompendium.core.attribute
|
package io.bkbn.kompendium.core.attribute
|
||||||
|
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
import io.ktor.util.AttributeKey
|
import io.ktor.util.AttributeKey
|
||||||
|
|
||||||
object KompendiumAttributes {
|
object KompendiumAttributes {
|
||||||
val openApiSpec = AttributeKey<OpenApiSpec>("OpenApiSpec")
|
val openApiSpec = AttributeKey<OpenApiSpec>("OpenApiSpec")
|
||||||
|
val schemaConfigurator = AttributeKey<SchemaConfigurator>("SchemaConfigurator")
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.core.plugin
|
package io.bkbn.kompendium.core.plugin
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
import io.bkbn.kompendium.core.attribute.KompendiumAttributes
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
@ -13,7 +14,6 @@ import io.ktor.server.routing.application
|
|||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
import kotlin.reflect.KClass
|
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
|
|
||||||
object NotarizedApplication {
|
object NotarizedApplication {
|
||||||
@ -28,6 +28,7 @@ object NotarizedApplication {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var customTypes: Map<KType, JsonSchema> = emptyMap()
|
var customTypes: Map<KType, JsonSchema> = emptyMap()
|
||||||
|
var schemaConfigurator: SchemaConfigurator = SchemaConfigurator.Default()
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun invoke() = createApplicationPlugin(
|
operator fun invoke() = createApplicationPlugin(
|
||||||
@ -41,5 +42,6 @@ object NotarizedApplication {
|
|||||||
spec.components.schemas[type.getSimpleSlug()] = schema
|
spec.components.schemas[type.getSimpleSlug()] = schema
|
||||||
}
|
}
|
||||||
application.attributes.put(KompendiumAttributes.openApiSpec, spec)
|
application.attributes.put(KompendiumAttributes.openApiSpec, spec)
|
||||||
|
application.attributes.put(KompendiumAttributes.schemaConfigurator, pluginConfig.schemaConfigurator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,17 +63,18 @@ object NotarizedRoute {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
|
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
||||||
|
|
||||||
val path = Path()
|
val path = Path()
|
||||||
path.parameters = pluginConfig.parameters
|
path.parameters = pluginConfig.parameters
|
||||||
|
|
||||||
pluginConfig.get?.addToSpec(path, spec, pluginConfig)
|
pluginConfig.get?.addToSpec(path, spec, pluginConfig, serializableReader)
|
||||||
pluginConfig.delete?.addToSpec(path, spec, pluginConfig)
|
pluginConfig.delete?.addToSpec(path, spec, pluginConfig, serializableReader)
|
||||||
pluginConfig.head?.addToSpec(path, spec, pluginConfig)
|
pluginConfig.head?.addToSpec(path, spec, pluginConfig, serializableReader)
|
||||||
pluginConfig.options?.addToSpec(path, spec, pluginConfig)
|
pluginConfig.options?.addToSpec(path, spec, pluginConfig, serializableReader)
|
||||||
pluginConfig.post?.addToSpec(path, spec, pluginConfig)
|
pluginConfig.post?.addToSpec(path, spec, pluginConfig, serializableReader)
|
||||||
pluginConfig.put?.addToSpec(path, spec, pluginConfig)
|
pluginConfig.put?.addToSpec(path, spec, pluginConfig, serializableReader)
|
||||||
pluginConfig.patch?.addToSpec(path, spec, pluginConfig)
|
pluginConfig.patch?.addToSpec(path, spec, pluginConfig, serializableReader)
|
||||||
|
|
||||||
pluginConfig.path = path
|
pluginConfig.path = path
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import io.bkbn.kompendium.core.metadata.PostInfo
|
|||||||
import io.bkbn.kompendium.core.metadata.PutInfo
|
import io.bkbn.kompendium.core.metadata.PutInfo
|
||||||
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
import io.bkbn.kompendium.core.metadata.ResponseInfo
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getReferenceSlug
|
||||||
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
import io.bkbn.kompendium.json.schema.util.Helpers.getSimpleSlug
|
||||||
@ -25,20 +26,27 @@ import kotlin.reflect.KType
|
|||||||
|
|
||||||
object Helpers {
|
object Helpers {
|
||||||
|
|
||||||
fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: SpecConfig) {
|
fun MethodInfo.addToSpec(path: Path, spec: OpenApiSpec, config: SpecConfig, schemaConfigurator: SchemaConfigurator) {
|
||||||
SchemaGenerator.fromTypeOrUnit(this.response.responseType, spec.components.schemas)?.let { schema ->
|
SchemaGenerator.fromTypeOrUnit(
|
||||||
|
this.response.responseType,
|
||||||
|
spec.components.schemas, schemaConfigurator
|
||||||
|
)?.let { schema ->
|
||||||
spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema
|
spec.components.schemas[this.response.responseType.getSimpleSlug()] = schema
|
||||||
}
|
}
|
||||||
|
|
||||||
errors.forEach { error ->
|
errors.forEach { error ->
|
||||||
SchemaGenerator.fromTypeOrUnit(error.responseType, spec.components.schemas)?.let { schema ->
|
SchemaGenerator.fromTypeOrUnit(error.responseType, spec.components.schemas, schemaConfigurator)?.let { schema ->
|
||||||
spec.components.schemas[error.responseType.getSimpleSlug()] = schema
|
spec.components.schemas[error.responseType.getSimpleSlug()] = schema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (this) {
|
when (this) {
|
||||||
is MethodInfoWithRequest -> {
|
is MethodInfoWithRequest -> {
|
||||||
SchemaGenerator.fromTypeOrUnit(this.request.requestType, spec.components.schemas)?.let { schema ->
|
SchemaGenerator.fromTypeOrUnit(
|
||||||
|
this.request.requestType,
|
||||||
|
spec.components.schemas,
|
||||||
|
schemaConfigurator
|
||||||
|
)?.let { schema ->
|
||||||
spec.components.schemas[this.request.requestType.getSimpleSlug()] = schema
|
spec.components.schemas[this.request.requestType.getSimpleSlug()] = schema
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import dev.forst.ktor.apikey.apiKey
|
|||||||
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
import io.bkbn.kompendium.core.fixtures.TestHelpers.openApiTestAllSerializers
|
||||||
import io.bkbn.kompendium.core.util.TestModules.complexRequest
|
import io.bkbn.kompendium.core.util.TestModules.complexRequest
|
||||||
import io.bkbn.kompendium.core.util.TestModules.customAuthConfig
|
import io.bkbn.kompendium.core.util.TestModules.customAuthConfig
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.customFieldNameResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.dateTimeString
|
import io.bkbn.kompendium.core.util.TestModules.dateTimeString
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultAuthConfig
|
import io.bkbn.kompendium.core.util.TestModules.defaultAuthConfig
|
||||||
import io.bkbn.kompendium.core.util.TestModules.defaultField
|
import io.bkbn.kompendium.core.util.TestModules.defaultField
|
||||||
@ -19,6 +20,7 @@ import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponse
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls
|
import io.bkbn.kompendium.core.util.TestModules.genericPolymorphicResponseMultipleImpls
|
||||||
import io.bkbn.kompendium.core.util.TestModules.gnarlyGenericResponse
|
import io.bkbn.kompendium.core.util.TestModules.gnarlyGenericResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.headerParameter
|
import io.bkbn.kompendium.core.util.TestModules.headerParameter
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.ignoredFieldsResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.multipleAuthStrategies
|
import io.bkbn.kompendium.core.util.TestModules.multipleAuthStrategies
|
||||||
import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
|
import io.bkbn.kompendium.core.util.TestModules.multipleExceptions
|
||||||
import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection
|
import io.bkbn.kompendium.core.util.TestModules.nestedGenericCollection
|
||||||
@ -48,6 +50,7 @@ import io.bkbn.kompendium.core.util.TestModules.simpleGenericResponse
|
|||||||
import io.bkbn.kompendium.core.util.TestModules.simplePathParsing
|
import io.bkbn.kompendium.core.util.TestModules.simplePathParsing
|
||||||
import io.bkbn.kompendium.core.util.TestModules.simpleRecursive
|
import io.bkbn.kompendium.core.util.TestModules.simpleRecursive
|
||||||
import io.bkbn.kompendium.core.util.TestModules.trailingSlash
|
import io.bkbn.kompendium.core.util.TestModules.trailingSlash
|
||||||
|
import io.bkbn.kompendium.core.util.TestModules.unbackedFieldsResponse
|
||||||
import io.bkbn.kompendium.core.util.TestModules.withOperationId
|
import io.bkbn.kompendium.core.util.TestModules.withOperationId
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import io.bkbn.kompendium.oas.component.Components
|
import io.bkbn.kompendium.oas.component.Components
|
||||||
@ -192,6 +195,17 @@ class KompendiumTest : DescribeSpec({
|
|||||||
openApiTestAllSerializers("T0043__gnarly_generic_example.json") { gnarlyGenericResponse() }
|
openApiTestAllSerializers("T0043__gnarly_generic_example.json") { gnarlyGenericResponse() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
describe("Custom Serializable Reader tests") {
|
||||||
|
it("Can support ignoring fields") {
|
||||||
|
openApiTestAllSerializers("T0048__ignored_property.json") { ignoredFieldsResponse() }
|
||||||
|
}
|
||||||
|
it("Can support un-backed fields") {
|
||||||
|
openApiTestAllSerializers("T0049__unbacked_property.json") { unbackedFieldsResponse() }
|
||||||
|
}
|
||||||
|
it("Can support custom named fields") {
|
||||||
|
openApiTestAllSerializers("T0050__custom_named_property.json") { customFieldNameResponse() }
|
||||||
|
}
|
||||||
|
}
|
||||||
describe("Miscellaneous") {
|
describe("Miscellaneous") {
|
||||||
xit("Can generate the necessary ReDoc home page") {
|
xit("Can generate the necessary ReDoc home page") {
|
||||||
// TODO apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
|
// TODO apiFunctionalityTest(getFileSnapshot("redoc.html"), "/docs") { returnsList() }
|
||||||
|
@ -1,27 +1,6 @@
|
|||||||
package io.bkbn.kompendium.core.util
|
package io.bkbn.kompendium.core.util
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Barzo
|
import io.bkbn.kompendium.core.fixtures.*
|
||||||
import io.bkbn.kompendium.core.fixtures.ColumnSchema
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.DateTimeString
|
|
||||||
import io.bkbn.kompendium.core.fixtures.DefaultField
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ExceptionResponse
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Flibbity
|
|
||||||
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Foosy
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Gibbity
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ManyThings
|
|
||||||
import io.bkbn.kompendium.core.fixtures.MultiNestedGenerics
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Nested
|
|
||||||
import io.bkbn.kompendium.core.fixtures.NullableEnum
|
|
||||||
import io.bkbn.kompendium.core.fixtures.NullableField
|
|
||||||
import io.bkbn.kompendium.core.fixtures.Page
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ProfileUpdateRequest
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestCreatedResponse
|
|
||||||
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.TestSimpleRequest
|
|
||||||
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
import io.bkbn.kompendium.core.metadata.DeleteInfo
|
||||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
import io.bkbn.kompendium.core.metadata.HeadInfo
|
import io.bkbn.kompendium.core.metadata.HeadInfo
|
||||||
@ -563,6 +542,12 @@ object TestModules {
|
|||||||
|
|
||||||
fun Routing.polymorphicResponse() = basicGetGenerator<FlibbityGibbit>()
|
fun Routing.polymorphicResponse() = basicGetGenerator<FlibbityGibbit>()
|
||||||
|
|
||||||
|
fun Routing.ignoredFieldsResponse() = basicGetGenerator<TransientObject>()
|
||||||
|
|
||||||
|
fun Routing.unbackedFieldsResponse() = basicGetGenerator<UnbakcedObject>()
|
||||||
|
|
||||||
|
fun Routing.customFieldNameResponse() = basicGetGenerator<SerialNameObject>()
|
||||||
|
|
||||||
fun Routing.polymorphicCollectionResponse() = basicGetGenerator<List<FlibbityGibbit>>()
|
fun Routing.polymorphicCollectionResponse() = basicGetGenerator<List<FlibbityGibbit>>()
|
||||||
|
|
||||||
fun Routing.polymorphicMapResponse() = basicGetGenerator<Map<String, FlibbityGibbit>>()
|
fun Routing.polymorphicMapResponse() = basicGetGenerator<Map<String, FlibbityGibbit>>()
|
||||||
|
72
core/src/test/resources/T0048__ignored_property.json
Normal file
72
core/src/test/resources/T0048__ignored_property.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"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": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/TransientObject"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"TransientObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nonTransient": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nonTransient"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
72
core/src/test/resources/T0049__unbacked_property.json
Normal file
72
core/src/test/resources/T0049__unbacked_property.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"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": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UnbakcedObject"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"UnbakcedObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"backed": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"backed"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
72
core/src/test/resources/T0050__custom_named_property.json
Normal file
72
core/src/test/resources/T0050__custom_named_property.json
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.1.0",
|
||||||
|
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"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": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [],
|
||||||
|
"summary": "Great Summary!",
|
||||||
|
"description": "testing more",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "A Successful Endeavor",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SerialNameObject"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deprecated": false
|
||||||
|
},
|
||||||
|
"parameters": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"webhooks": {},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"SerialNameObject": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"snake_case_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"snake_case_name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"securitySchemes": {}
|
||||||
|
},
|
||||||
|
"security": [],
|
||||||
|
"tags": []
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package io.bkbn.kompendium.core.fixtures
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
import kotlin.reflect.jvm.javaField
|
||||||
|
|
||||||
|
/*
|
||||||
|
These are test implementation and may well be a good starting point for creating production ones.
|
||||||
|
Both Gson and Jackson are complex and can achieve this things is more than one way therefore
|
||||||
|
these will not always work hence why they are in the test package
|
||||||
|
*/
|
||||||
|
|
||||||
|
class GsonSchemaConfigurator: SchemaConfigurator {
|
||||||
|
|
||||||
|
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> {
|
||||||
|
// NOTE: This is test logic Expose is set at a global Gson level so configure to match your Gson set up
|
||||||
|
val hasAnyExpose = clazz.memberProperties.any { it.hasJavaAnnotation<Expose>() }
|
||||||
|
return if(hasAnyExpose) {
|
||||||
|
clazz.memberProperties
|
||||||
|
.filter { it.hasJavaAnnotation<Expose>() }
|
||||||
|
} else clazz.memberProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serializableName(property: KProperty1<out Any, *>): String =
|
||||||
|
property.getJavaAnnotation<SerializedName>()?.value?: property.name
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class JacksonSchemaConfigurator: SchemaConfigurator {
|
||||||
|
|
||||||
|
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
||||||
|
clazz.memberProperties
|
||||||
|
.filterNot {
|
||||||
|
it.hasJavaAnnotation<JsonIgnore>()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serializableName(property: KProperty1<out Any, *>): String =
|
||||||
|
property.getJavaAnnotation<JsonProperty>()?.value?: property.name
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T: Annotation> KProperty1<*, *>.hasJavaAnnotation(): Boolean {
|
||||||
|
return javaField?.isAnnotationPresent(T::class.java)?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun <reified T: Annotation> KProperty1<*, *>.getJavaAnnotation(): T? {
|
||||||
|
return javaField?.getDeclaredAnnotation(T::class.java)
|
||||||
|
}
|
@ -5,13 +5,10 @@ import com.fasterxml.jackson.databind.SerializationFeature
|
|||||||
import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec
|
import io.bkbn.kompendium.core.fixtures.TestSpecs.defaultSpec
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.oas.OpenApiSpec
|
import io.bkbn.kompendium.oas.OpenApiSpec
|
||||||
import io.bkbn.kompendium.oas.info.Contact
|
|
||||||
import io.bkbn.kompendium.oas.info.Info
|
|
||||||
import io.bkbn.kompendium.oas.info.License
|
|
||||||
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
import io.bkbn.kompendium.oas.server.Server
|
|
||||||
import io.kotest.assertions.json.shouldEqualJson
|
import io.kotest.assertions.json.shouldEqualJson
|
||||||
import io.kotest.assertions.ktor.client.shouldHaveStatus
|
import io.kotest.assertions.ktor.client.shouldHaveStatus
|
||||||
import io.kotest.matchers.shouldNot
|
import io.kotest.matchers.shouldNot
|
||||||
@ -31,7 +28,6 @@ import io.ktor.server.testing.testApplication
|
|||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
object TestHelpers {
|
object TestHelpers {
|
||||||
private const val OPEN_API_ENDPOINT = "/openapi.json"
|
private const val OPEN_API_ENDPOINT = "/openapi.json"
|
||||||
@ -83,6 +79,11 @@ object TestHelpers {
|
|||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
customTypes = typeOverrides
|
customTypes = typeOverrides
|
||||||
spec = defaultSpec().specOverrides()
|
spec = defaultSpec().specOverrides()
|
||||||
|
schemaConfigurator = when (serializer) {
|
||||||
|
SupportedSerializer.KOTLINX -> KotlinXSchemaConfigurator()
|
||||||
|
SupportedSerializer.GSON -> GsonSchemaConfigurator()
|
||||||
|
SupportedSerializer.JACKSON -> JacksonSchemaConfigurator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
install(ContentNegotiation) {
|
install(ContentNegotiation) {
|
||||||
when (serializer) {
|
when (serializer) {
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
package io.bkbn.kompendium.core.fixtures
|
package io.bkbn.kompendium.core.fixtures
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@ -146,3 +152,27 @@ object Nested {
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class Response(val idk: Boolean)
|
data class Response(val idk: Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TransientObject(
|
||||||
|
@field:Expose
|
||||||
|
val nonTransient: String,
|
||||||
|
@field:JsonIgnore
|
||||||
|
@Transient
|
||||||
|
val transient: String = "transient"
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class UnbakcedObject(
|
||||||
|
val backed: String
|
||||||
|
) {
|
||||||
|
val unbacked: String get() = "unbacked"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SerialNameObject(
|
||||||
|
@field:JsonProperty("snake_case_name")
|
||||||
|
@field:SerializedName("snake_case_name")
|
||||||
|
@SerialName("snake_case_name")
|
||||||
|
val camelCaseName: String
|
||||||
|
)
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package io.bkbn.kompendium.json.schema
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Transient
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.hasAnnotation
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
|
||||||
|
class KotlinXSchemaConfigurator: SchemaConfigurator {
|
||||||
|
|
||||||
|
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
||||||
|
clazz.memberProperties
|
||||||
|
.filterNot { it.hasAnnotation<Transient>() }
|
||||||
|
|
||||||
|
override fun serializableName(property: KProperty1<out Any, *>): String =
|
||||||
|
property.annotations
|
||||||
|
.filterIsInstance<SerialName>()
|
||||||
|
.firstOrNull()?.value?: property.name
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package io.bkbn.kompendium.json.schema
|
||||||
|
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
|
||||||
|
interface SchemaConfigurator {
|
||||||
|
fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>>
|
||||||
|
fun serializableName(property: KProperty1<out Any, *>): String
|
||||||
|
|
||||||
|
open class Default: SchemaConfigurator {
|
||||||
|
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>>
|
||||||
|
= clazz.memberProperties
|
||||||
|
override fun serializableName(property: KProperty1<out Any, *>): String
|
||||||
|
= property.name
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -17,10 +17,17 @@ import kotlin.reflect.typeOf
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
object SchemaGenerator {
|
object SchemaGenerator {
|
||||||
inline fun <reified T : Any?> fromTypeToSchema(cache: MutableMap<String, JsonSchema> = mutableMapOf()) =
|
|
||||||
fromTypeToSchema(typeOf<T>(), cache)
|
|
||||||
|
|
||||||
fun fromTypeToSchema(type: KType, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
inline fun <reified T : Any?> fromTypeToSchema(
|
||||||
|
cache: MutableMap<String, JsonSchema> = mutableMapOf(),
|
||||||
|
schemaConfigurator: SchemaConfigurator = SchemaConfigurator.Default()
|
||||||
|
) = fromTypeToSchema(typeOf<T>(), cache, schemaConfigurator)
|
||||||
|
|
||||||
|
fun fromTypeToSchema(
|
||||||
|
type: KType,
|
||||||
|
cache: MutableMap<String, JsonSchema>,
|
||||||
|
schemaConfigurator: SchemaConfigurator
|
||||||
|
): JsonSchema {
|
||||||
cache[type.getSimpleSlug()]?.let {
|
cache[type.getSimpleSlug()]?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
@ -41,26 +48,30 @@ object SchemaGenerator {
|
|||||||
UUID::class -> checkForNull(type, TypeDefinition.UUID)
|
UUID::class -> checkForNull(type, TypeDefinition.UUID)
|
||||||
else -> when {
|
else -> when {
|
||||||
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz)
|
clazz.isSubclassOf(Enum::class) -> EnumHandler.handle(type, clazz)
|
||||||
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache)
|
clazz.isSubclassOf(Collection::class) -> CollectionHandler.handle(type, cache, schemaConfigurator)
|
||||||
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache)
|
clazz.isSubclassOf(Map::class) -> MapHandler.handle(type, cache, schemaConfigurator)
|
||||||
else -> {
|
else -> {
|
||||||
if (clazz.isSealed) {
|
if (clazz.isSealed) {
|
||||||
SealedObjectHandler.handle(type, clazz, cache)
|
SealedObjectHandler.handle(type, clazz, cache, schemaConfigurator)
|
||||||
} else {
|
} else {
|
||||||
SimpleObjectHandler.handle(type, clazz, cache)
|
SimpleObjectHandler.handle(type, clazz, cache, schemaConfigurator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromTypeOrUnit(type: KType, cache: MutableMap<String, JsonSchema> = mutableMapOf()): JsonSchema? =
|
fun fromTypeOrUnit(
|
||||||
|
type: KType,
|
||||||
|
cache: MutableMap<String, JsonSchema> = mutableMapOf(),
|
||||||
|
schemaConfigurator: SchemaConfigurator
|
||||||
|
): JsonSchema? =
|
||||||
when (type.classifier as KClass<*>) {
|
when (type.classifier as KClass<*>) {
|
||||||
Unit::class -> null
|
Unit::class -> null
|
||||||
else -> fromTypeToSchema(type, cache)
|
else -> fromTypeToSchema(type, cache, schemaConfigurator)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkForNull(type: KType, schema: JsonSchema): JsonSchema = when (type.isMarkedNullable) {
|
private fun checkForNull(type: KType, schema: JsonSchema, ): JsonSchema = when (type.isMarkedNullable) {
|
||||||
true -> OneOfDefinition(NullableDefinition(), schema)
|
true -> OneOfDefinition(NullableDefinition(), schema)
|
||||||
false -> schema
|
false -> schema
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.json.schema.handler
|
package io.bkbn.kompendium.json.schema.handler
|
||||||
|
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.ArrayDefinition
|
import io.bkbn.kompendium.json.schema.definition.ArrayDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
||||||
@ -13,10 +14,10 @@ import kotlin.reflect.KType
|
|||||||
|
|
||||||
object CollectionHandler {
|
object CollectionHandler {
|
||||||
|
|
||||||
fun handle(type: KType, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
fun handle(type: KType, cache: MutableMap<String, JsonSchema>, schemaConfigurator: SchemaConfigurator): JsonSchema {
|
||||||
val collectionType = type.arguments.first().type
|
val collectionType = type.arguments.first().type
|
||||||
?: error("This indicates a bug in Kompendium, please open a GitHub issue!")
|
?: error("This indicates a bug in Kompendium, please open a GitHub issue!")
|
||||||
val typeSchema = SchemaGenerator.fromTypeToSchema(collectionType, cache).let {
|
val typeSchema = SchemaGenerator.fromTypeToSchema(collectionType, cache, schemaConfigurator).let {
|
||||||
if (it is TypeDefinition && it.type == "object") {
|
if (it is TypeDefinition && it.type == "object") {
|
||||||
cache[collectionType.getSimpleSlug()] = it
|
cache[collectionType.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(collectionType.getReferenceSlug())
|
ReferenceDefinition(collectionType.getReferenceSlug())
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.json.schema.handler
|
package io.bkbn.kompendium.json.schema.handler
|
||||||
|
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.definition.MapDefinition
|
import io.bkbn.kompendium.json.schema.definition.MapDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
||||||
@ -14,12 +15,12 @@ import kotlin.reflect.KType
|
|||||||
|
|
||||||
object MapHandler {
|
object MapHandler {
|
||||||
|
|
||||||
fun handle(type: KType, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
fun handle(type: KType, cache: MutableMap<String, JsonSchema>, schemaConfigurator: SchemaConfigurator): JsonSchema {
|
||||||
require(type.arguments.first().type?.classifier as KClass<*> == String::class) {
|
require(type.arguments.first().type?.classifier as KClass<*> == String::class) {
|
||||||
"JSON requires that map keys MUST be Strings. You provided ${type.arguments.first().type}"
|
"JSON requires that map keys MUST be Strings. You provided ${type.arguments.first().type}"
|
||||||
}
|
}
|
||||||
val valueType = type.arguments[1].type ?: error("this indicates a bug in Kompendium, please open a GitHub issue")
|
val valueType = type.arguments[1].type ?: error("this indicates a bug in Kompendium, please open a GitHub issue")
|
||||||
val valueSchema = SchemaGenerator.fromTypeToSchema(valueType, cache).let {
|
val valueSchema = SchemaGenerator.fromTypeToSchema(valueType, cache, schemaConfigurator).let {
|
||||||
if (it is TypeDefinition && it.type == "object") {
|
if (it is TypeDefinition && it.type == "object") {
|
||||||
cache[valueType.getSimpleSlug()] = it
|
cache[valueType.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(valueType.getReferenceSlug())
|
ReferenceDefinition(valueType.getReferenceSlug())
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.json.schema.handler
|
package io.bkbn.kompendium.json.schema.handler
|
||||||
|
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.AnyOfDefinition
|
import io.bkbn.kompendium.json.schema.definition.AnyOfDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
import io.bkbn.kompendium.json.schema.definition.ReferenceDefinition
|
||||||
@ -13,11 +14,16 @@ import kotlin.reflect.full.createType
|
|||||||
|
|
||||||
object SealedObjectHandler {
|
object SealedObjectHandler {
|
||||||
|
|
||||||
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
fun handle(
|
||||||
|
type: KType,
|
||||||
|
clazz: KClass<*>,
|
||||||
|
cache: MutableMap<String, JsonSchema>,
|
||||||
|
schemaConfigurator: SchemaConfigurator
|
||||||
|
): JsonSchema {
|
||||||
val subclasses = clazz.sealedSubclasses
|
val subclasses = clazz.sealedSubclasses
|
||||||
.map { it.createType(type.arguments) }
|
.map { it.createType(type.arguments) }
|
||||||
.map { t ->
|
.map { t ->
|
||||||
SchemaGenerator.fromTypeToSchema(t, cache).let { js ->
|
SchemaGenerator.fromTypeToSchema(t, cache, schemaConfigurator).let { js ->
|
||||||
if (js is TypeDefinition && js.type == "object") {
|
if (js is TypeDefinition && js.type == "object") {
|
||||||
cache[t.getSimpleSlug()] = js
|
cache[t.getSimpleSlug()] = js
|
||||||
ReferenceDefinition(t.getReferenceSlug())
|
ReferenceDefinition(t.getReferenceSlug())
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package io.bkbn.kompendium.json.schema.handler
|
package io.bkbn.kompendium.json.schema.handler
|
||||||
|
|
||||||
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
import io.bkbn.kompendium.json.schema.SchemaGenerator
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
import io.bkbn.kompendium.json.schema.definition.NullableDefinition
|
||||||
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
import io.bkbn.kompendium.json.schema.definition.OneOfDefinition
|
||||||
@ -14,22 +15,29 @@ import kotlin.reflect.KType
|
|||||||
import kotlin.reflect.KTypeParameter
|
import kotlin.reflect.KTypeParameter
|
||||||
import kotlin.reflect.KTypeProjection
|
import kotlin.reflect.KTypeProjection
|
||||||
import kotlin.reflect.full.createType
|
import kotlin.reflect.full.createType
|
||||||
import kotlin.reflect.full.memberProperties
|
|
||||||
import kotlin.reflect.full.primaryConstructor
|
import kotlin.reflect.full.primaryConstructor
|
||||||
|
import kotlin.reflect.jvm.javaField
|
||||||
|
|
||||||
object SimpleObjectHandler {
|
object SimpleObjectHandler {
|
||||||
|
|
||||||
fun handle(type: KType, clazz: KClass<*>, cache: MutableMap<String, JsonSchema>): JsonSchema {
|
fun handle(
|
||||||
|
type: KType,
|
||||||
|
clazz: KClass<*>,
|
||||||
|
cache: MutableMap<String, JsonSchema>,
|
||||||
|
schemaConfigurator: SchemaConfigurator
|
||||||
|
): JsonSchema {
|
||||||
|
|
||||||
cache[type.getSimpleSlug()] = ReferenceDefinition(type.getReferenceSlug())
|
cache[type.getSimpleSlug()] = ReferenceDefinition(type.getReferenceSlug())
|
||||||
|
|
||||||
val typeMap = clazz.typeParameters.zip(type.arguments).toMap()
|
val typeMap = clazz.typeParameters.zip(type.arguments).toMap()
|
||||||
val props = clazz.memberProperties.associate { prop ->
|
val props = schemaConfigurator.serializableMemberProperties(clazz)
|
||||||
|
.filterNot { it.javaField == null }
|
||||||
|
.associate { prop ->
|
||||||
val schema = when (prop.needsToInjectGenerics(typeMap)) {
|
val schema = when (prop.needsToInjectGenerics(typeMap)) {
|
||||||
true -> handleNestedGenerics(typeMap, prop, cache)
|
true -> handleNestedGenerics(typeMap, prop, cache, schemaConfigurator)
|
||||||
false -> when (typeMap.containsKey(prop.returnType.classifier)) {
|
false -> when (typeMap.containsKey(prop.returnType.classifier)) {
|
||||||
true -> handleGenericProperty(prop, typeMap, cache)
|
true -> handleGenericProperty(prop, typeMap, cache, schemaConfigurator)
|
||||||
false -> handleProperty(prop, cache)
|
false -> handleProperty(prop, cache, schemaConfigurator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,15 +46,17 @@ object SimpleObjectHandler {
|
|||||||
false -> schema
|
false -> schema
|
||||||
}
|
}
|
||||||
|
|
||||||
prop.name to nullCheckSchema
|
schemaConfigurator.serializableName(prop) to nullCheckSchema
|
||||||
}
|
}
|
||||||
|
|
||||||
val required = clazz.memberProperties.filterNot { prop -> prop.returnType.isMarkedNullable }
|
val required = schemaConfigurator.serializableMemberProperties(clazz)
|
||||||
|
.asSequence()
|
||||||
|
.filterNot { it.javaField == null }
|
||||||
|
.filterNot { prop -> prop.returnType.isMarkedNullable }
|
||||||
.filterNot { prop -> clazz.primaryConstructor!!.parameters.find { it.name == prop.name }!!.isOptional }
|
.filterNot { prop -> clazz.primaryConstructor!!.parameters.find { it.name == prop.name }!!.isOptional }
|
||||||
.map { it.name }
|
.map { schemaConfigurator.serializableName(it) }
|
||||||
.toSet()
|
.toSet()
|
||||||
|
|
||||||
|
|
||||||
val definition = TypeDefinition(
|
val definition = TypeDefinition(
|
||||||
type = "object",
|
type = "object",
|
||||||
properties = props,
|
properties = props,
|
||||||
@ -69,7 +79,8 @@ object SimpleObjectHandler {
|
|||||||
private fun handleNestedGenerics(
|
private fun handleNestedGenerics(
|
||||||
typeMap: Map<KTypeParameter, KTypeProjection>,
|
typeMap: Map<KTypeParameter, KTypeProjection>,
|
||||||
prop: KProperty<*>,
|
prop: KProperty<*>,
|
||||||
cache: MutableMap<String, JsonSchema>
|
cache: MutableMap<String, JsonSchema>,
|
||||||
|
schemaConfigurator: SchemaConfigurator
|
||||||
): JsonSchema {
|
): JsonSchema {
|
||||||
val propClass = prop.returnType.classifier as KClass<*>
|
val propClass = prop.returnType.classifier as KClass<*>
|
||||||
val types = prop.returnType.arguments.map {
|
val types = prop.returnType.arguments.map {
|
||||||
@ -77,7 +88,7 @@ object SimpleObjectHandler {
|
|||||||
typeMap.filterKeys { k -> k.name == typeSymbol }.values.first()
|
typeMap.filterKeys { k -> k.name == typeSymbol }.values.first()
|
||||||
}
|
}
|
||||||
val constructedType = propClass.createType(types)
|
val constructedType = propClass.createType(types)
|
||||||
return SchemaGenerator.fromTypeToSchema(constructedType, cache).let {
|
return SchemaGenerator.fromTypeToSchema(constructedType, cache, schemaConfigurator).let {
|
||||||
if (it.isOrContainsObjectDef()) {
|
if (it.isOrContainsObjectDef()) {
|
||||||
cache[constructedType.getSimpleSlug()] = it
|
cache[constructedType.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
||||||
@ -90,11 +101,12 @@ object SimpleObjectHandler {
|
|||||||
private fun handleGenericProperty(
|
private fun handleGenericProperty(
|
||||||
prop: KProperty<*>,
|
prop: KProperty<*>,
|
||||||
typeMap: Map<KTypeParameter, KTypeProjection>,
|
typeMap: Map<KTypeParameter, KTypeProjection>,
|
||||||
cache: MutableMap<String, JsonSchema>
|
cache: MutableMap<String, JsonSchema>,
|
||||||
|
schemaConfigurator: SchemaConfigurator
|
||||||
): JsonSchema {
|
): JsonSchema {
|
||||||
val type = typeMap[prop.returnType.classifier]?.type
|
val type = typeMap[prop.returnType.classifier]?.type
|
||||||
?: error("This indicates a bug in Kompendium, please open a GitHub issue")
|
?: error("This indicates a bug in Kompendium, please open a GitHub issue")
|
||||||
return SchemaGenerator.fromTypeToSchema(type, cache).let {
|
return SchemaGenerator.fromTypeToSchema(type, cache, schemaConfigurator).let {
|
||||||
if (it.isOrContainsObjectDef()) {
|
if (it.isOrContainsObjectDef()) {
|
||||||
cache[type.getSimpleSlug()] = it
|
cache[type.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(type.getReferenceSlug())
|
ReferenceDefinition(type.getReferenceSlug())
|
||||||
@ -104,8 +116,12 @@ object SimpleObjectHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleProperty(prop: KProperty<*>, cache: MutableMap<String, JsonSchema>): JsonSchema =
|
private fun handleProperty(
|
||||||
SchemaGenerator.fromTypeToSchema(prop.returnType, cache).let {
|
prop: KProperty<*>,
|
||||||
|
cache: MutableMap<String, JsonSchema>,
|
||||||
|
schemaConfigurator: SchemaConfigurator
|
||||||
|
): JsonSchema =
|
||||||
|
SchemaGenerator.fromTypeToSchema(prop.returnType, cache, schemaConfigurator).let {
|
||||||
if (it.isOrContainsObjectDef()) {
|
if (it.isOrContainsObjectDef()) {
|
||||||
cache[prop.returnType.getSimpleSlug()] = it
|
cache[prop.returnType.getSimpleSlug()] = it
|
||||||
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
ReferenceDefinition(prop.returnType.getReferenceSlug())
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
package io.bkbn.kompendium.json.schema
|
package io.bkbn.kompendium.json.schema
|
||||||
|
|
||||||
import io.bkbn.kompendium.core.fixtures.ComplexRequest
|
import io.bkbn.kompendium.core.fixtures.*
|
||||||
import io.bkbn.kompendium.core.fixtures.FlibbityGibbit
|
|
||||||
import io.bkbn.kompendium.core.fixtures.SimpleEnum
|
|
||||||
import io.bkbn.kompendium.core.fixtures.SlammaJamma
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot
|
import io.bkbn.kompendium.core.fixtures.TestHelpers.getFileSnapshot
|
||||||
import io.bkbn.kompendium.core.fixtures.TestResponse
|
|
||||||
import io.bkbn.kompendium.core.fixtures.TestSimpleRequest
|
|
||||||
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
import io.bkbn.kompendium.json.schema.definition.JsonSchema
|
||||||
import io.kotest.assertions.json.shouldEqualJson
|
import io.kotest.assertions.json.shouldEqualJson
|
||||||
import io.kotest.assertions.throwables.shouldThrow
|
import io.kotest.assertions.throwables.shouldThrow
|
||||||
@ -45,6 +40,15 @@ class SchemaGeneratorTest : DescribeSpec({
|
|||||||
xit("Can generate the schema for a recursive type") {
|
xit("Can generate the schema for a recursive type") {
|
||||||
// TODO jsonSchemaTest<SlammaJamma>("T0016__recursive_object.json")
|
// TODO jsonSchemaTest<SlammaJamma>("T0016__recursive_object.json")
|
||||||
}
|
}
|
||||||
|
it("Can generate the schema for object with transient property") {
|
||||||
|
jsonSchemaTest<TransientObject>("T0018__transient_object.json")
|
||||||
|
}
|
||||||
|
it("Can generate the schema for object with unbacked property") {
|
||||||
|
jsonSchemaTest<UnbakcedObject>("T0019__unbacked_object.json")
|
||||||
|
}
|
||||||
|
it("Can generate the schema for object with SerialName annotation") {
|
||||||
|
jsonSchemaTest<SerialNameObject>("T0020__serial_name_object.json")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
describe("Enums") {
|
describe("Enums") {
|
||||||
it("Can generate the schema for a simple enum") {
|
it("Can generate the schema for a simple enum") {
|
||||||
@ -91,7 +95,7 @@ class SchemaGeneratorTest : DescribeSpec({
|
|||||||
|
|
||||||
private inline fun <reified T> jsonSchemaTest(snapshotName: String) {
|
private inline fun <reified T> jsonSchemaTest(snapshotName: String) {
|
||||||
// act
|
// act
|
||||||
val schema = SchemaGenerator.fromTypeToSchema<T>()
|
val schema = SchemaGenerator.fromTypeToSchema<T>(schemaConfigurator = KotlinXSchemaConfigurator())
|
||||||
|
|
||||||
// todo add cache assertions!!!
|
// todo add cache assertions!!!
|
||||||
|
|
||||||
|
11
json-schema/src/test/resources/T0018__transient_object.json
Normal file
11
json-schema/src/test/resources/T0018__transient_object.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"nonTransient": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"nonTransient"
|
||||||
|
]
|
||||||
|
}
|
11
json-schema/src/test/resources/T0019__unbacked_object.json
Normal file
11
json-schema/src/test/resources/T0019__unbacked_object.json
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"backed": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"backed"
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"snake_case_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"snake_case_name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -44,16 +44,17 @@ object NotarizedLocations {
|
|||||||
createConfiguration = ::Config
|
createConfiguration = ::Config
|
||||||
) {
|
) {
|
||||||
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
val spec = application.attributes[KompendiumAttributes.openApiSpec]
|
||||||
|
val serializableReader = application.attributes[KompendiumAttributes.schemaConfigurator]
|
||||||
pluginConfig.locations.forEach { (k, v) ->
|
pluginConfig.locations.forEach { (k, v) ->
|
||||||
val path = Path()
|
val path = Path()
|
||||||
path.parameters = v.parameters
|
path.parameters = v.parameters
|
||||||
v.get?.addToSpec(path, spec, v)
|
v.get?.addToSpec(path, spec, v, serializableReader)
|
||||||
v.delete?.addToSpec(path, spec, v)
|
v.delete?.addToSpec(path, spec, v, serializableReader)
|
||||||
v.head?.addToSpec(path, spec, v)
|
v.head?.addToSpec(path, spec, v, serializableReader)
|
||||||
v.options?.addToSpec(path, spec, v)
|
v.options?.addToSpec(path, spec, v, serializableReader)
|
||||||
v.post?.addToSpec(path, spec, v)
|
v.post?.addToSpec(path, spec, v, serializableReader)
|
||||||
v.put?.addToSpec(path, spec, v)
|
v.put?.addToSpec(path, spec, v, serializableReader)
|
||||||
v.patch?.addToSpec(path, spec, v)
|
v.patch?.addToSpec(path, spec, v, serializableReader)
|
||||||
|
|
||||||
val location = k.getLocationFromClass()
|
val location = k.getLocationFromClass()
|
||||||
spec.paths[location] = path
|
spec.paths[location] = path
|
||||||
|
@ -4,6 +4,7 @@ import io.bkbn.kompendium.core.metadata.GetInfo
|
|||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.json.schema.KotlinXSchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
import io.bkbn.kompendium.oas.serialization.KompendiumSerializersModule
|
||||||
@ -42,6 +43,9 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = baseSpec
|
||||||
|
// Adds support for @Transient and @SerialName
|
||||||
|
// If you are not using them this is not required.
|
||||||
|
schemaConfigurator = KotlinXSchemaConfigurator()
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package io.bkbn.kompendium.playground
|
package io.bkbn.kompendium.playground
|
||||||
|
|
||||||
|
import com.google.gson.annotations.Expose
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.bkbn.kompendium.playground.util.ExampleResponse
|
import io.bkbn.kompendium.playground.util.ExampleResponse
|
||||||
@ -21,6 +24,10 @@ import io.ktor.server.routing.Route
|
|||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
import kotlin.reflect.jvm.javaField
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(
|
embeddedServer(
|
||||||
@ -38,6 +45,7 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = baseSpec
|
||||||
|
schemaConfigurator = GsonSchemaConfigurator()
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
@ -71,3 +79,27 @@ private fun Route.locationDocumentation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds support for Expose and SerializedName annotations,
|
||||||
|
// if you are not using them this is not required
|
||||||
|
class GsonSchemaConfigurator(
|
||||||
|
private val excludeFieldsWithoutExposeAnnotation: Boolean = false
|
||||||
|
): SchemaConfigurator.Default() {
|
||||||
|
|
||||||
|
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> {
|
||||||
|
return if(excludeFieldsWithoutExposeAnnotation) clazz.memberProperties
|
||||||
|
.filter { it.hasJavaAnnotation<Expose>() }
|
||||||
|
else clazz.memberProperties
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serializableName(property: KProperty1<out Any, *>): String =
|
||||||
|
property.getJavaAnnotation<SerializedName>()?.value?: property.name
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T: Annotation> KProperty1<*, *>.hasJavaAnnotation(): Boolean {
|
||||||
|
return javaField?.isAnnotationPresent(T::class.java)?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T: Annotation> KProperty1<*, *>.getJavaAnnotation(): T? {
|
||||||
|
return javaField?.getDeclaredAnnotation(T::class.java)
|
||||||
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
package io.bkbn.kompendium.playground
|
package io.bkbn.kompendium.playground
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonInclude
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature
|
import com.fasterxml.jackson.databind.SerializationFeature
|
||||||
import io.bkbn.kompendium.core.metadata.GetInfo
|
import io.bkbn.kompendium.core.metadata.GetInfo
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
import io.bkbn.kompendium.core.plugin.NotarizedApplication
|
||||||
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
import io.bkbn.kompendium.core.plugin.NotarizedRoute
|
||||||
import io.bkbn.kompendium.core.routes.redoc
|
import io.bkbn.kompendium.core.routes.redoc
|
||||||
|
import io.bkbn.kompendium.json.schema.SchemaConfigurator
|
||||||
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
import io.bkbn.kompendium.json.schema.definition.TypeDefinition
|
||||||
import io.bkbn.kompendium.oas.payload.Parameter
|
import io.bkbn.kompendium.oas.payload.Parameter
|
||||||
import io.bkbn.kompendium.playground.util.ExampleResponse
|
import io.bkbn.kompendium.playground.util.ExampleResponse
|
||||||
@ -23,6 +26,10 @@ import io.ktor.server.routing.Route
|
|||||||
import io.ktor.server.routing.get
|
import io.ktor.server.routing.get
|
||||||
import io.ktor.server.routing.route
|
import io.ktor.server.routing.route
|
||||||
import io.ktor.server.routing.routing
|
import io.ktor.server.routing.routing
|
||||||
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.memberProperties
|
||||||
|
import kotlin.reflect.jvm.javaField
|
||||||
|
|
||||||
fun main() {
|
fun main() {
|
||||||
embeddedServer(
|
embeddedServer(
|
||||||
@ -41,6 +48,7 @@ private fun Application.mainModule() {
|
|||||||
}
|
}
|
||||||
install(NotarizedApplication()) {
|
install(NotarizedApplication()) {
|
||||||
spec = baseSpec
|
spec = baseSpec
|
||||||
|
schemaConfigurator = JacksonSchemaConfigurator()
|
||||||
}
|
}
|
||||||
routing {
|
routing {
|
||||||
redoc(pageTitle = "Simple API Docs")
|
redoc(pageTitle = "Simple API Docs")
|
||||||
@ -75,3 +83,26 @@ private fun Route.locationDocumentation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Adds support for JsonIgnore and JsonProperty annotations,
|
||||||
|
// if you are not using them this is not required
|
||||||
|
// This also does not support class level configuration
|
||||||
|
private class JacksonSchemaConfigurator: SchemaConfigurator.Default() {
|
||||||
|
|
||||||
|
override fun serializableMemberProperties(clazz: KClass<*>): Collection<KProperty1<out Any, *>> =
|
||||||
|
clazz.memberProperties
|
||||||
|
.filterNot {
|
||||||
|
it.hasJavaAnnotation<JsonIgnore>()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun serializableName(property: KProperty1<out Any, *>): String =
|
||||||
|
property.getJavaAnnotation<JsonProperty>()?.value?: property.name
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T: Annotation> KProperty1<*, *>.hasJavaAnnotation(): Boolean {
|
||||||
|
return javaField?.isAnnotationPresent(T::class.java)?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun <reified T: Annotation> KProperty1<*, *>.getJavaAnnotation(): T? {
|
||||||
|
return javaField?.getDeclaredAnnotation(T::class.java)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user