envelopmenuskypeburger-menulink-externalfacebooktwitterlinkedin2crossgithub-minilinkedin-minitwitter-miniarrow_rightarrow_leftphonegithubphone-receiverstack-overflow

Vavr and data validation

UPDATE 14.06.2017: Since Javaslang has been renamed to Vavr, I have updated this post accordingly.

A validation of data in our applications is quite an important aspect. The exact way how we can implement the mechanism depends on a framework or libraries used in a project. If you are using Spring MVC then you have the possibility to use its internal mechanism of validation, based on Bean Validation standard (JSR-303 and JSR-349). However, there is another way to do this.

Here, I would like to have a closer look on what Vavr offers when it comes to correctness of data.

Vavr offering

With the library, you get a validation mechanism based on the io.vavr.control.Validation interface. It has two implementations: Valid and Invalid, representing - as their names suggest - valid data and an erroneous state respectively. Both provide a value - the former contains valid data, while the latter an error representation. Values can be instances of any classes.

The validation mechanism is based on applicative functor idea. In short, it combines results of all checks so, in both cases (whether data is correct or there some errors), all of them will be merged into a single value. For a good explanation about the mechanics of applicative functor, check this post.

How to validate data

To validate a given piece of data, you need to call Validation.combine(...) that accepts up to 8 validation results. Every validation can be computed in a separate method.

All of them are combined into a single result with help of a Validation.Builder class and its ap(fn) method. The output of it is an instance of Valid or Invalid class. The exact type depends on validation results.

If all of them are successful, the ap(fn) method maps all results to a single value using a function it takes as an argument. The value is hold by the Valid result. There are some requirements to the function. It must expect the same number of parameters as the number of validation results passed to combine(...) method. Next, types of parameters must be the same as values contained by validation results. How are they matched? The order of arguments passed to the function corresponds to the order of validation results.

If at least one of validation results points to invalid data, then ap(fn) methods returns Invalid instance, containing a list of all errors that occurred. Similar to a valid result, we can map all errors to some other class by calling a Validation.mapError(fn) method. The function provided as parameter is responsible for mapping the list to something else.

Show me the code

So far we covered some general assumptions of Vavr’s Validation. Now, let’s look at some example.

Suppose we have a REST endpoint receiving some data in request’s body. Let the endpoint be a service registering a new visit for a given guest in a theme park. The expected data are an identifier of a guest, an identifier of a card assigned to this guest and the type of ticket.

How to validate the data? It could look like the following:

Validation<String, ValidRegistrationRequest> validate(RegistrationRequest request) {
   return combine(
       validateCardId(request.getCardId()),
       validateTicketType(request.getTicketType()),
       validateGuestId(request.getGuestId())
)
       .ap(ValidRegistrationRequest::new)
       .mapError(this::errorsAsJson);
}

Let’s start with the combine(...) method, statically imported for better readability. It takes three arguments, which are results of calls to three validation methods. Each method takes a single parameter from the request, does some computation and returns an instance of Valid or Invalid class (both implementing Validation interface). Here are their signatures:

private Validation<String, Card> validateCardId(String cardId) {
    // validate cardId
    // if correct then return an instance of entity the cardId corresponds to
}

private Validation<String, TicketType> validateTicketType(String ticketType) {
    // validate ticketType
    // if known then return enumeration representing the ticket
}

private Validation<String, Guest> validateGuest(String guestId) {
    // validate guestId
    // if correct then return an instance of entity the questId corresponds to
}

Each method contains validation logic responsible to validate a given parameter only. For example the logic in validateCardId(...) could check whether is it null, is too long or too short, does it exist in a system, is it assigned to some other guest already and so on. If every criterion is fulfilled, then a Valid instance is returned with Card entity provided as its value. In case of a failed check, on the output we receive an Invalid instance containing a message informing about a validation error. The same applies to the remaining two validation methods.

So we have particular results computed. What is next? In case when all of them are valid, an instance of ValidRegistrationRequest class is created (through the reference to its constructor provided to ap(fn) method). The instance contains entities found in repository instead of raw request’s values. This is beneficial for us since we do not have to query for entities for the second time.

As I mentioned above, for a validation failure an instance of Invalid is returned and it contains a list of all errors. In our example, this is a list of strings, since all validation methods return a String value in case of an error. Depending on requirements, we could return such result or map the list to something else. In presented code we map errors to some JSON representation.

Consume validation result

What can we do with such final result of a validation process? There are a couple of possibilities. The first one could be extracting a value using if-else statement and Validation.get()/getError() methods. Next, we could change the result to io.vavr.control.Either and consume it from there. There is Validation.fold(invalidFn, validFn) method allowing to have a one-liner to consume both, invalid and valid results. And the list of possibilities does not stop here. There are many ways we could consume validation results. Which one you will choose depends on your specific case.

This post presents a validation mechanism provided by Vavr. I hope the reading will spark your interest in the library. It is definitely worth checking and I really recommend using it in your projects.