"Helios was the personification of the Sun in Greek mythology. He is the son of the Titan Hyperion and the Titaness Theia (according to Hesiod), also known as Euryphaessa (in Homeric Hymn 31) and brother of the goddesses Selene, the moon, and Eos, the dawn" — https://en.wikipedia.org/wiki/Helios

1. Intro

Helios is a micro validation library that focuses on:

  • API simplicity

  • Small footprint

  • Validator reusability

  • Embrace functional programming

  • Documentation

Many Java validation libraries out there are based on annotations. Annotations are ok, but then validation depends on having your data model polluted with them. Moreover, sometimes you may want to have different validation restrictions on the same type of object in different parts of the application.

2. Usage

2.1. Dependency

If you’re using Gradle:

repositories {
    maven {
        url  "http://dl.bintray.com/mariogarcia/maven"
    }
}

compile 'com.github.mariogarcia:helios:0.1.0'
Replace 0.1.0 with the latest available version, which is 0.1.2

2.2. Simple property

When validating anything with Helios you would be using the helios.Helios#validate method. The validate method is overloaded to be able to receive validators as a variable arguments or as a list, we’ll see later on when to use one or another. The validate method parameters are:

  • A string identifying the property validated

  • The value to validate

  • A variable number of validators

The following example shows how to validate a simple property isolated.

package helios.samples.asciidoc;

import java.util.List;

import helios.Helios;
import helios.Validators;
import helios.ValidatorError;

public class SimpleProperty {

    public List<ValidatorError> validateName(String name) {
        return Helios.validate("name",                            (1)
                               name,                              (2)
                               Validators.required(),             (3)
                               Validators.inRangeOfString(0,4));
    }
}
1 Helios.validate call. Also the first parameter is the identifier of the validated property.
2 The value to validate.
3 Varargs of built-in validators.

If you pass an invalid number to the validateName method the validation will return a list of helios.ValidatorError. Every ValidatorError instance is immutable, so you can access to its property, key and keyI18n values directly, no getters needed.

In the example we’re using some built-int Helios validators. Helios has a little set of built-in validators you can find in the class helios.ValidatorsUtil class.

2.3. Grouping

One way of reusing validators is to group them in a list and use the helios.Helios.validate method that permits a list of validators:

package helios.samples.asciidoc;

import static helios.Helios.validate;
import static helios.Validators.matches;
import static helios.Validators.required;
import static helios.Validators.inRangeOfString;

import java.util.List;
import java.util.Arrays;

import helios.Validator;
import helios.ValidatorError;

public class Grouping {

    private List<Validator<String>> VALIDATORS =
        Arrays.asList(
            required(),
            inRangeOfString(0,4),
            matches("j.*"));

    public List<ValidatorError> validateName(String name) {
        return validate("name", name, VALIDATORS);
    }

    public List<ValidatorError> validateOtherName(String other) {
        return validate("other", other, VALIDATORS);
    }
}

2.4. Nested

In a real application we’ll be probably be validating a full object, not only single properties, this can be addressed in Helios by nesting calls to Helios#validate inside lambda expressions.

    public List<ValidatorError> validateProduct(final Product product) {
        return validate("product", product,
            (Product p) -> validate("desc", p.desc, getDescValidators()),         (1)
            (Product p) -> validate("items", p.items, getItemsValidators()),      (2)
            (Product p) -> validate("amount", p.amount, getAmountValidators()));  (3)
    }
1 Validator to check "desc" property
2 Validator to check "items" property
3 Validator to check "amount" property

These validators are just lambda expressions that comply with the helios.Validator interface. They receive a given value of the required type and thanks to invoking validate they return a list of ValidatorError which is the expected outcome for a Validator.

The validateProduct method itself could be considered as a validator of type Validator<Product> because it receives a Product and returns a list of ValidatorError.

3. Validators

All validators are based on helios.Validator interface. This interface is a functional interface and has one single method validate that receives a given value and may return a list of zero or more errors.

Because Helios try to keep its size to the minimum, there’re only a set of basic built-in validators coming with the library. However there’re plenty of other ways of creating your own validators.

3.1. Built-in

Although you can create you own validators, Helios comes with a basic set of validators for numbers, strings, and lists. Please checkout the javadoc to see them all.

3.2. Create your own

3.2.1. Validator

Of course you can implement helios.Validator to create a new validator type.

package helios.samples.asciidoc.validators;

