Skip to content

Commit 1f8762e

Browse files
committed
improved validation handler
1 parent 63488e8 commit 1f8762e

File tree

3 files changed

+109
-49
lines changed

3 files changed

+109
-49
lines changed

src/main/kotlin/com/papsign/ktor/openapigen/validators/ValidationHandler.kt

Lines changed: 83 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,68 +4,98 @@ import com.papsign.kotlin.reflection.getKType
44
import com.papsign.ktor.openapigen.classLogger
55
import com.papsign.ktor.openapigen.modules.ModuleProvider
66
import com.papsign.ktor.openapigen.modules.ofClass
7-
import java.lang.Error
8-
import java.lang.Exception
9-
import kotlin.reflect.*
10-
import kotlin.reflect.full.functions
11-
import kotlin.reflect.full.instanceParameter
12-
import kotlin.reflect.full.memberProperties
7+
import kotlin.reflect.KClass
8+
import kotlin.reflect.KFunction
9+
import kotlin.reflect.KProperty1
10+
import kotlin.reflect.KType
11+
import kotlin.reflect.full.*
1312
import kotlin.reflect.jvm.jvmErasure
1413

1514
class ValidationHandler<T>(type: KType, context: ModuleProvider<*>) {
1615
private val log = classLogger()
1716

1817
private val transformFun: ((T) -> T)?
18+
1919
init {
20-
val validators = context.ofClass<Validator<Any, Annotation>>()
21-
val eligibleProperties = try { type.jvmErasure.memberProperties } catch (e: Error) { listOf<KProperty1<T, *>>() }
22-
val targets = eligibleProperties.associateWith { prop ->
23-
val annotations = prop.annotations.map { it.annotationClass }.toSet()
24-
val handler = ValidationHandler<Any?>(prop.returnType, context)
25-
val appliedValidators = validators.filter {
26-
val applied = (annotations as Set<KClass<Annotation>>).contains(it.annotationClass)
27-
if (applied && !it.isTypeSupported(prop.returnType.jvmErasure)) {
28-
log.error("Validator ${it::class.simpleName} does not support type ${prop.returnType} of $prop and will be ignored")
29-
false
30-
} else {
31-
applied
32-
}
33-
}.associateWith {
34-
prop.annotations.filter { annot -> it.annotationClass.isInstance(annot) }
20+
val nonNullType = type.withNullability(false)
21+
when {
22+
nonNullType.isSubtypeOf(arrayErasure) -> {
23+
val arrtype = type.arguments[0].type!!
24+
val handler = ValidationHandler<Any?>(arrtype, context)
25+
transformFun = if (handler.isUseful()) {
26+
{ t: T -> if (t != null) (t as Array<Any?>).map { handler.handle(it) }.toTypedArray() as T else t }
27+
} else null
3528
}
36-
val validations = appliedValidators.flatMap { (valid, annots) ->
37-
annots.map { annot ->
38-
log.trace("Applying validator ${valid::class} with $annot on $prop");
39-
{ t: Any? -> valid.validate(t, annot) }
29+
nonNullType.isSubtypeOf(iterableErasure) -> {
30+
val listtype = type.arguments[0].type!!
31+
val handler = ValidationHandler<Any?>(listtype, context)
32+
transformFun = when {
33+
nonNullType.isSupertypeOf(listErasure) -> {
34+
log.error("Supertypes of List are not yet supported, ignoring $type")
35+
null
36+
}
37+
handler.isUseful() -> {
38+
{ t: T -> if (t != null) (t as Iterable<Any?>).map { handler.handle(it) } as T else t }
39+
}
40+
else -> null
4041
}
4142
}
42-
if (validations.isNotEmpty()) {
43-
if (handler.isUseful()) {
44-
{ t: Any? -> validations.fold(handler.handle(t)) { v, op -> op(v) } }
45-
} else {
46-
{ t: Any? -> validations.fold(t) { v, op -> op(v) } }
43+
else -> {
44+
val validators = context.ofClass<Validator<Any, Annotation>>()
45+
val eligibleProperties = try {
46+
type.jvmErasure.memberProperties
47+
} catch (e: Error) {
48+
listOf<KProperty1<T, *>>()
4749
}
48-
} else {
49-
if (handler.isUseful()) {
50-
handler::handle
51-
} else {
52-
null
50+
val targets = eligibleProperties.associateWith { prop ->
51+
val annotations = prop.annotations.map { it.annotationClass }.toSet()
52+
val handler = ValidationHandler<Any?>(prop.returnType, context)
53+
val appliedValidators = validators.filter {
54+
val applied = (annotations as Set<KClass<Annotation>>).contains(it.annotationClass)
55+
if (applied && !it.isTypeSupported(prop.returnType.jvmErasure)) {
56+
log.error("Validator ${it::class.simpleName} does not support type ${prop.returnType} of $prop and will be ignored")
57+
false
58+
} else {
59+
applied
60+
}
61+
}.associateWith {
62+
prop.annotations.filter { annot -> it.annotationClass.isInstance(annot) }
63+
}
64+
val validations = appliedValidators.flatMap { (valid, annots) ->
65+
annots.map { annot ->
66+
log.trace("Applying validator ${valid::class.simpleName} with @${annot.annotationClass.simpleName} on $prop");
67+
{ t: Any? -> valid.validate(t, annot) }
68+
}
69+
}
70+
if (validations.isNotEmpty()) {
71+
if (handler.isUseful()) {
72+
{ t: Any? -> validations.fold(handler.handle(t)) { v, op -> op(v) } }
73+
} else {
74+
{ t: Any? -> validations.fold(t) { v, op -> op(v) } }
75+
}
76+
} else {
77+
if (handler.isUseful()) {
78+
handler::handle
79+
} else {
80+
null
81+
}
82+
}
83+
}.filterValues { it != null } as Map<KProperty1<T, *>, (Any?) -> Any?>
84+
transformFun = when {
85+
targets.isEmpty() -> null
86+
type.jvmErasure.isData -> {
87+
val copy = type.jvmErasure.functions.find { it.name == "copy" } as KFunction<T>
88+
val propMap = copy.parameters.associateBy { it.name }
89+
val mapped = targets.map { (prop, fn) -> Pair(propMap[prop.name]!!, Pair(prop, fn)) }.associate { it }
90+
val instanceParam = copy.instanceParameter!!
91+
{ t: T -> if (t != null) copy.callBy(mapOf(instanceParam to t) + mapped.mapValues { it.value.second(it.value.first.get(t)) }) else t }
92+
}
93+
else -> {
94+
log.error("Validators are only supported on data classes, tried on: $type")
95+
null
96+
}
5397
}
5498
}
55-
}.filterValues { it != null } as Map<KProperty1<T, *>, (Any?) -> Any?>
56-
transformFun = when {
57-
targets.isEmpty() -> null
58-
type.jvmErasure.isData -> {
59-
val copy = type.jvmErasure.functions.find { it.name == "copy" } as KFunction<T>
60-
val propMap = copy.parameters.associateBy { it.name }
61-
val mapped = targets.map { (prop, fn) -> Pair(propMap[prop.name]!!, Pair(prop, fn)) }.associate { it }
62-
val instanceParam = copy.instanceParameter!!
63-
{ t: T -> if (t != null) copy.callBy(mapOf(instanceParam to t) + mapped.mapValues { it.value.second(it.value.first.get(t)) }) else t }
64-
}
65-
else -> {
66-
log.error("Validators are only supported on data classes, tried on: $type")
67-
null
68-
}
6999
}
70100
}
71101

@@ -83,3 +113,7 @@ class ValidationHandler<T>(type: KType, context: ModuleProvider<*>) {
83113
}
84114
}
85115
}
116+
117+
val arrayErasure = getKType<Array<*>>().jvmErasure.starProjectedType
118+
val iterableErasure = getKType<Iterable<*>>().jvmErasure.starProjectedType
119+
val listErasure = getKType<List<*>>().jvmErasure.starProjectedType
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.papsign.ktor.openapigen.validators.defaults
2+
3+
import com.papsign.ktor.openapigen.validators.util.AValidator
4+
5+
@Retention(AnnotationRetention.RUNTIME)
6+
@Target(AnnotationTarget.PROPERTY)
7+
annotation class LowerCase
8+
9+
object LowerCaseValidator : AValidator<String, LowerCase>(String::class, LowerCase::class) {
10+
override fun validate(subject: String?, annotation: LowerCase): String? {
11+
return subject?.toLowerCase()
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.papsign.ktor.openapigen.validators.defaults
2+
3+
import com.papsign.ktor.openapigen.validators.util.AValidator
4+
5+
@Retention(AnnotationRetention.RUNTIME)
6+
@Target(AnnotationTarget.PROPERTY)
7+
annotation class Trim
8+
9+
object TrimValidator : AValidator<String, Trim>(String::class, Trim::class) {
10+
override fun validate(subject: String?, annotation: Trim): String? {
11+
return subject?.trim()
12+
}
13+
}

0 commit comments

Comments
 (0)