
javaslang.control.Validation Maven / Gradle / Ivy
/* / \____ _ _ ____ ______ / \ ____ __ _______
* / / \/ \ / \/ \ / /\__\/ // \/ \ // /\__\ JΛVΛSLΛNG
* _/ / /\ \ \/ / /\ \\__\\ \ // /\ \ /\\/ \ /__\ \ Copyright 2014-2016 Javaslang, http://javaslang.io
* /___/\_/ \_/\____/\_/ \_/\__\/__/\__\_/ \_// \__/\_____/ Licensed under the Apache License, Version 2.0
*/
package javaslang.control;
import javaslang.*;
import javaslang.collection.Iterator;
import javaslang.collection.List;
import javaslang.collection.Seq;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* An implementation similar to scalaz's Validation control.
*
*
* The Validation type is different from a Monad type, it is an applicative
* functor. Whereas a Monad will short circuit after the first error, the
* applicative functor will continue on, accumulating ALL errors. This is
* especially helpful in cases such as validation, where you want to know
* all the validation errors that have occurred, not just the first one.
*
*
*
*
* Validation construction:
*
* Valid:
* Validation<String,Integer> valid = Validation.valid(5);
*
* Invalid:
* Validation<List<String>,Integer> invalid = Validation.invalid(List.of("error1","error2"));
*
* Validation combination:
*
* Validation<String,String> valid1 = Validation.valid("John");
* Validation<String,Integer> valid2 = Validation.valid(5);
* Validation<String,Option<String>> valid3 = Validation.valid(Option.of("123 Fake St."));
* Function3<String,Integer,Option<String>,Person> f = ...;
*
* Validation<List<String>,String> result = valid1.combine(valid2).ap((name,age) -> "Name: "+name+" Age: "+age);
* Validation<List<String>,Person> result2 = valid1.combine(valid2).combine(valid3).ap(f);
*
*
*
* @param value type in the case of invalid
* @param value type in the case of valid
* @author Eric Nelson
* @see Validation
* @since 2.0.0
*/
public interface Validation extends Value {
/**
* Creates a {@link Valid} that contains the given {@code value}.
*
* @param type of the error
* @param type of the given {@code value}
* @param value A value
* @return {@code Valid(value)}
*/
static Validation valid(T value) {
return new Valid<>(value);
}
/**
* Creates an {@link Invalid} that contains the given {@code error}.
*
* @param type of the given {@code error}
* @param type of the value
* @param error An error
* @return {@code Invalid(error)}
* @throws NullPointerException if error is null
*/
static Validation invalid(E error) {
Objects.requireNonNull(error, "error is null");
return new Invalid<>(error);
}
/**
* Creates a {@code Validation} of an {@code Either}.
*
* @param either An {@code Either}
* @param type of the given {@code error}
* @param type of the value
* @return A {@code Valid(either.get())} if either is a Right, otherwise {@code Invalid(either.getLeft())}.
* @throws NullPointerException if either is null
*/
static Validation fromEither(Either either) {
Objects.requireNonNull(either, "either is null");
return either.isRight() ? valid(either.get()) : invalid(either.getLeft());
}
/**
* Reduces many {@code Validation} instances into a single {@code Validation} by transforming an
* {@code Iterable>} into a {@code Validation>}.
*
* @param value type in the case of invalid
* @param value type in the case of valid
* @param values An iterable of Validation instances.
* @return A valid Validation of a sequence of values if all Validation instances are valid
* or an invalid Validation containing an accumulated List of errors.
* @throws NullPointerException if values is null
*/
static Validation, Seq> sequence(Iterable extends Validation, T>> values) {
Objects.requireNonNull(values, "values is null");
List errors = List.empty();
List list = List.empty();
for (Validation, T> value : values) {
if (value.isInvalid()) {
errors = errors.prependAll(value.getError().reverse());
} else if (errors.isEmpty()) {
list = list.prepend(value.get());
}
}
return errors.isEmpty() ? valid(list.reverse()) : invalid(errors.reverse());
}
/**
* Narrows a widened {@code Validation extends E, ? extends T>} to {@code Validation}
* by performing a type safe-cast. This is eligible because immutable/read-only
* collections are covariant.
*
* @param validation A {@code Validation}.
* @param type of error
* @param type of valid value
* @return the given {@code validation} instance as narrowed type {@code Validation}.
*/
@SuppressWarnings("unchecked")
static Validation narrow(Validation extends E, ? extends T> validation) {
return (Validation) validation;
}
/**
* Combines two {@code Validation}s into a {@link Builder}.
*
* @param type of error
* @param type of first valid value
* @param type of second valid value
* @param validation1 first validation
* @param validation2 second validation
* @return an instance of Builder<E,T1,T2>
* @throws NullPointerException if validation1 or validation2 is null
*/
static Builder combine(Validation validation1, Validation validation2) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
return new Builder<>(validation1, validation2);
}
/**
* Combines three {@code Validation}s into a {@link Builder3}.
*
* @param type of error
* @param type of first valid value
* @param type of second valid value
* @param type of third valid value
* @param validation1 first validation
* @param validation2 second validation
* @param validation3 third validation
* @return an instance of Builder3<E,T1,T2,T3>
* @throws NullPointerException if validation1, validation2 or validation3 is null
*/
static Builder3 combine(Validation validation1, Validation validation2, Validation validation3) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
Objects.requireNonNull(validation3, "validation3 is null");
return new Builder3<>(validation1, validation2, validation3);
}
/**
* Combines four {@code Validation}s into a {@link Builder4}.
*
* @param type of error
* @param type of first valid value
* @param type of second valid value
* @param type of third valid value
* @param type of fourth valid value
* @param validation1 first validation
* @param validation2 second validation
* @param validation3 third validation
* @param validation4 fourth validation
* @return an instance of Builder3<E,T1,T2,T3,T4>
* @throws NullPointerException if validation1, validation2, validation3 or validation4 is null
*/
static Builder4 combine(Validation validation1, Validation validation2, Validation validation3, Validation validation4) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
Objects.requireNonNull(validation3, "validation3 is null");
Objects.requireNonNull(validation4, "validation4 is null");
return new Builder4<>(validation1, validation2, validation3, validation4);
}
/**
* Combines five {@code Validation}s into a {@link Builder5}.
*
* @param type of error
* @param type of first valid value
* @param type of second valid value
* @param type of third valid value
* @param type of fourth valid value
* @param type of fifth valid value
* @param validation1 first validation
* @param validation2 second validation
* @param validation3 third validation
* @param validation4 fourth validation
* @param validation5 fifth validation
* @return an instance of Builder3<E,T1,T2,T3,T4,T5>
* @throws NullPointerException if validation1, validation2, validation3, validation4 or validation5 is null
*/
static Builder5 combine(Validation validation1, Validation validation2, Validation validation3, Validation validation4, Validation validation5) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
Objects.requireNonNull(validation3, "validation3 is null");
Objects.requireNonNull(validation4, "validation4 is null");
Objects.requireNonNull(validation5, "validation5 is null");
return new Builder5<>(validation1, validation2, validation3, validation4, validation5);
}
/**
* Combines six {@code Validation}s into a {@link Builder6}.
*
* @param type of error
* @param type of first valid value
* @param type of second valid value
* @param type of third valid value
* @param type of fourth valid value
* @param type of fifth valid value
* @param type of sixth valid value
* @param validation1 first validation
* @param validation2 second validation
* @param validation3 third validation
* @param validation4 fourth validation
* @param validation5 fifth validation
* @param validation6 sixth validation
* @return an instance of Builder3<E,T1,T2,T3,T4,T5,T6>
* @throws NullPointerException if validation1, validation2, validation3, validation4, validation5 or validation6 is null
*/
static Builder6 combine(Validation validation1, Validation validation2, Validation validation3, Validation validation4, Validation validation5, Validation validation6) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
Objects.requireNonNull(validation3, "validation3 is null");
Objects.requireNonNull(validation4, "validation4 is null");
Objects.requireNonNull(validation5, "validation5 is null");
Objects.requireNonNull(validation6, "validation6 is null");
return new Builder6<>(validation1, validation2, validation3, validation4, validation5, validation6);
}
/**
* Combines seven {@code Validation}s into a {@link Builder7}.
*
* @param type of error
* @param type of first valid value
* @param type of second valid value
* @param type of third valid value
* @param type of fourth valid value
* @param type of fifth valid value
* @param type of sixth valid value
* @param type of seventh valid value
* @param validation1 first validation
* @param validation2 second validation
* @param validation3 third validation
* @param validation4 fourth validation
* @param validation5 fifth validation
* @param validation6 sixth validation
* @param validation7 seventh validation
* @return an instance of Builder3<E,T1,T2,T3,T4,T5,T6,T7>
* @throws NullPointerException if validation1, validation2, validation3, validation4, validation5, validation6 or validation7 is null
*/
static Builder7 combine(Validation validation1, Validation validation2, Validation validation3, Validation validation4, Validation validation5, Validation validation6, Validation validation7) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
Objects.requireNonNull(validation3, "validation3 is null");
Objects.requireNonNull(validation4, "validation4 is null");
Objects.requireNonNull(validation5, "validation5 is null");
Objects.requireNonNull(validation6, "validation6 is null");
Objects.requireNonNull(validation7, "validation7 is null");
return new Builder7<>(validation1, validation2, validation3, validation4, validation5, validation6, validation7);
}
/**
* Combines eight {@code Validation}s into a {@link Builder8}.
*
* @param type of error
* @param type of first valid value
* @param type of second valid value
* @param type of third valid value
* @param type of fourth valid value
* @param type of fifth valid value
* @param type of sixth valid value
* @param type of seventh valid value
* @param type of eighth valid value
* @param validation1 first validation
* @param validation2 second validation
* @param validation3 third validation
* @param validation4 fourth validation
* @param validation5 fifth validation
* @param validation6 sixth validation
* @param validation7 seventh validation
* @param validation8 eigth validation
* @return an instance of Builder3<E,T1,T2,T3,T4,T5,T6,T7,T8>
* @throws NullPointerException if validation1, validation2, validation3, validation4, validation5, validation6, validation7 or validation8 is null
*/
static Builder8 combine(Validation validation1, Validation validation2, Validation validation3, Validation validation4, Validation validation5, Validation validation6, Validation validation7, Validation validation8) {
Objects.requireNonNull(validation1, "validation1 is null");
Objects.requireNonNull(validation2, "validation2 is null");
Objects.requireNonNull(validation3, "validation3 is null");
Objects.requireNonNull(validation4, "validation4 is null");
Objects.requireNonNull(validation5, "validation5 is null");
Objects.requireNonNull(validation6, "validation6 is null");
Objects.requireNonNull(validation7, "validation7 is null");
Objects.requireNonNull(validation8, "validation8 is null");
return new Builder8<>(validation1, validation2, validation3, validation4, validation5, validation6, validation7, validation8);
}
/**
* Check whether this is of type {@code Valid}
*
* @return true if is a Valid, false if is an Invalid
*/
boolean isValid();
/**
* Check whether this is of type {@code Invalid}
*
* @return true if is an Invalid, false if is a Valid
*/
boolean isInvalid();
/**
* Returns this {@code Validation} if it is valid, otherwise return the alternative.
*
* @param other An alternative {@code Validation}
* @return this {@code Validation} if it is valid, otherwise return the alternative.
*/
@SuppressWarnings("unchecked")
default Validation orElse(Validation extends E, ? extends T> other) {
Objects.requireNonNull(other, "other is null");
return isValid() ? this : (Validation) other;
}
/**
* Returns this {@code Validation} if it is valid, otherwise return the result of evaluating supplier.
*
* @param supplier An alternative {@code Validation} supplier
* @return this {@code Validation} if it is valid, otherwise return the result of evaluating supplier.
*/
@SuppressWarnings("unchecked")
default Validation orElse(Supplier> supplier) {
Objects.requireNonNull(supplier, "supplier is null");
return isValid() ? this : (Validation) supplier.get();
}
@Override
default boolean isEmpty() {
return isInvalid();
}
/**
* Gets the value of this Validation if is a Valid or throws if this is an Invalid
*
* @return The value of this Validation
* @throws NoSuchElementException if this is an Invalid
*/
@Override
T get();
/**
* Gets the error of this Validation if is an Invalid or throws if this is a Valid
*
* @return The error of this Invalid
* @throws RuntimeException if this is a Valid
*/
E getError();
/**
* Returns this as {@code Either}.
*
* @return {@code Either.right(get())} if this is valid, otherwise {@code Either.left(getError())}.
*/
default Either toEither() {
return isValid() ? Either.right(get()) : Either.left(getError());
}
@Override
boolean equals(Object o);
@Override
int hashCode();
@Override
String toString();
/**
* Performs the given action for the value contained in {@code Valid}, or do nothing
* if this is an Invalid.
*
* @param action the action to be performed on the contained value
* @throws NullPointerException if action is null
*/
@Override
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action, "action is null");
if (isValid()) {
action.accept(get());
}
}
/**
* Performs the action in {@code fInvalid} on {@code error} if this is an {@code Invalid},
* or {@code fValid} on {@code value} if this is a {@code Valid}.
* Returns an object of type U.
*
*
*
* For example:
* Validation<List<String>,String> valid = ...;
* Integer i = valid.fold(List::length, String::length);
*
*
*
* @param the fold result type
* @param fInvalid the invalid fold operation
* @param fValid the valid fold operation
* @return an instance of type U
* @throws NullPointerException if fInvalid or fValid is null
*/
default U fold(Function super E, ? extends U> fInvalid, Function super T, ? extends U> fValid) {
Objects.requireNonNull(fInvalid, "function fInvalid null");
Objects.requireNonNull(fValid, "function fValid null");
if (isInvalid()) {
E error = this.getError();
return fInvalid.apply(error);
} else {
T value = this.get();
return fValid.apply(value);
}
}
/**
* Flip the valid/invalid values for this Validation. If this is a Valid<E,T>, returns Invalid<T,E>.
* Or if this is an Invalid<E,T>, return a Valid<T,E>.
*
* @return a flipped instance of Validation
*/
default Validation swap() {
if (isInvalid()) {
E error = this.getError();
return Validation.valid(error);
} else {
T value = this.get();
return Validation.invalid(value);
}
}
@Override
default Validation map(Function super T, ? extends U> f) {
Objects.requireNonNull(f, "function f is null");
if (isInvalid()) {
return Validation.invalid(this.getError());
} else {
T value = this.get();
return Validation.valid(f.apply(value));
}
}
/**
* Whereas map only performs a mapping on a valid Validation, and leftMap performs a mapping on an invalid
* Validation, bimap allows you to provide mapping actions for both, and will give you the result based
* on what type of Validation this is. Without this, you would have to do something like:
*
* validation.map(...).leftMap(...);
*
* @param type of the mapping result if this is an invalid
* @param type of the mapping result if this is a valid
* @param errorMapper the invalid mapping operation
* @param valueMapper the valid mapping operation
* @return an instance of Validation<U,R>
* @throws NullPointerException if invalidMapper or validMapper is null
*/
default Validation bimap(Function super E, ? extends E2> errorMapper, Function super T, ? extends T2> valueMapper) {
Objects.requireNonNull(errorMapper, "errorMapper is null");
Objects.requireNonNull(valueMapper, "valueMapper is null");
if (isInvalid()) {
E error = this.getError();
return Validation.invalid(errorMapper.apply(error));
} else {
T value = this.get();
return Validation.valid(valueMapper.apply(value));
}
}
/**
* Applies a function f to the error of this Validation if this is an Invalid. Otherwise does nothing
* if this is a Valid.
*
* @param type of the error resulting from the mapping
* @param f a function that maps the error in this Invalid
* @return an instance of Validation<U,T>
* @throws NullPointerException if mapping operation f is null
*/
default Validation leftMap(Function super E, ? extends U> f) {
Objects.requireNonNull(f, "function f is null");
if (isInvalid()) {
E error = this.getError();
return Validation.invalid(f.apply(error));
} else {
return Validation.valid(this.get());
}
}
default Validation, U> ap(Validation, ? extends Function super T, ? extends U>> validation) {
Objects.requireNonNull(validation, "validation is null");
if (isValid()) {
if (validation.isValid()) {
Function super T, ? extends U> f = validation.get();
U u = f.apply(this.get());
return valid(u);
} else {
List errors = validation.getError();
return invalid(errors);
}
} else {
if (validation.isValid()) {
E error = this.getError();
return invalid(List.of(error));
} else {
List errors = validation.getError();
E error = this.getError();
return invalid(errors.append(error));
}
}
}
/**
* Combines two {@code Validation}s to form a {@link Builder}, which can then be used to perform further
* combines, or apply a function to it in order to transform the {@link Builder} into a {@code Validation}.
*
* @param type of the value contained in validation
* @param validation the validation object to combine this with
* @return an instance of Builder
*/
default Builder combine(Validation validation) {
return new Builder<>(this, validation);
}
// -- Implementation of Value
default Option> filter(Predicate super T> predicate) {
Objects.requireNonNull(predicate, "predicate is null");
return isInvalid() || predicate.test(get()) ? Option.some(this) : Option.none();
}
@SuppressWarnings("unchecked")
default Validation flatMap(Function super T, ? extends Validation> mapper) {
Objects.requireNonNull(mapper, "mapper is null");
return isInvalid() ? (Validation) this : (Validation) mapper.apply(get());
}
@Override
default Validation peek(Consumer super T> action) {
if (isValid()) {
action.accept(get());
}
return this;
}
@Override
default boolean isSingleValued() {
return true;
}
@Override
default Iterator iterator() {
return isValid() ? Iterator.of(get()) : Iterator.empty();
}
/**
* A valid Validation
*
* @param type of the error of this Validation
* @param type of the value of this Validation
*/
final class Valid implements Validation {
private final T value;
/**
* Construct a {@code Valid}
*
* @param value The value of this success
*/
private Valid(T value) {
this.value = value;
}
@Override
public boolean isValid() {
return true;
}
@Override
public boolean isInvalid() {
return false;
}
@Override
public T get() {
return value;
}
@Override
public E getError() throws RuntimeException {
throw new NoSuchElementException("error of 'valid' Validation");
}
@Override
public boolean equals(Object obj) {
return (obj == this) || (obj instanceof Valid && Objects.equals(value, ((Valid, ?>) obj).value));
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
@Override
public String stringPrefix() {
return "Valid";
}
@Override
public String toString() {
return stringPrefix() + "(" + value + ")";
}
}
/**
* An invalid Validation
*
* @param type of the error of this Validation
* @param type of the value of this Validation
*/
final class Invalid implements Validation {
private final E error;
/**
* Construct an {@code Invalid}
*
* @param error The value of this error
*/
private Invalid(E error) {
this.error = error;
}
@Override
public boolean isValid() {
return false;
}
@Override
public boolean isInvalid() {
return true;
}
@Override
public T get() throws RuntimeException {
throw new NoSuchElementException("get of 'invalid' Validation");
}
@Override
public E getError() {
return error;
}
@Override
public boolean equals(Object obj) {
return (obj == this) || (obj instanceof Invalid && Objects.equals(error, ((Invalid, ?>) obj).error));
}
@Override
public int hashCode() {
return Objects.hashCode(error);
}
@Override
public String stringPrefix() {
return "Invalid";
}
@Override
public String toString() {
return stringPrefix() + "(" + error + ")";
}
}
final class Builder {
private Validation v1;
private Validation v2;
private Builder(Validation v1, Validation v2) {
this.v1 = v1;
this.v2 = v2;
}
public Validation, R> ap(Function2 f) {
return v2.ap(v1.ap(Validation.valid(f.curried())));
}
public Builder3 combine(Validation v3) {
return new Builder3<>(v1, v2, v3);
}
}
final class Builder3 {
private Validation v1;
private Validation v2;
private Validation v3;
private Builder3(Validation v1, Validation v2, Validation v3) {
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
}
public Validation, R> ap(Function3 f) {
return v3.ap(v2.ap(v1.ap(Validation.valid(f.curried()))));
}
public Builder4 combine(Validation v4) {
return new Builder4<>(v1, v2, v3, v4);
}
}
final class Builder4 {
private Validation v1;
private Validation v2;
private Validation v3;
private Validation v4;
private Builder4(Validation v1, Validation v2, Validation v3, Validation v4) {
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.v4 = v4;
}
public Validation, R> ap(Function4 f) {
return v4.ap(v3.ap(v2.ap(v1.ap(Validation.valid(f.curried())))));
}
public Builder5 combine(Validation v5) {
return new Builder5<>(v1, v2, v3, v4, v5);
}
}
final class Builder5 {
private Validation v1;
private Validation v2;
private Validation v3;
private Validation v4;
private Validation v5;
private Builder5(Validation v1, Validation v2, Validation v3, Validation v4, Validation v5) {
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.v4 = v4;
this.v5 = v5;
}
public Validation, R> ap(Function5 f) {
return v5.ap(v4.ap(v3.ap(v2.ap(v1.ap(Validation.valid(f.curried()))))));
}
public Builder6 combine(Validation v6) {
return new Builder6<>(v1, v2, v3, v4, v5, v6);
}
}
final class Builder6 {
private Validation v1;
private Validation v2;
private Validation v3;
private Validation v4;
private Validation v5;
private Validation v6;
private Builder6(Validation v1, Validation v2, Validation v3, Validation v4, Validation v5, Validation v6) {
this.v1 = v1;
this.v2 = v2;
this.v3 = v3;
this.v4 = v4;
this.v5 = v5;
this.v6 = v6;
}
public Validation, R> ap(Function6 f) {
return v6.ap(v5.ap(v4.ap(v3.ap(v2.ap(v1.ap(Validation.valid(f.curried())))))));
}
public Builder7 combine(Validation v7) {
return new Builder7<>(v1, v2, v3, v4, v5, v6, v7);
}
}
final class Builder7 {
private Validation v1;
private Validation v2;
private Validation v3;
private Validation v4;
private Validation v5;
private Validation