Skip to content

Commit f59ab2b

Browse files
committed
allowed multiple non competing definitions on same method and path to be registered separately
1 parent 31ae8ad commit f59ab2b

File tree

8 files changed

+63
-27
lines changed

8 files changed

+63
-27
lines changed

src/main/kotlin/com/papsign/ktor/openapigen/content/type/ktor/KtorContentProvider.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,12 @@ object KtorContentProvider : ContentTypeProvider, BodyParser, ResponseSerializer
6868
when (usage) { // check if it is explicitly declared or none is present
6969
ContentTypeProvider.Usage.PARSE -> when {
7070
clazz.findAnnotation<KtorRequest>() != null -> {}
71-
clazz.findAnnotation<APIRequestFormat>() == null -> {}
71+
clazz.annotations.none { it.annotationClass.findAnnotation<APIRequestFormat>() != null } -> {}
7272
else -> return null
7373
}
7474
ContentTypeProvider.Usage.SERIALIZE -> when {
7575
clazz.findAnnotation<KtorResponse>() != null -> {}
76-
clazz.findAnnotation<APIResponseFormat>() == null -> {}
76+
clazz.annotations.none { it.annotationClass.findAnnotation<APIResponseFormat>() != null } -> {}
7777
else -> return null
7878
}
7979
}

src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/RequestHandlerModule.kt

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.papsign.ktor.openapigen.modules.handlers
33
import com.papsign.kotlin.reflection.getKType
44
import com.papsign.ktor.openapigen.OpenAPIGen
55
import com.papsign.ktor.openapigen.annotations.Request
6+
import com.papsign.ktor.openapigen.classLogger
67
import com.papsign.ktor.openapigen.content.type.BodyParser
78
import com.papsign.ktor.openapigen.content.type.ContentTypeProvider
89
import com.papsign.ktor.openapigen.content.type.SelectedParser
@@ -21,15 +22,29 @@ class RequestHandlerModule<T : Any>(
2122
val requestType: KType,
2223
val requestExample: T? = null
2324
) : OperationModule {
25+
26+
private val log = classLogger()
27+
2428
override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: Operation) {
25-
operation.requestBody = RequestBody(provider.ofClass<BodyParser>().mapNotNull {
29+
val map = provider.ofClass<BodyParser>().mapNotNull {
2630
val mediaType = it.getMediaType(requestType, apiGen, provider, requestExample, ContentTypeProvider.Usage.PARSE)
2731
?: return@mapNotNull null
2832
provider.registerModule(SelectedParser(it))
2933
mediaType.map { Pair(it.key.toString(), it.value) }
30-
}.flatten().fold(mutableMapOf<String, MediaType<T>>()) { a, b -> a[b.first] = b.second; a },
31-
description = requestClass.findAnnotation<Request>()?.description
32-
)
34+
}.flatten().associate { it }
35+
36+
val requestMeta = requestClass.findAnnotation<Request>()
37+
38+
operation.requestBody = operation.requestBody?.apply {
39+
map.forEach { (key, value) ->
40+
content.putIfAbsent(key, value)?.let { if (value != it) log.warn("ContentType of $requestType request $key already registered, ignoring $value") }
41+
}
42+
if (description != null) {
43+
if (requestMeta?.description != null) log.warn("ContentType description of $requestType request already registered, ignoring")
44+
} else {
45+
description = requestMeta?.description
46+
}
47+
} ?: RequestBody(map.toMutableMap(), description = requestMeta?.description)
3348
}
3449

