Akkurate provides a DSL to help you write clear and concise code. With it, you can validate your data in a more declarative manner, improving readability and maintainability.
Access properties
To access a property, write its name:
@Validate
data class Book(val title: String)
Validator<Book> {
title.isNotEmpty()
}
To access a deep property, write the path to it, like ordinary Kotlin code:
@Validate
data class Book(val author: Author)
@Validate
data class Author(val user: User)
@Validate
data class User(val emailAddress: String)
Validator<Book> {
author.user.emailAddress.isNotEmpty()
}
Avoid path repetition
When applying multiple constraints to a single property, it can become cumbersome to write its path each time to constrain it. You can avoid repetition by writing the path and invoking it with a lambda:
When you need to validate multiple properties in the same manner, you can use validatable compounds to write the constraints only once.
To showcase this, let's add new properties to the User class:
@Validate
data class User(
val emailAddress: String,
// Extend the `User` class with new properties
val firstName: String,
val middleName: String,
val lastName: String
)
Now you can either apply the same constraints to each property, or use a validatable compound:
Validator<User> {
(firstName and middleName and lastName) {
isNotEmpty()
hasLengthLowerThanOrEqualTo(50)
}
}
Use nullable types
When manipulating nullable types, there is no need to handle nullability during path traversal. Let's demonstrate this by making the author nullable inside Book:
@Validate
data class Book(val author: Author?)
When accessing author, it returns a Validatable<Author?> type:
Even if author is nullable, it is possible to not bother and access its child properties without any nullability management. However, the nullability will propagate to the children:
@Validate
data class Book(val author: Author)
Validator<Book> {
author // Validatable<Author>
author.user // Validatable<User>
author.user.emailAddress // Validatable<String>
}
@Validate
data class Book(val author: Author?)
Validator<Book> {
author // Validatable<Author?>
author.user // Validatable<User?>
author.user.emailAddress // Validatable<String?>
}
This behavior allows applying constraints on a deep property without having to care about nullability. If the property is null, the constraint will always succeed:
Validator<Book> {
// This constraint will always succeed when
// the value of the property is `null`.
author.user.emailAddress.isNotEmpty()
}
If you want to enforce your users to provide a value for a property, use the isNotNull constraint:
Validator<Book> {
author.user.emailAddress {
isNotNull() // Fails if the value is null.
isNotEmpty() // When not null, checks for emptiness.
}
}
Unwrapping the value
Sometimes it might be necessary to access the original value of the property. When calling unwrap() on a Validatable<T>, it returns the T value that was wrapped:
@Validate
data class Book(val title: String)
val validateBook = Validator<Book> {
println(title)
println(title.unwrap())
}
validateBook(Book(title = "The Lord of the Rings"))
// prints: Validatable(unwrap=The Lord of the Rings, path=[title])
// prints: The Lord of the Rings
val validateBook = Validator<Book> {
val (unwrappedTitle) = title
println(unwrappedTitle)
}
validateBook(Book(title = "The Lord of the Rings"))
// prints: The Lord of the Rings