I have a data class
which I need to validate:
import javax.validation.Valid
import whatever.pckg.validation.PkiSignWithBusinessCode
import whatever.pckg.validation.NullOrNotBlank
data class UploadFileReq(
val id: String? = null,
...(other fields)...
@get:Valid
val signaturesInfo: MutableList<Pair<SignatureInfo, Object>> = mutableListOf() # Object here is for simplicity
) {
@PkiSignWithBusinessCode
data class SignatureInfo(
val typeSign: String = "",
@get:NullOrNotBlank
val businessCode: String? = null,
)
}
@NullOrNotBlank
annotation is just a simple merge of standard @NotBlank
and @Null
annotations.
I also have another custom validation annotation @PkiSignWithBusinessCode
, its definition is below:
import whatever.pckg.UploadFileReq
import javax.validation.*
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.reflect.KClass
@Constraint(validatedBy = [PkiSignWithBusinessCodeValidator::class])
@Target(AnnotationTarget.CLASS)
@Retention(RUNTIME)
annotation class PkiSignWithBusinessCode(
val message: String = "PKI signature requires filled businessCode",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class PkiSignWithBusinessCodeValidator: ConstraintValidator<PkiSignWithBusinessCode, UploadFileReq.SignatureInfo>> {
override fun isValid(obj: UploadFileReq.SignatureInfo?, context: ConstraintValidatorContext): Boolean {
if (obj != null) {
if ((obj.typeSign == "PKI") && (obj.businessCode == null)) {
return false
}
}
return true
}
Logic of above annotation is quite simple - when typeSign
equals PKI
and businessCode
is null
, then validator should treat that as invalid object.
For your reference here's a simple unit-test that tries to check the work of @PkiSignWithBusinessCode
:
import org.junit.jupiter.api.Test
import whatever.pckg.UploadFileReq
import javax.validation.Validation
import kotlin.test.assertEquals
class PkiSignWithBusinessCodeTest {
@Test
fun `validate PkiSignWithBusinessCodeTest`() {
val validator = Validation.buildDefaultValidatorFactory().validator
val signatureInfo = UploadFileReq.SignatureInfo(
typeSign = "PKI",
businessCode = null
)
val uploadFileReq = UploadFileReq(
null,
signaturesInfo = mutableListOf(signatureInfo to Object)
)
val result = validator.validate(uploadFileReq)
assertEquals(1, result.size)
assertEquals("PKI signature requires filled businessCode", result.first().messageTemplate)
}
}
But this test obviously fails on first assertion state: java.lang.AssertionError: Expected <1>, actual <0>.
So no constraint violations found by validator.
The problem is that Spring
ignores validation rule of above annotation. As an assumption I suppose that somehow Pair
class wrap prevents Spring
from using my validation annotation. Maybe it's a bug?
Or maybe I overlooked something in my code?
CodePudding user response:
Found a workaround on this - need to make own ValidatingPair
with @Valid
annotations on first
and second
members of this new Pair
:
import javax.validation.Valid
data class ValidatingPair<out A, out B>(
@get:Valid
public val first: A,
@get:Valid
public val second: B
) : java.io.Serializable {
override fun toString(): String = "($first, $second)"
}
And make:
val signaturesInfo: MutableList<Pair<SignatureInfo, Object>>
to become
val signaturesInfo: MutableList<ValidatingPair<SignatureInfo, Object>>
Then validation starts working for list members.