Validations & Validation steps

Validations & Failures

A Validation consists of a method which validates the field(s) of the Modddel, and returns a failure if it's/they're invalid. For the return type, we use the functional programming Option type. You can learn more about it here.

Practically speaking, Option can assume two values:

  1. Some, a type that contains the value of the Option when it is present

  2. None, a type that tells us that the Option does not contain any value

Example : A validation called "length" for a ValueObject called "Name".

@override
Option<NameLengthFailure> validateLength(name) {
    /// TODO : Implement validateLength
    return none();
}

A Failure represents a validation that failed for a specific reason, making the modddel invalid. A valid modddel has no failures, while an invalid Modddel has at least one failure.

There are two kinds of failures : ValueFailure for ValueObjects, and EntityFailure for Entities.

Typically, you'll want to use a freezed sealed class to make a Failure, where the union-cases are the different reasons for the failure. Each union-case can contain fields that further describe the failure.

Example (continued) : We create the ValueFailure for the validation using freezed.

@freezed
class NameLengthFailure extends ValueFailure with _$NameLengthFailure {
  const factory NameLengthFailure.empty() = _Empty;
  const factory NameLengthFailure.tooShort({required int minLength}) =
      _TooShort;
}

Then we implement the method : The validation should fail if the name is empty, or if it's shorter than minLength.

static const minLength = 5;

@override
Option<NameLengthFailure> validateLength(name) {
    if(name.value.isEmpty){
        return some(const NameLengthFailure.empty());
    }
    if(name.value.length < minLength){
        return some(const NameLengthFailure.tooShort(minLength: minLength));
    }
    return none();
}

ValidationSteps

A Modddel is not validated in one go, rather it's validated in one or multiple steps called validationSteps.

A ValidationStep is a group of validations (≥1) that are processed during the same step. For each validationStep, a matching invalid union-case is generated. These are called "invalid-step union-cases".

When a Modddel is created, the validationSteps are processed sequentially :

We start with the first validationStep and process its validations. If at least one validation fails, the whole Modddel is invalid and becomes an instance of the invalid-step union-case matching the failed validationStep. If all validations pass, we move to the next validationStep. If all validationSteps pass, the Modddel is valid and becomes an instance of the "Valid" union-case.

While the order of the validationSteps matters, the order of the validations doesn't.

Example 1

Let's suppose we have an Age ValueObject that holds an integer field age. This ValueObject has one validationStep called 'Value', that contains one validation called 'legal' which checks that the age is >= 18.

When creating this Age ValueObject, the validations are made in this order :

  1. The 'Value' validationStep : The 'legal' validation is processed : If the age is < 18, it returns a ValueFailure, and the ValueObject becomes an instance of the InvalidAgeValue union-case, and holds that ValueFailure.

  2. → All Validation steps passed : This ValueObject is valid, and becomes an instance of the ValidAge union-case.

Example 2

Let's suppose we have a Username ValueObject that holds a string field username. This ValueObject has two validationSteps :

  1. The first validationStep is called 'form', and contains two validations :

    • A 'length' validation, that validates the length of the username string. (Should be between 5 and 20)

    • A 'characters' validation, that validates the characters of the username string. (Should be alpha-numeric)

  2. The second validationStep is called 'availability', and contains one validation :

    • A 'reserved' validation, that checks whether the username is reserved and should not be used (Ex : 'admin', 'moderator'...), or whether the username is taken by another user.

When creating this Username ValueObject, the validations are made in this order :

  1. The 'Form' validationStep : The 'length' and 'characters' validations are processed. If at least one of these validations fails (i.e returns a ValueFailure), then this ValueObject becomes an instance of the InvalidUsernameForm union-case, and holds the valueFailure(s). If both validations are passed successfully, we move to the next step.

  2. The 'Availability' validationStep : The 'reserved' validation is processed. If it fails, then this ValueObject becomes an instance of the InvalidUsernameAvailability union-case, and holds the valueFailure.

  3. → All Validation steps passed : This ValueObject is valid, and becomes an instance of the ValidUsername union-case.