Open cerker opened 3 years ago
What do you think about using run
to conditionally include specific validators?
EDIT: this doesn't work as I expected - ifPresent
doesn't run conditionally, and will always execute the run
, which adds the validation whether validUntil
is null or not.
import java.time.LocalDate
import java.time.LocalDateTime
import io.konform.validation.Validation
data class User(
val validFrom: LocalDateTime,
val validUntil: LocalDateTime?,
) {
companion object {
/** validate two fields on User */
private val validateUserDates = Validation<User> {
addConstraint("before/after") {
it.validUntil?.isAfter(it.validFrom) ?: false
}
}
/** public validator, combining private validators */
val validateUser = Validation<User> {
User::validUntil ifPresent {
run(validateUserDates)
}
}
}
}
fun main() {
val time = LocalDate.of(2020, 1, 1).atStartOfDay()
val userValid = User(time, time.plusDays(10))
println(User.validateUser(userValid))
// Valid(value=User(validFrom=2020-01-01T00:00, validUntil=2020-01-11T00:00))
val userInvalid = User(time, time.plusDays(-10))
println(User.validateUser(userInvalid))
// Invalid(errors=[ValidationError(dataPath=, message=before/after)])
// EDIT: the `ifPresent` didn't work as I expected
val userNoUntil = User(time, null)
println(User.validateUser(userNoUntil))
// Invalid(errors=[ValidationError(dataPath=, message=before/after)])
}
The downside is that the error message isn't dynamic. It would be nice to have a Constraint
implementation that can dynamically create a message.
I'm a little surprised this feature is missing. I keep running into this, and it feels like the most missed feature in good ol' JSR303.
Example I have right now: validate a postal code depending on the country.
Basically, it should be possible to select validations to apply to an object dynamically from the objects runtime state, not just statically. Assuming custom validations:
val validateAddress = Validation<Address> {
Address::countryCode {
validIsoCountryCode()
}
Address::postalCode dynamicValidation { it: Address ->
when (it.countryCode) {
"US" -> validUsPostalCode()
"DE" -> validGermanPostalCode()
else -> // default validation of some kind
}
}
}
I'm not quite sure, why ValidationBuilder
should not give reference inside init
block to the variable under validation:
val validateAddress = Validation<Address> { it: Address ->
Address::countryCode {
validIsoCountryCode()
}
Address::postalCode {
when (it.countryCode) {
"US" -> validUsPostalCode()
"DE" -> validGermanPostalCode()
else -> // default validation of some kind
}
}
}
Is there a reason behind building static validators (caching validators by PropKey?)?
Is it possible to validate one field in relation to others?
For example, my user has a validFrom: LocalDateTime and a validUntil: LocalDateTime? field. How can I validate that validUntil is after validFrom?
doesn't the following snippet address your scenario?
data class User(val validFrom: LocalDateTime, val validUntil: LocalDateTime?) {
companion object {
val validateUser = Validation<User> {
addConstraint("validUntil, if present, must be after validFrom") {
it.validUntil?.isAfter(it.validFrom) ?: true
}
}
}
}
doesn't the following snippet address your scenario?
Hey @minisaw I'm not @cerker but the limitation of what you suggested is that the error is associated with the top level value, not the field (i.e. the dataPath will be ""
rather than ".validUntil"
).
@nlochschmidt do you have any intention to support cross-field validation in konform? I tried looking through the issues for more context on this feature request.
Is it possible to validate one field in relation to others?
For example, my user has a validFrom: LocalDateTime and a validUntil: LocalDateTime? field. How can I validate that validUntil is after validFrom?
I started writing a custom validator:
but how can I pass the other value?