making / yavi

Yet Another Validation for Java (A lambda based type safe validation framework)
https://yavi.ik.am
Apache License 2.0
743 stars 61 forks source link

custom validation #22

Closed rodrigodevelms closed 4 years ago

rodrigodevelms commented 5 years ago

I have a validation that takes a string and returns a boolean. As I enter in the other validations. Example:

val validator: Validator<User> = ValidatorBuilder.of<User>()
 .konstraint(User::name).myCustomValidation().message("some message")
making commented 5 years ago

What’s the problem?

rodrigodevelms commented 5 years ago

I have not found a method to add a custom validation, where I enter a string and get a boolean

making commented 5 years ago

Sorry, I don’t get your question.

https://github.com/making/yavi#custom Here is a way to add custom constraints.

rodrigodevelms commented 5 years ago

I read it but I'm still lost. I'll explain better. I have the following class

data class User (
  val name: String,
  val lastName: String,
  val cnpj: String
) 

And I have the following function to validate the cnpj field

fun validarCnpj(cnpj: String): Boolean {
    return validateCNPJLength(cnpj) && validateCNPJRepeatedNumbers(cnpj)
            && validateCNPJVerificationDigit(true, cnpj)
            && validateCNPJVerificationDigit(false, cnpj)
}

/**
 * Verifies if the CNPJ has 14 digits.
 *
 * @return True if valid.
 */
private fun validateCNPJLength(cnpj: String) = cnpj.length == 14

/**
 * Verifies if the CNPJ is not repeated numbers.
 *
 * A CNPJ with repeated is considered invalid, ex:
 *
 *   '00000000000000'
 *   '11111111111111'
 *   '22222222222222'
 *   ...
 *   '88888888888888'
 *   '99999999999999'
 *
 * @return True if valid.
 */
private fun validateCNPJRepeatedNumbers(cnpj: String): Boolean {
    return (0..9)
        .map { it.toString().repeat(14) }
        .map { cnpj == it }
        .all { !it }
}

/**
 * Verifies the CNPJ verification digit.
 *
 * This algorithm checks the verification digit (dígito verificador) do CNPJ.
 * This was based from: https://www.devmedia.com.br/validando-o-cnpj-em-uma-aplicacao-java/22374
 *
 * @param[firstDigit] True when checking the first digit. False to check the second digit.
 *
 * @return True if valid.
 */
private fun validateCNPJVerificationDigit(firstDigit: Boolean, cnpj: String): Boolean {
    val startPos = when (firstDigit) {
        true -> 11
        else -> 12
    }
    val weightOffset = when (firstDigit) {
        true -> 0
        false -> 1
    }
    val sum = (startPos downTo 0).fold(0) { acc, pos ->
        val weight = 2 + ((11 + weightOffset - pos) % 8)
        val num = cnpj[pos].toString().toInt()
        val sum = acc + (num * weight)
        sum
    }
    val result = sum % 11
    val expectedDigit = when (result) {
        0, 1 -> 0
        else -> 11 - result
    }

    val actualDigit = cnpj[startPos + 1].toString().toInt()

    return expectedDigit == actualDigit
}

And now I'm trying:

val validator: Validator<User> = ValidatorBuilder.of<User>()
    .konstraint(User::name) {
        notNull().message("Field name is required")
    }
    .konstraint(User::cnpj) {
        notBlank().message("Field cnpj is required.")
           .//now I wanted to insert the validation I created for cnpj, but I do not know how.
    }
making commented 4 years ago

@rodrigodevelms I think this example is same as your case.

night-crawler commented 4 years ago

I'm trying to achieve almost the same here, except I need to validate an Instant field. It happened so that there are no constraints against Instant class were defined. I made something like this:


fun <T> notNullCondition(property: KProperty1<T, Any?>) =
        ConstraintCondition<T> { dto, _ -> property(dto) != null }

fun <T, V: Instant?> ValidatorBuilder<T>.instantConstraint(
        property: KProperty1<T, V>,
        getStart: () -> Instant = Instant::now,
        getEnd: () -> Instant = { Instant.MAX },
        errorTemplate: String = "Instant value {0} is illegal",
        chain: ValidatorBuilder<T>.(ValidatorBuilder<T>) -> Unit = { }
): ValidatorBuilder<T> {
    val whenTimeIsBad = Predicate<T> {
        val value = property(it)
        if (value == null) true
        else getStart() <= value && value <= getEnd()
    }

    konstraintOnCondition(notNullCondition(property)) {
        constraintOnTarget(whenTimeIsBad, property.name, "custom.instant", errorTemplate)
        chain(this)
    }
    return this
}

It works fine, except for a problem with dynamic boundaries for the checks that I want to have in my error message.

So, I have two questions:

Thank you.

making commented 4 years ago

@night-crawler Would you mind converting your snippet to Java so that I can understand you case well?

I'm still not sure if providing constraints on date/time type is useful. Bean Validation has @Past, @PastOrPresent, @Future and @FutureOrPresentthat I have never used. Maintaining date/time constraints is not that easy as it needs to support a lot of types (JSR-310 and legacy types). So I'd like to understand if it is really helpful.

night-crawler commented 4 years ago

@making I'll try to come up with something in Java later, but honestly, I've never written more than 10 lines of code in Java, so I need some time to do that :)

Also, the sole purpose of this check is to ensure that validTill date is in the future.

BTW, what about adding some runtime information in error message (except for the args that are already provided)?

making commented 4 years ago

@night-crawler

BTW, what about adding some runtime information in error message (except for the args that are already provided)?

What type of output do you expect? I don't get it from your sample.

night-crawler commented 4 years ago

@making In my example, I want to put the current Instant in the message (both from getStart() and getEnd() callbacks).

making commented 4 years ago

@night-crawler I got it and it makes sense to support that for custom constraints. I'm closing this issue as this issue does not seem to be the right place to discuss further. I've created new issues to support your requests #36 #37