https://github.com/resoluteworks/validk
https://github.com/resoluteworks/validk
Last synced: 6 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/resoluteworks/validk
- Owner: resoluteworks
- License: apache-2.0
- Created: 2024-03-09T16:44:03.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2025-12-22T20:04:19.000Z (6 months ago)
- Last Synced: 2025-12-24T09:03:21.839Z (6 months ago)
- Language: Kotlin
- Size: 408 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
README
# Validk


Validk is a validation framework for Kotlin JVM designed with these goals in mind:
* Typesafe DSL to defining validation rules
* No annotations or "magic"
* Value-aware and conditional validation rules (aka dynamic validation)
* Zero dependencies
Documentation:
* [API Docs](https://resoluteworks.github.io/validk/validk/validk/io.validk/index.html)
* [Built-in constraints](https://resoluteworks.github.io/validk/validk/validk/io.validk.constraints/index.html)
* [Previous 1.x version](https://github.com/resoluteworks/validk/tree/v1.2.9)
## Dependency
```groovy
implementation "io.resoluteworks:validk:${validkVersion}"
```
## Quick start
```kotlin
data class Employee(val name: String, val email: String?)
data class Organisation(val name: String, val employees: List)
val organisationValidation = Validation {
// Organisation name should be at least 5 characters long
Organisation::name { minLength(5) }
Organisation::employees each {
// Each employee should have a name that is at least 10 characters long.
Employee::name { minLength(10) }
// An employee can have an email address, but it's not required.
// When present, it should be a valid email.
Employee::email ifNotNull { email() }
}
}
val org = Organisation(
name = "ACME",
employees = listOf(Employee("John", "john@test.com"), Employee("Hannah Johnson", "hanna"))
)
val result: ValidationResult = organisationValidation.validate(org)
when (result) {
is ValidationResult.Success -> println("Validation success")
is ValidationResult.Failure -> result.allErrors.forEach { println(it) }
}
```
The call `organisationValidation.validate(org)` returns a `ValidationResult` which can be either a
`ValidationResult.Success` or a `ValidationResult.Failure`. The `ValidationResult.Failure` returns the details
of the validation errors.
The code above would print the following:
```text
ValidationError(path=name, message=Must be at least 5 characters long)
ValidationError(path=employees[0].name, message=Must be at least 10 characters long)
ValidationError(path=employees[1].email, message=Must be a valid email)
```
## Error messages
Error messages can be customised with any of the following constructs.
```kotlin
// Dynamic error message
Employee::email {
email() message { value -> "Invalid email address: $value" }
}
// Static error message
Employee::email {
email() message "This emails address is invalid"
}
```
## Custom constraints
You can define custom constraints by calling `addConstraint` inside a validation block.
```kotlin
Employee::name{
addConstraint("Must start with uppercase letter") {
it.first().isUpperCase() == true
}
}
```
## Dynamic validation
There are several options for defining validation rules which apply in a specific context or when
the value being validated meets a certain condition.
### withValue
The `withValue` lambda receives the object being validated and allows you to define constraints based
on its properties or state.
```kotlin
data class Entity(
val type: String,
val registeredOffice: String,
val proofOfId: String
)
Validation {
withValue { entity ->
when (entity.type) {
"PERSON" -> Entity::proofOfId { minLength(10) }
"COMPANY" -> Entity::registeredOffice { minLength(5) }
}
}
}
```
### whenIs
The `whenIs` construct allows you to define constraints based on specific values for a property.
```kotlin
Validation {
Entity::entityType.whenIs("PERSON") {
Entity::proofOfId { minLength(10) }
}
Entity::entityType.whenIs("COMPANY") {
Entity::registeredOffice { minLength(5) }
}
}
```
## Convert validation result
A `ValidationResult` can be converted to a custom type using the `map` function. This is typically
useful when a custom application state is required as the result of a validation. A common example
would be a web application that would return a different HTTP response or status code based on the
validation result.
```kotlin
val httpResponse = validation.validate(personForm).map {
success { person ->
Response(HttpStatus.OK, person)
}
error { person, errors ->
Response(HttpStatus.BAD_REQUEST, person, errors)
}
}
```
## Fail-fast validation
It's often required to only return the first failure message (first failed constraint) when validating a property.
This is the case, for example, when displaying user errors in a UI, and when the order of the constraints
implies the next ones would fail anyway (and thus don't need checking).
For example, let's say that we have an `email` field that's both required and needs to be a valid email.
In this case, if a `notBlank()` fails, that means that `email()` will fail as well. In this case, we'd like to
return only the first error message, which would be
```
"Email is required"
```
rather than
```
["Email is required", "This is not a valid email"].
```
We call this fail-fast validation and it's enabled by default. Fail-fast validation can be configured when creating
the `Validation` object. The example below will check all the constraints and return all the errors.
```kotlin
Validation {
failFast(false)
Person::name {
notBlank()
matches("[a-zA-Z]+ [a-zA-Z]+")
}
}
```
When turning fail-fast off, you can still opt to only select the first error message post-validation, by using
`ValidationErrors.error(propertyPath)`. See [ValiationErrors docs](https://resoluteworks.github.io/validk/validk/validk/io.validk/-validation-errors/index.html) for more details.
## ValidObject
`ValidObject` provides a basic mechanism for storing the validation logic within the object itself.
```kotlin
data class Person(val name: String, val email: String) : ValidObject {
override val validation: Validation = Validation {
Person::name { minLength(10) }
Person::email { email() }
}
}
val validationResult = Person("John Smith", "john@test.com").validate()
```
## License
[Apache 2.0 License](LICENSE)