fix: locations inheritance (#135)
This commit is contained in:
@ -8,6 +8,7 @@
|
||||
### Changed
|
||||
- Kompendium now leverages the chosen API serializer. Supports Jackson, Gson and Kotlinx Serialization
|
||||
- Fixed bug where overridden field names were not reflected in serialized object and required array
|
||||
- Fixed bug where Ktor Location parents were not being scanned for parameters
|
||||
|
||||
### Remove
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
package io.bkbn.kompendium.core
|
||||
|
||||
import io.bkbn.kompendium.core.parser.IMethodParser
|
||||
|
||||
object DefaultMethodParser : IMethodParser
|
@ -1,8 +1,9 @@
|
||||
package io.bkbn.kompendium.core
|
||||
|
||||
import io.bkbn.kompendium.annotations.Param
|
||||
import io.bkbn.kompendium.core.DefaultMethodParser.calculateRoutePath
|
||||
import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight
|
||||
import io.bkbn.kompendium.core.MethodParser.parseMethodInfo
|
||||
import io.bkbn.kompendium.core.DefaultMethodParser.parseMethodInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.HeadInfo
|
||||
@ -168,10 +169,4 @@ object Notarized {
|
||||
feature.config.spec.paths[path]?.options = postProcess(baseInfo)
|
||||
return method(HttpMethod.Options) { handle(body) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the built-in Ktor route path [Route.toString] but cuts out any meta route such as authentication... anything
|
||||
* that matches the RegEx pattern `/\\(.+\\)`
|
||||
*/
|
||||
fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
package io.bkbn.kompendium.core
|
||||
package io.bkbn.kompendium.core.parser
|
||||
|
||||
import io.bkbn.kompendium.annotations.Param
|
||||
import io.bkbn.kompendium.core.Kontent.generateKontent
|
||||
import io.bkbn.kompendium.core.Kompendium
|
||||
import io.bkbn.kompendium.core.Kontent
|
||||
import io.bkbn.kompendium.core.metadata.ExceptionInfo
|
||||
import io.bkbn.kompendium.core.metadata.ParameterExample
|
||||
import io.bkbn.kompendium.core.metadata.RequestInfo
|
||||
@ -19,22 +20,20 @@ import io.bkbn.kompendium.oas.payload.Request
|
||||
import io.bkbn.kompendium.oas.payload.Response
|
||||
import io.bkbn.kompendium.oas.schema.AnyOfSchema
|
||||
import io.bkbn.kompendium.oas.schema.ObjectSchema
|
||||
import io.ktor.routing.Route
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KParameter
|
||||
import kotlin.reflect.KProperty
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.createType
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.hasAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.full.primaryConstructor
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* The MethodParser is responsible for converting route metadata and types into an OpenAPI compatible data class.
|
||||
*/
|
||||
object MethodParser {
|
||||
|
||||
interface IMethodParser {
|
||||
/**
|
||||
* Generates the OpenAPI Path spec from provided metadata
|
||||
* @param info implementation of the [MethodInfo] sealed class
|
||||
@ -68,17 +67,17 @@ object MethodParser {
|
||||
) else null
|
||||
)
|
||||
|
||||
private fun parseResponse(
|
||||
fun parseResponse(
|
||||
responseType: KType,
|
||||
responseInfo: ResponseInfo<*>?,
|
||||
feature: Kompendium
|
||||
): Map<Int, Response> = responseType.toResponseSpec(responseInfo, feature)?.let { mapOf(it) }.orEmpty()
|
||||
|
||||
private fun parseExceptions(
|
||||
fun parseExceptions(
|
||||
exceptionInfo: Set<ExceptionInfo<*>>,
|
||||
feature: Kompendium,
|
||||
): Map<Int, Response> = exceptionInfo.associate { info ->
|
||||
feature.config.cache = generateKontent(info.responseType, feature.config.cache)
|
||||
feature.config.cache = Kontent.generateKontent(info.responseType, feature.config.cache)
|
||||
val response = Response(
|
||||
description = info.description,
|
||||
content = feature.resolveContent(info.responseType, info.mediaTypes, info.examples)
|
||||
@ -92,7 +91,7 @@ object MethodParser {
|
||||
* @param requestInfo request metadata
|
||||
* @return Will return a generated [Request] if requestInfo is not null
|
||||
*/
|
||||
private fun KType.toRequestSpec(requestInfo: RequestInfo<*>?, feature: Kompendium): Request? =
|
||||
fun KType.toRequestSpec(requestInfo: RequestInfo<*>?, feature: Kompendium): Request? =
|
||||
when (requestInfo) {
|
||||
null -> null
|
||||
else -> {
|
||||
@ -111,7 +110,7 @@ object MethodParser {
|
||||
* @param responseInfo response metadata
|
||||
* @return Will return a generated [Pair] if responseInfo is not null
|
||||
*/
|
||||
private fun KType.toResponseSpec(responseInfo: ResponseInfo<*>?, feature: Kompendium): Pair<Int, Response>? =
|
||||
fun KType.toResponseSpec(responseInfo: ResponseInfo<*>?, feature: Kompendium): Pair<Int, Response>? =
|
||||
when (responseInfo) {
|
||||
null -> null
|
||||
else -> {
|
||||
@ -130,7 +129,7 @@ object MethodParser {
|
||||
* @param examples Mapping of named examples of valid bodies.
|
||||
* @return Named mapping of media types.
|
||||
*/
|
||||
private fun Kompendium.resolveContent(
|
||||
fun Kompendium.resolveContent(
|
||||
type: KType,
|
||||
mediaTypes: List<String>,
|
||||
examples: Map<String, Any>
|
||||
@ -163,29 +162,36 @@ object MethodParser {
|
||||
* @return list of valid parameter specs as detailed by the [KType] members
|
||||
* @throws [IllegalStateException] if the class could not be parsed properly
|
||||
*/
|
||||
private fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List<Parameter> {
|
||||
fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List<Parameter> {
|
||||
val clazz = classifier as KClass<*>
|
||||
return clazz.memberProperties.filter { prop ->
|
||||
prop.findAnnotation<Param>() != null
|
||||
}.map { prop ->
|
||||
val wrapperSchema = feature.config.cache[this.getSimpleSlug()]!! as ObjectSchema
|
||||
val anny = prop.findAnnotation<Param>()
|
||||
?: error("Field ${prop.name} is not annotated with KompendiumParam")
|
||||
val schema = wrapperSchema.properties[prop.name]
|
||||
?: error("Could not find component type for $prop")
|
||||
val defaultValue = getDefaultParameterValue(clazz, prop)
|
||||
Parameter(
|
||||
name = prop.name,
|
||||
`in` = anny.type.name.lowercase(Locale.getDefault()),
|
||||
schema = schema.addDefault(defaultValue),
|
||||
description = schema.description,
|
||||
required = !prop.returnType.isMarkedNullable && defaultValue == null,
|
||||
examples = info.parameterExamples.mapToSpec(prop.name)
|
||||
)
|
||||
}
|
||||
return clazz.memberProperties
|
||||
.filter { prop -> prop.hasAnnotation<Param>() }
|
||||
.map { prop -> prop.toParameter(info, this, clazz, feature) }
|
||||
}
|
||||
|
||||
private fun Set<ParameterExample>.mapToSpec(parameterName: String): Map<String, Parameter.Example>? {
|
||||
fun KProperty<*>.toParameter(
|
||||
info: MethodInfo<*, *>,
|
||||
parentType: KType,
|
||||
parentClazz: KClass<*>,
|
||||
feature: Kompendium
|
||||
): Parameter {
|
||||
val wrapperSchema = feature.config.cache[parentType.getSimpleSlug()]!! as ObjectSchema
|
||||
val anny = this.findAnnotation<Param>()
|
||||
?: error("Field $name is not annotated with KompendiumParam")
|
||||
val schema = wrapperSchema.properties[name]
|
||||
?: error("Could not find component type for $this")
|
||||
val defaultValue = getDefaultParameterValue(parentClazz, this)
|
||||
return Parameter(
|
||||
name = name,
|
||||
`in` = anny.type.name.lowercase(Locale.getDefault()),
|
||||
schema = schema.addDefault(defaultValue),
|
||||
description = schema.description,
|
||||
required = !returnType.isMarkedNullable && defaultValue == null,
|
||||
examples = info.parameterExamples.mapToSpec(name)
|
||||
)
|
||||
}
|
||||
|
||||
fun Set<ParameterExample>.mapToSpec(parameterName: String): Map<String, Parameter.Example>? {
|
||||
val filtered = filter { it.parameterName == parameterName }
|
||||
return if (filtered.isEmpty()) {
|
||||
null
|
||||
@ -200,7 +206,7 @@ object MethodParser {
|
||||
* @param prop the property in question
|
||||
* @return The default value if found
|
||||
*/
|
||||
private fun getDefaultParameterValue(clazz: KClass<*>, prop: KProperty<*>): Any? {
|
||||
fun getDefaultParameterValue(clazz: KClass<*>, prop: KProperty<*>): Any? {
|
||||
val constructor = clazz.primaryConstructor
|
||||
val parameterInQuestion = constructor
|
||||
?.parameters
|
||||
@ -227,7 +233,7 @@ object MethodParser {
|
||||
* @return value of the proper type to match param
|
||||
* @throws [IllegalStateException] if parameter type is not one of the basic types supported below.
|
||||
*/
|
||||
private fun defaultValueInjector(param: KParameter): Any = when (param.type.classifier) {
|
||||
fun defaultValueInjector(param: KParameter): Any = when (param.type.classifier) {
|
||||
String::class -> "test"
|
||||
Boolean::class -> false
|
||||
Int::class -> 1
|
||||
@ -237,4 +243,10 @@ object MethodParser {
|
||||
UUID::class -> UUID.randomUUID()
|
||||
else -> error("Unsupported Type")
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the built-in Ktor route path [Route.toString] but cuts out any meta route such as authentication... anything
|
||||
* that matches the RegEx pattern `/\\(.+\\)`
|
||||
*/
|
||||
fun Route.calculateRoutePath() = toString().replace(Regex("/\\(.+\\)"), "")
|
||||
}
|
@ -0,0 +1,84 @@
|
||||
package io.bkbn.kompendium.locations
|
||||
|
||||
import io.bkbn.kompendium.annotations.Param
|
||||
import io.bkbn.kompendium.core.Kompendium
|
||||
import io.bkbn.kompendium.core.metadata.method.MethodInfo
|
||||
import io.bkbn.kompendium.core.parser.IMethodParser
|
||||
import io.bkbn.kompendium.oas.path.Path
|
||||
import io.bkbn.kompendium.oas.path.PathOperation
|
||||
import io.bkbn.kompendium.oas.payload.Parameter
|
||||
import io.ktor.application.feature
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.application
|
||||
import kotlin.reflect.KAnnotatedElement
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KClassifier
|
||||
import kotlin.reflect.KType
|
||||
import kotlin.reflect.full.createType
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
import kotlin.reflect.full.hasAnnotation
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
@OptIn(KtorExperimentalLocationsAPI::class)
|
||||
object LocationMethodParser : IMethodParser {
|
||||
override fun KType.toParameterSpec(info: MethodInfo<*, *>, feature: Kompendium): List<Parameter> {
|
||||
val clazzList = determineLocationParents(classifier!!)
|
||||
return clazzList.associateWith { it.memberProperties }
|
||||
.flatMap { (clazz, memberProperties) -> memberProperties.associateWith { clazz }.toList() }
|
||||
.filter { (prop, _) -> prop.hasAnnotation<Param>() }
|
||||
.map { (prop, clazz) -> prop.toParameter(info, clazz.createType(), clazz, feature) }
|
||||
}
|
||||
|
||||
private fun determineLocationParents(classifier: KClassifier): List<KClass<*>> {
|
||||
var clazz: KClass<*>? = classifier as KClass<*>
|
||||
val clazzList = mutableListOf<KClass<*>>()
|
||||
while (clazz != null) {
|
||||
clazzList.add(clazz)
|
||||
clazz = getLocationParent(clazz)
|
||||
}
|
||||
return clazzList
|
||||
}
|
||||
|
||||
private fun getLocationParent(clazz: KClass<*>): KClass<*>? {
|
||||
val parent = clazz.memberProperties
|
||||
.find { (it.returnType.classifier as KAnnotatedElement).hasAnnotation<Location>() }
|
||||
return parent?.returnType?.classifier as? KClass<*>
|
||||
}
|
||||
|
||||
fun KClass<*>.calculateLocationPath(suffix: String = ""): String {
|
||||
val locationAnnotation = this.findAnnotation<Location>()
|
||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||
val parent = this.java.declaringClass?.kotlin
|
||||
val newSuffix = locationAnnotation.path.plus(suffix)
|
||||
return when (parent) {
|
||||
null -> newSuffix
|
||||
else -> parent.calculateLocationPath(newSuffix)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified TParam : Any> processBaseInfo(
|
||||
paramType: KType,
|
||||
requestType: KType,
|
||||
responseType: KType,
|
||||
info: MethodInfo<*, *>,
|
||||
route: Route
|
||||
): LocationBaseInfo {
|
||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||
val path = route.calculateRoutePath()
|
||||
val locationPath = TParam::class.calculateLocationPath()
|
||||
val pathWithLocation = path.plus(locationPath)
|
||||
val feature = route.application.feature(Kompendium)
|
||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||
return LocationBaseInfo(baseInfo, feature, pathWithLocation)
|
||||
}
|
||||
|
||||
data class LocationBaseInfo(
|
||||
val op: PathOperation,
|
||||
val feature: Kompendium,
|
||||
val path: String
|
||||
)
|
||||
}
|
@ -1,28 +1,19 @@
|
||||
package io.bkbn.kompendium.locations
|
||||
|
||||
import io.bkbn.kompendium.core.Kompendium
|
||||
import io.bkbn.kompendium.core.KompendiumPreFlight.methodNotarizationPreFlight
|
||||
import io.bkbn.kompendium.core.MethodParser.parseMethodInfo
|
||||
import io.bkbn.kompendium.core.Notarized.calculateRoutePath
|
||||
import io.bkbn.kompendium.core.metadata.method.DeleteInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.GetInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.PostInfo
|
||||
import io.bkbn.kompendium.core.metadata.method.PutInfo
|
||||
import io.bkbn.kompendium.oas.path.Path
|
||||
import io.bkbn.kompendium.oas.path.PathOperation
|
||||
import io.ktor.application.ApplicationCall
|
||||
import io.ktor.application.feature
|
||||
import io.ktor.http.HttpMethod
|
||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||
import io.ktor.locations.Location
|
||||
import io.ktor.locations.handle
|
||||
import io.ktor.locations.location
|
||||
import io.ktor.routing.Route
|
||||
import io.ktor.routing.application
|
||||
import io.ktor.routing.method
|
||||
import io.ktor.util.pipeline.PipelineContext
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.full.findAnnotation
|
||||
|
||||
/**
|
||||
* This version of notarized routes leverages the Ktor [io.ktor.locations.Locations] plugin to provide type safe access
|
||||
@ -45,15 +36,8 @@ object NotarizedLocation {
|
||||
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
||||
): Route = methodNotarizationPreFlight<TParam, Unit, TResp>() { paramType, requestType, responseType ->
|
||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||
val feature = application.feature(Kompendium)
|
||||
val path = calculateRoutePath()
|
||||
val locationPath = TParam::class.calculateLocationPath()
|
||||
val pathWithLocation = path.plus(locationPath)
|
||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||
feature.config.spec.paths[pathWithLocation]?.get = postProcess(baseInfo)
|
||||
val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
||||
lbi.feature.config.spec.paths[lbi.path]?.get = postProcess(lbi.op)
|
||||
return location(TParam::class) {
|
||||
method(HttpMethod.Get) { handle(body) }
|
||||
}
|
||||
@ -74,15 +58,8 @@ object NotarizedLocation {
|
||||
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
||||
): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||
val feature = application.feature(Kompendium)
|
||||
val path = calculateRoutePath()
|
||||
val locationPath = TParam::class.calculateLocationPath()
|
||||
val pathWithLocation = path.plus(locationPath)
|
||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||
feature.config.spec.paths[pathWithLocation]?.post = postProcess(baseInfo)
|
||||
val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
||||
lbi.feature.config.spec.paths[lbi.path]?.post = postProcess(lbi.op)
|
||||
return location(TParam::class) {
|
||||
method(HttpMethod.Post) { handle(body) }
|
||||
}
|
||||
@ -103,15 +80,8 @@ object NotarizedLocation {
|
||||
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
||||
): Route = methodNotarizationPreFlight<TParam, TReq, TResp>() { paramType, requestType, responseType ->
|
||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||
val feature = application.feature(Kompendium)
|
||||
val path = calculateRoutePath()
|
||||
val locationPath = TParam::class.calculateLocationPath()
|
||||
val pathWithLocation = path.plus(locationPath)
|
||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||
feature.config.spec.paths[pathWithLocation]?.put = postProcess(baseInfo)
|
||||
val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
||||
lbi.feature.config.spec.paths[lbi.path]?.put = postProcess(lbi.op)
|
||||
return location(TParam::class) {
|
||||
method(HttpMethod.Put) { handle(body) }
|
||||
}
|
||||
@ -131,28 +101,10 @@ object NotarizedLocation {
|
||||
postProcess: (PathOperation) -> PathOperation = { p -> p },
|
||||
noinline body: suspend PipelineContext<Unit, ApplicationCall>.(TParam) -> Unit
|
||||
): Route = methodNotarizationPreFlight<TParam, Unit, TResp> { paramType, requestType, responseType ->
|
||||
val locationAnnotation = TParam::class.findAnnotation<Location>()
|
||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||
val feature = application.feature(Kompendium)
|
||||
val path = calculateRoutePath()
|
||||
val locationPath = TParam::class.calculateLocationPath()
|
||||
val pathWithLocation = path.plus(locationPath)
|
||||
feature.config.spec.paths.getOrPut(pathWithLocation) { Path() }
|
||||
val baseInfo = parseMethodInfo(info, paramType, requestType, responseType, feature)
|
||||
feature.config.spec.paths[pathWithLocation]?.delete = postProcess(baseInfo)
|
||||
val lbi = LocationMethodParser.processBaseInfo<TParam>(paramType, requestType, responseType, info, this)
|
||||
lbi.feature.config.spec.paths[lbi.path]?.delete = postProcess(lbi.op)
|
||||
return location(TParam::class) {
|
||||
method(HttpMethod.Delete) { handle(body) }
|
||||
}
|
||||
}
|
||||
|
||||
fun KClass<*>.calculateLocationPath(suffix: String = ""): String {
|
||||
val locationAnnotation = this.findAnnotation<Location>()
|
||||
require(locationAnnotation != null) { "Location annotation must be present to leverage notarized location api" }
|
||||
val parent = this.java.declaringClass?.kotlin
|
||||
val newSuffix = locationAnnotation.path.plus(suffix)
|
||||
return when (parent) {
|
||||
null -> newSuffix
|
||||
else -> parent.calculateLocationPath(newSuffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,15 @@
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -40,6 +40,15 @@
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
@ -40,6 +40,15 @@
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
|
@ -40,6 +40,15 @@
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"required": true,
|
||||
"deprecated": false
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
|
@ -20,5 +20,4 @@ object NumberSerializer : KSerializer<Number> {
|
||||
override fun serialize(encoder: Encoder, value: Number) {
|
||||
encoder.encodeString(value.toString())
|
||||
}
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user