Skip to main content
Version: 2.x

ZValidation

Like many other data types in ZIO, the Validation data type we learned about before is actually a type alias for a more general data type called ZValidation.

A ZValidation[W, E, A] is either a Success with a value of type A or a Failure with one or more errors of type E, and in either case contains a log of values of type W. A Validation[E, A] is just a type alias for ZValidation[Nothing, E, A] and since the log type was Nothing we were able to ignore it.

type Validation[+E, +A] = ZValidation[Nothing, E, A]

This type W can be used for two main purposes.

First, it can be used to maintain a "log" of the things we did in validating some data. In some cases it can be important for auditing or debugging purposes to know not just whether data is valid or why not but the steps we took in validating that data.

Second, it can be used to represent a concept of "warnings".

By definition a Validation is either a success with a value or a failure with one or more errors. So if an error occurred the data is invalid and there is no value at all.

However, we can imagine some cases where data might be questionable or noteworthy in some way without being invalid.

For example, we might say that a valid Person must have an age that is not negative, so a Person with an age of 10 would still be valid data. But depending on the application this data might still be unusual, for example if this Person represents a credit card customer.

In cases like this it would be useful to have some way of capturing these warnings as data and propagating them through our validation logic so that we can see all the warnings that occurred and take some further action based on them. This could include logging them, triggering a user to confirm that their input is correct, or triggering other processes based on the content of these warnings.

To model this we can represent a ZValidation as follows:

import zio.{Chunk, NonEmptyChunk}

trait ZValidation[+W, +E, +A]

object ZValidation {
case class Success[W, A](log: Chunk[W], value: A) extends ZValidation[W, Nothing, A]
case class Failure[W, E](log: Chunk[W], errors: NonEmptyChunk[E]) extends ZValidation[W, E, Nothing]
}

Now both validation successes and failures have a log of values of type W so we can keep track of values in the log regardless of whether the validation is a success or a failure.

Since a Validation is a ZValidation all of the operators we learned about before for Validation still work. So we will focus here on operators that are specific to working with the W type.

Writing Values To The Log

To write values to the log we can just use the log operator on the ZValidation trait.

trait ZValidation[+W, +E, +A] {
def log[W1 >: W](w: W1): ZValidation[W1, E, A]
}

This will just append the specified value to the log without changing the success or failure value of the ZValidation.

For example, we could add a warning that the Person is a child like this:

import zio.prelude.ZValidation

def validateAge(age: Int): ZValidation[String, String, Int] =
if (age < 0) ZValidation.fail(s"Age $age was less than zero")
else if (age < 18) ZValidation.succeed(age).log(s"Age $age is less than 18")
else ZValidation.succeed(age)

Notice that we used exactly the same constructors as when we were working with Validation except we now use the log operator to append to the log.

Transforming The Log

We can transform the value of the log entries with the mapLog operator.

trait ZValidation[+W, +E, +A] {
def mapLog[W2](f: W => W2): ZValidation[W2, E, A]
}

This can be useful to transform the log values into some common data model, for example mapping String logs into some more structured data format.

Reading Values From The Log

We can get values from the log by using the getLog operator, which just returns the current contents of the log.

import zio.Chunk

trait ZValidation[+W, +E, +A] {
def getLog: Chunk[W]
}

Since a ZValidation is just a value we can use a combination of the getLog operator and fold to extract the log and the errors or success value.