Validk is a validation framework for Kotlin (JVM), largely inspired by Konform. Among other things, the design aims to solve some specialised use cases like context-aware and conditional validation.
The core framework provides a typesafe DSL and has zero dependencies.
An additional module enables integration with Micronaut. For details please see Validk Micronaut docs.
implementation "io.resoluteworks:validk:${validkVersion}"
data class Employee(
val name: String,
val email: String?
)
data class Organisation(
val name: String, val
employees: List<Employee>
)
val validation = validation<Organisation> {
Organisation::name { minLength(5) }
Organisation::employees each {
Employee::name { minLength(10) }
Employee::email ifNotNull { email() }
}
}
val org = Organisation(
"A", listOf(
Employee("John", "john@test.com"),
Employee("Hannah Johnson", "hanna")
)
)
val errors = validation.validate(org)
errors?.errors?.forEach { println(it) }
This would print
ValidationError(propertyPath=name, errorMessage=must be at least 5 characters)
ValidationError(propertyPath=employees[0].name, errorMessage=must be at least 10 characters)
ValidationError(propertyPath=employees[1].email, errorMessage=must be a valid email)
Validating an object returns a ValidationErrors
which is null
when validation succeeds.
In other words, validation is successful when the response is null
, or an instance of ValidationErrors
when it fails.
Please check the tests for more examples and the documentation for a full list of constraints.
It’s often only required to return the first failure (failed constraint) message when validating a field. This is usually the case when displaying user errors in an application and when the order of the constraints implies the next one would fail: notBlank()
failing implies email()
will fail, but we first want to respond with “Email is required” rather than [“Email is required”, “This is not a valid email”].
For this purpose ValidationErrors
provides eager*
versions of its properties, including eagerErrors
and eagerErrorMessages
. For a full list of properties please check the ValiationErrors docs
Validk provides the ability to access the object being validated using the withValue
construct.
private data class Entity(
val entityType: String,
val registeredOffice: String,
val proofOfId: String
)
private enum class EntityType { COMPANY, PERSON }
validation<Entity> {
Entity::entityType { enum<EntityType>() }
withValue { entity ->
when (entity.entityType) {
"PERSON" -> Entity::proofOfId { minLength(10) }
"COMPANY" -> Entity::registeredOffice { minLength(5) }
}
}
}
Alternatively, you can add validation logic based on the value of a specific property using whenIs
.
val validation = validation<Entity> {
Entity::entityType { enum<EntityType>() }
Entity::entityType.whenIs("PERSON") {
Entity::proofOfId { minLength(10) }
}
Entity::entityType.whenIs("COMPANY") {
Entity::registeredOffice { minLength(5) }
}
}
ValidObject
provides a basic mechanism for storing the validation logic within the object itself.
data class MyObject(val name: String, val age: Int) : ValidObject<MyObject> {
override fun validation(): Validation<MyObject> {
return validation {
MyObject::name { notBlank() }
MyObject::age { min(18) }
}
}
}
val result = MyObject("John Smith", 12).validate()
validation<Person> {
Person::name {
notBlank() message "A person needs a name"
matches("[a-zA-Z\\s]+") message "Letters only please"
}
}
For a complete guide on integrating Validk with Micronaut please see the reference documentation.