import static helios.ValidatorError.error; (1)
import static helios.ValidatorError.errors;

import java.util.List;

import helios.Validator;
import helios.ValidatorError;

public class StartsWithJ implements Validator<String> {  (2)

    @Override
    public List<ValidatorError> validate(String word) {
        return word.startsWith("j") ?
            errors() :
            errors(error(word, "starts.with.j"));        (3)
    }
}
1 Static methods will serve to produce ValidatorError instances easily
2 Implementing helios.Validator
3 Creating errors if its needed with a given error key and the value that produced the error

3.2.2. Lambda

Because helios.Validator is a functional interface you can create new instances out of lambda expressions.

package helios.samples.asciidoc.validators;

import static helios.ValidatorError.error;
import static helios.ValidatorError.errors;
import static helios.Validators.required;

import helios.Validator;
import helios.Helios;

public class LambdaValidator {

    public Validator<String> getCustomValidator() { (1)
        return (String word) -> {
            return word.startsWith("j") ?
                errors() :
                errors(error(word, "starts.with.j"));
        };
    }

    public Validator<String> getRequiredStringValidator() { (2)
        return (String word) -> Helios.validate("word", word, required());
    }
}
1 From a given boolean expression we could make use of the ValidatorError errors and error methods to return a list of ValidatorError instances
2 You can also use Helios#validate if you would like to reuse pre-existent built-in validators or you may want to nest validators

3.2.3. Method Reference

If you had a method that complies with the helios.Validator contract, you could use it in any method where a Validator is passed as parameter.

package helios.samples.asciidoc.validators;

import static helios.Validators.required;
import static helios.Validators.inRangeOfString;

import static helios.ValidatorError.error;
import static helios.ValidatorError.errors;

import java.util.List;

import helios.Helios;
import helios.Validator;
import helios.ValidatorError;

public class MethodReference {

    public List<ValidatorError> isSmallWord(final String word) {
        return Helios.validate("word", word, this::stringValidator);
    }

    public List<ValidatorError> stringValidator(String word) {
        return word != null && word.length() > 0 ?
            errors() :
            errors(error(word, "string.required"));
    }
}

3.2.4. Compose

Apart from grouping validators in lists, you can also create a validator out of any pre-existent validators with the method helios.ValidatorsUtil.compose. In fact this method is already used in Helios built-in validators:

    public static Validator<String> inRangeOfString(int min, int max) {
        return compose(minOfString(min), maxOfString(max));
    }

3.2.5. As Predicate

At some point you may realize a validator or a set of validators could be used as a java.util.function.Predicate. A good example could be to filter out a list of words:

package helios.samples.asciidoc.validators;

import static helios.Validators.matches;
import static helios.Validators.required;

import static helios.ValidatorsUtil.compose;

import java.util.List;
import java.util.Arrays;

import helios.Validator;

public class AsPredicate {

    private final List<String> WORDS = Arrays.asList( (1)
        "something",
        "aloha",
        "somewhere",
        "some");

    public Long countWordsStartingWithS() {
        Validator<String> byCustom = compose(required(), matches("[Ss].*"));

        return countWords(WORDS, byCustom); (2)
    }

    public Long countWords(final List<String> wordList, final Validator<String> filter) {
        return wordList
            .stream()
            .filter(filter.toPredicate())  (3)
            .count();
    }
}
1 A list of words we want to filter
2 Invoking a function receiving a Validator as parameter
3 Invoking Validator#toPredicate to convert a Validator to Predicate and use it to filter out a stream of words

4. ValidatorError

If any validator fails it will return a list of helios.ValidatorError instances. When a validator is created it could have the following information:

  • property: the validated property

  • key: the error identificator

  • keyI18n: it’s a combination of "$property.$key". It contains a unique identifier that can be used to translate the error.

You can create a new ValidatorError either by calling its constructor directly or using the method ValidatorError#error. Although this method was first created to be used internally, it was pretty clear that it could be very useful to developers when creating custom validators.

All instances of ValidatorError are efectively immutable. All its fields are final and initialized in constructor. It could be considered thread-safe friendly.

5. Development

5.1. Dependencies

Library Description Scope

org.slf4j:slf4j-api:1.7.21

serves as a simple facade or abstraction for various logging frameworks

compile

com.nagternal:spock-genesis:0.4.0

Spock Genesis provides a variety of classes that produce generators

testCompile

5.2. Github and Javadoc