All Downloads are FREE. Search and download functionalities are using the official Maven repository.

javaslang.control.Validation Maven / Gradle / Ivy

There is a newer version: 2.1.0-alpha
Show newest version
/*     / \____  _    _  ____   ______  / \ ____  __    _______
 *    /  /    \/ \  / \/    \ /  /\__\/  //    \/  \  //  /\__\   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, 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} 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 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 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 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 fInvalid, Function 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 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 errorMapper, Function 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 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> validation) { Objects.requireNonNull(validation, "validation is null"); if (isValid()) { if (validation.isValid()) { Function 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 predicate) { Objects.requireNonNull(predicate, "predicate is null"); return isInvalid() || predicate.test(get()) ? Option.some(this) : Option.none(); } @SuppressWarnings("unchecked") default Validation flatMap(Function> mapper) { Objects.requireNonNull(mapper, "mapper is null"); return isInvalid() ? (Validation) this : (Validation) mapper.apply(get()); } @Override default Validation peek(Consumer 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 v7; private Builder7(Validation v1, Validation v2, Validation v3, Validation v4, Validation v5, Validation v6, Validation v7) { this.v1 = v1; this.v2 = v2; this.v3 = v3; this.v4 = v4; this.v5 = v5; this.v6 = v6; this.v7 = v7; } public Validation, R> ap(Function7 f) { return v7.ap(v6.ap(v5.ap(v4.ap(v3.ap(v2.ap(v1.ap(Validation.valid(f.curried())))))))); } public Builder8 combine(Validation v8) { return new Builder8<>(v1, v2, v3, v4, v5, v6, v7, v8); } } final class Builder8 { private Validation v1; private Validation v2; private Validation v3; private Validation v4; private Validation v5; private Validation v6; private Validation v7; private Validation v8; private Builder8(Validation v1, Validation v2, Validation v3, Validation v4, Validation v5, Validation v6, Validation v7, Validation v8) { this.v1 = v1; this.v2 = v2; this.v3 = v3; this.v4 = v4; this.v5 = v5; this.v6 = v6; this.v7 = v7; this.v8 = v8; } public Validation, R> ap(Function8 f) { return v8.ap(v7.ap(v6.ap(v5.ap(v4.ap(v3.ap(v2.ap(v1.ap(Validation.valid(f.curried()))))))))); } } }