package co.unruly.control.validation;


import co.unruly.control.Optionals;

import java.util.Arrays;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public interface Validators {

    @SafeVarargs
    public static <T, E> Validator<T, E> compose(Validator<T, E>... validators) {
        return t -> Arrays.stream(validators).flatMap(v -> v.validate(t));
    }

    public static <T, E> Validator<T, E> rejectIf(Predicate<T> test, E error) {
        return acceptIf(test.negate(), error);
    }

    public static <T, E> Validator<T, E> rejectIf(Predicate<T> test, Function<T, E> errorGenerator) {
        return acceptIf(test.negate(), errorGenerator);
    }

    public static <T, E> Validator<T, E> acceptIf(Predicate<T> test, E error) {
        return acceptIf(test, t -> error);
    }

    public static <T, E> Validator<T, E> acceptIf(Predicate<T> test, Function<T, E> errorGenerator) {
        return t -> test.test(t) ? Stream.empty() : Stream.of(errorGenerator.apply(t));
    }

    public static <T, E> Validator<T, E> firstOf(Validator<T, E> validator) {
        return t -> Optionals.stream(validator.validate(t).findFirst());
    }

    public static <T, E> Validator<T, E> onlyIf(Predicate<T> test, Validator<T, E> validator) {
        return t -> test.test(t) ? validator.validate(t) : Stream.empty();
    }

    public static <T, E, E1> Validator<T, E1> mappingErrors(Validator<T, E> validator, BiFunction<T, E, E1> errorMapper) {
        return t -> validator.validate(t).map(e -> errorMapper.apply(t, e));
    }

    public static <T, T1, E> Validator<T, E> on(Function<T, T1> accessor, Validator<T1, E> innerValidator) {
        return t -> innerValidator.validate(accessor.apply(t));
    }

    public static <T, T1, E> Validator<T, E> onEach(Function<T, Iterable<T1>> iterator, Validator<T1, E> innerValidator) {
        return t -> StreamSupport.stream(iterator.apply(t).spliterator(), false).flatMap(innerValidator::validate);
    }

    public static <T, E> Validator<T, E> tryTo(Validator<T, E> validatorWhichThrowsRuntimeExceptions, Function<RuntimeException, E> errorMapper) {
        return t -> {
            try {
                return validatorWhichThrowsRuntimeExceptions.validate(t);
            } catch (RuntimeException ex) {
                return Stream.of(errorMapper.apply(ex));
            }
        };
    }

}

@@ -68,16 +68,75 @@
 tionals;
+%0Aimport co.unruly.control.ThrowingLambdas.ThrowingFunction;
 %0A%0Aimport
@@ -1890,32 +1890,443 @@
 ply(t));%0A    %7D%0A%0A
+    public static %3CT, T1, E, X extends Exception%3E Validator%3CT, E%3E tryOn(ThrowingFunction%3CT, T1, X%3E accessor, Function%3CException, E%3E onException, Validator%3CT1, E%3E innerValidator) %7B%0A        return t -%3E %7B%0A            try %7B%0A                return innerValidator.validate(accessor.apply(t));%0A            %7D catch (Exception e) %7B%0A                return Stream.of(onException.apply(e));%0A            %7D%0A        %7D;%0A    %7D%0A%0A
     public stati

Add tryOn validator, when we want to lens in on something via methods which can throw (potentially checked) exceptions