3550
companion object {

src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/ResponseHandlerModule.kt

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.papsign.ktor.openapigen.modules.handlers
33
import com.papsign.kotlin.reflection.getKType
44
import com.papsign.ktor.openapigen.OpenAPIGen
55
import com.papsign.ktor.openapigen.annotations.Response
6+
import com.papsign.ktor.openapigen.classLogger
67
import com.papsign.ktor.openapigen.content.type.ContentTypeProvider
78
import com.papsign.ktor.openapigen.content.type.ResponseSerializer
89
import com.papsign.ktor.openapigen.content.type.SelectedSerializer
@@ -17,19 +18,29 @@ import kotlin.reflect.KType
1718
import kotlin.reflect.full.findAnnotation
1819

1920
class ResponseHandlerModule<T : Any>(val responseClass: KClass<T>, val responseType: KType, val responseExample: T? = null) : OperationModule {
21+
private val log = classLogger()
2022
override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: Operation) {
2123

22-
val description = responseClass.findAnnotation<Response>()
23-
val statusCode = description?.statusCode?.let { HttpStatusCode.fromValue(it) } ?: HttpStatusCode.OK
24-
operation.responses[statusCode.value.toString()] = StatusResponse(
25-
description?.description ?: statusCode.description,
26-
content = provider.ofClass<ResponseSerializer>().mapNotNull {
27-
val mediaType = it.getMediaType(responseType, apiGen, provider, responseExample, ContentTypeProvider.Usage.SERIALIZE)
28-
?: return@mapNotNull null
29-
provider.registerModule(SelectedSerializer(it))
30-
mediaType.map { Pair(it.key.toString(), it.value) }
31-
}.flatten().fold(mutableMapOf()) { a, b -> a[b.first] = b.second; a }
32-
)
24+
val responseMeta = responseClass.findAnnotation<Response>()
25+
val statusCode = responseMeta?.statusCode?.let { HttpStatusCode.fromValue(it) } ?: HttpStatusCode.OK
26+
val status = statusCode.value.toString()
27+
val map = provider.ofClass<ResponseSerializer>().mapNotNull {
28+
val mediaType = it.getMediaType(responseType, apiGen, provider, responseExample, ContentTypeProvider.Usage.SERIALIZE)
29+
?: return@mapNotNull null
30+
provider.registerModule(SelectedSerializer(it))
31+
mediaType.map { Pair(it.key.toString(), it.value) }
32+
}.flatten().associate { it }
33+
val descstr = responseMeta?.description ?: statusCode.description
34+
operation.responses[status] = operation.responses[status]?.apply {
35+
map.forEach { (key, value) ->
36+
content.putIfAbsent(key, value)?.let { if (value != it) log.warn("ContentType of $responseType response $key already registered, ignoring $value") }
37+
}
38+
if (description != statusCode.description) {
39+
if (responseMeta?.description != null) log.warn("ContentType description of $responseType response already registered, ignoring")
40+
} else {
41+
description = responseMeta?.description ?: statusCode.description
42+
}
43+
} ?: StatusResponse(descstr, content = map.toMutableMap())
3344
}
3445

3546
companion object {

src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/RouteHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ object RouteHandler: HandlerModule {
3333
pathItem.parameters = (pathItem.parameters + parameters).distinct()
3434
methods.forEach {
3535
val name = it.method.value.toLowerCase()
36-
if (pathItem.containsKey(name)) error("$path::$name already defined")
36+
//if (pathItem.containsKey(name)) error("$path::$name already defined")
3737
val op = pathItem.getOrPut(name) { Operation() } as Operation
3838
operationModules.forEach {
3939
it.configure(apiGen, provider, op)

src/main/kotlin/com/papsign/ktor/openapigen/modules/handlers/ThrowOperationHandler.kt

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.papsign.ktor.openapigen.modules.handlers
22

3+
import com.papsign.kotlin.reflection.unitKType
34
import com.papsign.ktor.openapigen.OpenAPIGen
5+
import com.papsign.ktor.openapigen.classLogger
46
import com.papsign.ktor.openapigen.content.type.ContentTypeProvider
57
import com.papsign.ktor.openapigen.content.type.ResponseSerializer
68
import com.papsign.ktor.openapigen.content.type.SelectedSerializer
@@ -14,12 +16,14 @@ import com.papsign.ktor.openapigen.openapi.Schema
1416
import com.papsign.ktor.openapigen.openapi.StatusResponse
1517

1618
object ThrowOperationHandler : OperationModule {
19+
private val log = classLogger()
1720
override fun configure(apiGen: OpenAPIGen, provider: ModuleProvider<*>, operation: Operation) {
1821

1922
val exceptions = provider.ofClass<ThrowInfoProvider>().flatMap { it.exceptions }
2023
exceptions.groupBy { it.status }.forEach { exceptions ->
21-
val mediaTypes: MutableMap<String, MediaType<*>> = exceptions.value.flatMap { ex ->
24+
val map: MutableMap<String, MediaType<*>> = exceptions.value.flatMap { ex ->
2225
provider.ofClass<ResponseSerializer>().mapNotNull {
26+
if (ex.contentType == unitKType) return@mapNotNull null
2327
val mediaType = it.getMediaType<Any>(ex.contentType, apiGen, provider, null, ContentTypeProvider.Usage.SERIALIZE) ?: return@mapNotNull null
2428
provider.registerModule(SelectedSerializer(it))
2529
mediaType.map { Pair(it.key.toString(), it.value) }
@@ -33,10 +37,13 @@ object ThrowOperationHandler : OperationModule {
3337
}
3438
MediaType(schema)
3539
}.toMutableMap()
36-
val status = exceptions.key
37-
val prev = operation.responses[status.value.toString()]
38-
if (prev != null) error("Error Mapping Exception handlers on $status, it is already in use by ${status.description(prev.description)}")
39-
operation.responses[status.value.toString()] = StatusResponse(status.description, content = mediaTypes)
40+
val statusCode = exceptions.key
41+
val status = statusCode.value.toString()
42+
operation.responses[status] = operation.responses[status]?.apply {
43+
map.forEach { (key, value) ->
44+
content.putIfAbsent(key, value)?.let { if (value != it) log.warn("Cannot map Exception handler on $status with type $key, it is already in use by ${statusCode.description(description)}") }
45+
}
46+
} ?: StatusResponse(statusCode.description, content = map.toMutableMap())
4047
}
4148
}
4249
}

src/main/kotlin/com/papsign/ktor/openapigen/modules/schema/PrimitiveSchemas.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ class PrimitiveSchemas(val namer: SchemaNamer) : PartialSchemaRegistrar {
2121

2222
companion object {
2323
private val basicSchemas = LinkedHashSchemaMap().apply {
24+
registerSchema<Any>(DataType.`object`)
25+
registerSchema<Any?>(DataType.`object`)
26+
2427
registerSchema<Boolean>(DataType.boolean)
2528
registerSchema<Boolean?>(DataType.boolean)
2629

src/main/kotlin/com/papsign/ktor/openapigen/openapi/OpenAPIModels.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ enum class DataFormat {
3434
int32, int64, float, double, string, byte, binary, date, `date-time`, password, email, uuid
3535
}
3636

37-
data class RequestBody<T>(
38-
var content: MutableMap<String, MediaType<T>>,
37+
data class RequestBody(
38+
var content: MutableMap<String, MediaType<*>>,
3939
var description: String? = null,
4040
var required: Boolean? = null
4141
)
@@ -105,7 +105,7 @@ data class Components(
105105
var responses: MutableMap<String, StatusResponse> = sortedMapOf(),
106106
var parameters: MutableMap<String, Parameter<*>> = sortedMapOf(),
107107
var examples: MutableMap<String, Example<*>> = sortedMapOf(),
108-
var requestBodies: MutableMap<String, RequestBody<*>> = sortedMapOf(),
108+
var requestBodies: MutableMap<String, RequestBody> = sortedMapOf(),
109109
var headers: MutableMap<String, Header<*>> = sortedMapOf(),
110110
var securitySchemes: MutableMap<String, SecurityScheme<*>> = sortedMapOf()
111111
//links

src/main/kotlin/com/papsign/ktor/openapigen/openapi/Operation.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ data class Operation(
77
var externalDocs: ExternalDocumentation? = null,
88
var operationId: String? = null,
99
var parameters: List<Parameter<*>>? = null,
10-
var requestBody: RequestBody<*>? = null,
10+
var requestBody: RequestBody? = null,
1111
var responses: MutableMap<String, StatusResponse> = mutableMapOf(),
1212
// var callbacks ...
1313
var deprecated: Boolean? = null,

0 commit comments

Comments
 (0)