Skip to content

Commit ef8daf7

Browse files
committed
Fixed Generic types not creating proper models. Fixed creating separate models for nullable object types
1 parent 37453e0 commit ef8daf7

File tree

3 files changed

+25
-6
lines changed

3 files changed

+25
-6
lines changed

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

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package com.papsign.ktor.openapigen.modules.schema
22

33
import com.papsign.kotlin.reflection.getKType
4+
import com.papsign.kotlin.reflection.toInvariantFlexibleProjection
45
import com.papsign.kotlin.reflection.toKType
56
import com.papsign.ktor.openapigen.classLogger
67
import com.papsign.ktor.openapigen.openapi.Schema
78
import kotlin.reflect.KType
9+
import kotlin.reflect.KTypeParameter
810
import kotlin.reflect.KVisibility
911
import kotlin.reflect.full.declaredMemberProperties
1012
import kotlin.reflect.full.isSubclassOf
1113
import kotlin.reflect.full.starProjectedType
14+
import kotlin.reflect.full.withNullability
1215
import kotlin.reflect.jvm.jvmErasure
1316

1417
open class SimpleSchemaRegistrar(val namer: SchemaNamer) : SchemaRegistrar {
@@ -52,17 +55,23 @@ open class SimpleSchemaRegistrar(val namer: SchemaNamer) : SchemaRegistrar {
5255

5356
private fun SchemaRegistrar.makeObjectSchema(type: KType): Schema<*> {
5457
val erasure = type.jvmErasure
58+
val typeParameters = erasure.typeParameters.zip(type.arguments).associate { Pair(it.first.name, it.second.type) }
5559
if (erasure.isSealed) {
5660
return Schema.OneSchemaOf(erasure.sealedSubclasses.map { get(it.starProjectedType).schema })
5761
}
58-
val props = erasure.declaredMemberProperties.filter { it.visibility == KVisibility.PUBLIC }
59-
val properties = props.associate {
60-
Pair(it.name, get(it.returnType).schema)
61-
}
62+
val props = erasure.declaredMemberProperties.filter { it.visibility == KVisibility.PUBLIC }.associateWith {
63+
val retType = it.returnType
64+
when(val classifier = retType.classifier) {
65+
is KTypeParameter -> typeParameters[classifier.name] ?: it.returnType
66+
else -> it.returnType
67+
} }
68+
val properties = props.map { (key, value) ->
69+
Pair(key.name, get(value.withNullability(false)).schema)
70+
}.associate { it }
6271
if (properties.isEmpty()) log.warn("No public properties found in object $type")
6372
return Schema.SchemaObj<Any>(
6473
properties,
65-
props.filter { !it.returnType.isMarkedNullable }.map { it.name })
74+
props.filterValues { value -> !value.isMarkedNullable }.map { it.key.name })
6675
}
6776

6877
private fun SchemaRegistrar.makeMapSchema(type: KType): Schema<*> {

src/main/kotlin/com/papsign/ktor/openapigen/parameters/handlers/ModularParameterHander.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import io.ktor.util.toMap
1414
import kotlin.reflect.KFunction
1515
import kotlin.reflect.KParameter
1616
import kotlin.reflect.full.findAnnotation
17+
import kotlin.reflect.full.withNullability
1718

1819
class ModularParameterHander<T>(val parsers: Map<KParameter, Builder<*>>, val constructor: KFunction<T>) :
1920
ParameterHandler<T> {
@@ -30,7 +31,7 @@ class ModularParameterHander<T>(val parsers: Map<KParameter, Builder<*>>, val co
3031
`in`,
3132
!param.type.isMarkedNullable
3233
).also {
33-
it.schema = apiGen.schemaRegistrar[param.type].schema as Schema<Any>
34+
it.schema = apiGen.schemaRegistrar[param.type.withNullability(false)].schema as Schema<Any>
3435
config(it)
3536
}
3637
}

src/test/kotlin/Basic.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ object Basic {
9494
respond(body)
9595
}
9696
}
97+
98+
route("generic") {
99+
post<Unit, GenericTest<A?>, GenericTest<A?>> { params, body ->
100+
respond(body)
101+
}
102+
}
97103
}
98104
}.start(true)
99105

@@ -108,6 +114,9 @@ object Basic {
108114

109115
data class A(val b: String)
110116

117+
@Request
118+
data class GenericTest<T>(val value: T)
119+
111120
// A response can be any class, but a description will be generated from the annotation
112121
@Response("A String Response")
113122
data class StringResponse(val str: String)

0 commit comments

Comments
 (0)