javaslang.control.Validation Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of javaslang Show documentation
Show all versions of javaslang Show documentation
Javaslang is a Java standard library extension built for Java 8 and above.
/* / \____ _ _ ____ ______ / \ ____ __ _______
* / / \/ \ / \/ \ / /\__\/ // \/ \ // /\__\ 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)}
* @throws NullPointerException if value is null
*/
static Validation valid(T value) {
Objects.requireNonNull(value, "value is null");
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 v6;
private Validation