Home > Software design >  Kotlin validator for Pairs of objects in list doesn't work
Kotlin validator for Pairs of objects in list doesn't work

Time:06-14

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.

  • Related