From b6869c1b17c65594f65c3ad5e53461082e5c3088 Mon Sep 17 00:00:00 2001
From: pacien
Date: Fri, 29 Mar 2019 01:31:15 +0100
Subject: initial impl
---
.../java/org/pacien/lemonad/attempt/Attempt.java | 129 +++++++++++++++++++++
.../java/org/pacien/lemonad/attempt/Failure.java | 42 +++++++
.../java/org/pacien/lemonad/attempt/Success.java | 42 +++++++
.../pacien/lemonad/attempt/ThrowingSupplier.java | 32 +++++
.../lemonad/validation/ValidationResult.java | 129 +++++++++++++++++++++
.../validation/ValidationResultContainer.java | 40 +++++++
.../org/pacien/lemonad/validation/Validator.java | 80 +++++++++++++
7 files changed, 494 insertions(+)
create mode 100644 src/main/java/org/pacien/lemonad/attempt/Attempt.java
create mode 100644 src/main/java/org/pacien/lemonad/attempt/Failure.java
create mode 100644 src/main/java/org/pacien/lemonad/attempt/Success.java
create mode 100644 src/main/java/org/pacien/lemonad/attempt/ThrowingSupplier.java
create mode 100644 src/main/java/org/pacien/lemonad/validation/ValidationResult.java
create mode 100644 src/main/java/org/pacien/lemonad/validation/ValidationResultContainer.java
create mode 100644 src/main/java/org/pacien/lemonad/validation/Validator.java
(limited to 'src/main/java/org')
diff --git a/src/main/java/org/pacien/lemonad/attempt/Attempt.java b/src/main/java/org/pacien/lemonad/attempt/Attempt.java
new file mode 100644
index 0000000..fc437ad
--- /dev/null
+++ b/src/main/java/org/pacien/lemonad/attempt/Attempt.java
@@ -0,0 +1,129 @@
+/*
+ * lemonad - Some functional sweetness for Java
+ * Copyright (C) 2019 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.lemonad.attempt;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import lombok.NonNull;
+
+/**
+ * Wraps either a value from a success or an error from a failure.
+ *
+ * @param the potential wrapped result type.
+ * @param the potential error type.
+ * @author pacien
+ */
+public interface Attempt {
+ /**
+ * @return whether the {@link Attempt} is successful.
+ */
+ boolean isSuccess();
+
+ /**
+ * @return whether the {@link Attempt} is failed.
+ */
+ boolean isFailure();
+
+ /**
+ * @return the result if this {@link Attempt} is a success.
+ * @throws java.util.NoSuchElementException if this {@link Attempt} is a failure.
+ */
+ R getResult();
+
+ /**
+ * @return the error if this {@link Attempt} is a failure.
+ * @throws java.util.NoSuchElementException if this {@link Attempt} is a success.
+ */
+ E getError();
+
+ /**
+ * @param resultConsumer a {@link Consumer} of result called if the {@link Attempt} is a success.
+ * @return the current {@link Attempt}
+ */
+ default Attempt ifSuccess(@NonNull Consumer super R> resultConsumer) {
+ if (isSuccess()) resultConsumer.accept(getResult());
+ return this;
+ }
+
+ /**
+ * @param errorConsumer a {@link Consumer} of error called if the {@link Attempt} is a failure.
+ * @return the current {@link Attempt}
+ */
+ default Attempt ifFailure(@NonNull Consumer super E> errorConsumer) {
+ if (isFailure()) errorConsumer.accept(getError());
+ return this;
+ }
+
+ /**
+ * @param mapper a function producing an {@link Attempt}, called with the current result if this {@link Attempt} is a success.
+ * @return this {@link Attempt} if it is a failure, or the produced one otherwise.
+ */
+ default Attempt mapResult(@NonNull Function super R, ? extends Attempt extends RR, ? extends E>> mapper) {
+ //noinspection unchecked
+ return (Attempt) (isSuccess() ? mapper.apply(getResult()) : this);
+ }
+
+ /**
+ * @param mapper a function producing an {@link Attempt}, called with the current error if this {@link Attempt} is a failure.
+ * @return this {@link Attempt} if it is a success, or the alternative {@link Attempt} retrieved from the supplier otherwise.
+ */
+ default Attempt mapFailure(@NonNull Function super E, ? extends Attempt extends R, ? extends EE>> mapper) {
+ //noinspection unchecked
+ return (Attempt) (isFailure() ? mapper.apply(getError()) : this);
+ }
+
+ /**
+ * @param mapper a function transforming an {@link Attempt}.
+ * @return the transformed {@link Attempt}
+ */
+ default Attempt flatMap(@NonNull Function super Attempt super R, ? super E>, ? extends Attempt extends RR, ? extends EE>> mapper) {
+ //noinspection unchecked
+ return (Attempt) mapper.apply(this);
+ }
+
+ /**
+ * @param result the result of the {@link Attempt}.
+ * @return a successful {@link Attempt} wrapping the supplied result.
+ */
+ static Attempt success(R result) {
+ return new Success<>(result);
+ }
+
+ /**
+ * @param error the cause of the failure of the {@link Attempt}.
+ * @return a failed {@link Attempt} with the supplied error.
+ */
+ static Attempt failure(E error) {
+ return new Failure<>(error);
+ }
+
+ /**
+ * @param supplier a {@code Supplier} that may throw an {@link Throwable}.
+ * @return an {@link Attempt} wrapping either the result of the execution of the supplier or any thrown {@link Throwable}.
+ */
+ static Attempt attempt(@NonNull ThrowingSupplier extends R, ? extends E> supplier) {
+ try {
+ return success(supplier.get());
+ } catch (Throwable throwable) {
+ //noinspection unchecked
+ return (Attempt) failure(throwable);
+ }
+ }
+}
diff --git a/src/main/java/org/pacien/lemonad/attempt/Failure.java b/src/main/java/org/pacien/lemonad/attempt/Failure.java
new file mode 100644
index 0000000..752e981
--- /dev/null
+++ b/src/main/java/org/pacien/lemonad/attempt/Failure.java
@@ -0,0 +1,42 @@
+/*
+ * lemonad - Some functional sweetness for Java
+ * Copyright (C) 2019 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.lemonad.attempt;
+
+import java.util.NoSuchElementException;
+
+import lombok.Value;
+
+/**
+ * @author pacien
+ */
+@Value class Failure implements Attempt {
+ E error;
+
+ @Override public boolean isSuccess() {
+ return false;
+ }
+
+ @Override public boolean isFailure() {
+ return true;
+ }
+
+ @Override public R getResult() {
+ throw new NoSuchElementException();
+ }
+}
diff --git a/src/main/java/org/pacien/lemonad/attempt/Success.java b/src/main/java/org/pacien/lemonad/attempt/Success.java
new file mode 100644
index 0000000..928fbb1
--- /dev/null
+++ b/src/main/java/org/pacien/lemonad/attempt/Success.java
@@ -0,0 +1,42 @@
+/*
+ * lemonad - Some functional sweetness for Java
+ * Copyright (C) 2019 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.lemonad.attempt;
+
+import java.util.NoSuchElementException;
+
+import lombok.Value;
+
+/**
+ * @author pacien
+ */
+@Value class Success implements Attempt {
+ R result;
+
+ @Override public boolean isSuccess() {
+ return true;
+ }
+
+ @Override public boolean isFailure() {
+ return false;
+ }
+
+ @Override public E getError() {
+ throw new NoSuchElementException();
+ }
+}
diff --git a/src/main/java/org/pacien/lemonad/attempt/ThrowingSupplier.java b/src/main/java/org/pacien/lemonad/attempt/ThrowingSupplier.java
new file mode 100644
index 0000000..6dc7854
--- /dev/null
+++ b/src/main/java/org/pacien/lemonad/attempt/ThrowingSupplier.java
@@ -0,0 +1,32 @@
+/*
+ * lemonad - Some functional sweetness for Java
+ * Copyright (C) 2019 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.lemonad.attempt;
+
+/**
+ * @param the result type.
+ * @param the {@link Throwable} type.
+ * @author pacien
+ */
+public interface ThrowingSupplier {
+ /**
+ * @return a result.
+ * @throws T a potential {@link Throwable}.
+ */
+ R get() throws T;
+}
diff --git a/src/main/java/org/pacien/lemonad/validation/ValidationResult.java b/src/main/java/org/pacien/lemonad/validation/ValidationResult.java
new file mode 100644
index 0000000..65bb389
--- /dev/null
+++ b/src/main/java/org/pacien/lemonad/validation/ValidationResult.java
@@ -0,0 +1,129 @@
+/*
+ * lemonad - Some functional sweetness for Java
+ * Copyright (C) 2019 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.lemonad.validation;
+
+import org.pacien.lemonad.attempt.Attempt;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+import lombok.NonNull;
+
+import static java.util.stream.Collectors.toUnmodifiableList;
+import static org.pacien.lemonad.attempt.Attempt.failure;
+import static org.pacien.lemonad.attempt.Attempt.success;
+
+/**
+ * Wraps the result of the validation of a subject.
+ *
+ * @param the subject type,
+ * @param the error type.
+ * @author pacien
+ */
+public interface ValidationResult {
+ /**
+ * @return whether no error have been reported during the validation.
+ */
+ boolean isValid();
+
+ /**
+ * @return whether some error have been reported during the validation.
+ */
+ boolean isInvalid();
+
+ /**
+ * @return the subject of the validation.
+ */
+ S getSubject();
+
+ /**
+ * @return the potentially empty list of reported validation errors.
+ */
+ List getErrors();
+
+ /**
+ * @param consumer a subject consumer called if the validation is successful.
+ * @return the current object.
+ */
+ default ValidationResult ifValid(@NonNull Consumer super S> consumer) {
+ if (isValid()) consumer.accept(getSubject());
+ return this;
+ }
+
+ /**
+ * @param consumer the consumer called with the validation subject and reported errors if the validation is failed.
+ * @return the current object.
+ */
+ default ValidationResult ifInvalid(@NonNull BiConsumer super S, ? super List super E>> consumer) {
+ if (!isValid()) consumer.accept(getSubject(), getErrors());
+ return this;
+ }
+
+ /**
+ * @return an {@link Attempt} with a state corresponding to the one of the validation.
+ */
+ default Attempt> toAttempt() {
+ return isValid() ? success(getSubject()) : failure(getErrors());
+ }
+
+ /**
+ * @param mapper a function transforming a {@link ValidationResult}.
+ * @return the transformed {@link ValidationResult}.
+ */
+ default ValidationResult flatMap(@NonNull Function super ValidationResult super S, ? super E>, ? extends ValidationResult extends SS, ? extends EE>> mapper) {
+ //noinspection unchecked
+ return (ValidationResult) mapper.apply(this);
+ }
+
+ /**
+ * @param subject an overriding subject.
+ * @param validationResults a {@link Stream} of {@link ValidationResult}s to merge.
+ * @return the merged {@link ValidationResult} containing all errors from the supplied ones.
+ */
+ static ValidationResult merge(S subject, @NonNull Stream extends ValidationResult, ? extends E>> validationResults) {
+ return new ValidationResultContainer<>(
+ subject,
+ validationResults.flatMap(res -> res.getErrors().stream()).collect(toUnmodifiableList()));
+ }
+
+ /**
+ * @param subject the suject of the validation.
+ * @return a successful {@link ValidationResult}.
+ */
+ static ValidationResult valid(S subject) {
+ return new ValidationResultContainer<>(subject, List.of());
+ }
+
+ /**
+ * @param subject the suject of the validation.
+ * @param error a validation error.
+ * @param errors additional validation errors.
+ * @return a failed {@link ValidationResult} for the supplied subject.
+ */
+ @SafeVarargs static ValidationResult invalid(S subject, E error, E... errors) {
+ return new ValidationResultContainer<>(
+ subject,
+ Stream.concat(Stream.of(error), Arrays.stream(errors)).map(Objects::requireNonNull).collect(toUnmodifiableList()));
+ }
+}
diff --git a/src/main/java/org/pacien/lemonad/validation/ValidationResultContainer.java b/src/main/java/org/pacien/lemonad/validation/ValidationResultContainer.java
new file mode 100644
index 0000000..2c752f6
--- /dev/null
+++ b/src/main/java/org/pacien/lemonad/validation/ValidationResultContainer.java
@@ -0,0 +1,40 @@
+/*
+ * lemonad - Some functional sweetness for Java
+ * Copyright (C) 2019 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.lemonad.validation;
+
+import java.util.List;
+
+import lombok.NonNull;
+import lombok.Value;
+
+/**
+ * @author pacien
+ */
+@Value class ValidationResultContainer implements ValidationResult {
+ S subject;
+ @NonNull List errors;
+
+ @Override public boolean isValid() {
+ return errors.isEmpty();
+ }
+
+ @Override public boolean isInvalid() {
+ return !isValid();
+ }
+}
diff --git a/src/main/java/org/pacien/lemonad/validation/Validator.java b/src/main/java/org/pacien/lemonad/validation/Validator.java
new file mode 100644
index 0000000..a8a9e3f
--- /dev/null
+++ b/src/main/java/org/pacien/lemonad/validation/Validator.java
@@ -0,0 +1,80 @@
+/*
+ * lemonad - Some functional sweetness for Java
+ * Copyright (C) 2019 Pacien TRAN-GIRARD
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package org.pacien.lemonad.validation;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import lombok.NonNull;
+import lombok.val;
+
+import static java.util.stream.Collectors.toUnmodifiableList;
+import static org.pacien.lemonad.validation.ValidationResult.invalid;
+import static org.pacien.lemonad.validation.ValidationResult.valid;
+
+/**
+ * A function which applies validation rules on a subject and reports possible errors.
+ *
+ * @param the subject type
+ * @param the error type
+ * @author pacien
+ */
+@FunctionalInterface public interface Validator {
+ /**
+ * @param subject the subject to validate, which can potentially be null.
+ * @return the non-null result of the validation of the supplied subject.
+ */
+ ValidationResult validate(S subject);
+
+ /**
+ * @param predicate the validation predicate testing the validity of a subject.
+ * @param negativeError an error to return if the subject does not pass the test.
+ * @return a {@link Validator} based on the supplied predicate and error.
+ */
+ static Validator ensuringPredicate(@NonNull Predicate super S> predicate, @NonNull E negativeError) {
+ return subject -> predicate.test(subject) ? valid(subject) : invalid(subject, negativeError);
+ }
+
+ /**
+ * @param validators the {@link Validator}s to combine, to be evaluated in order of listing.
+ * @return a {@link Validator} based on the supplied ones.
+ */
+ @SafeVarargs static Validator validatingAll(@NonNull Validator super S, ? extends E>... validators) {
+ val validatorList = Arrays.stream(validators).map(Objects::requireNonNull).collect(toUnmodifiableList());
+ return subject -> new ValidationResultContainer<>(
+ subject,
+ validatorList.stream()
+ .flatMap(validator -> validator.validate(subject).getErrors().stream())
+ .collect(toUnmodifiableList()));
+ }
+
+ /**
+ * @param getter the field getter mapping the validation subject.
+ * @param validator the {@link Validator} validating the field.
+ * @return a {@link Validator} validating the parent object.
+ */
+ static Validator validatingField(@NonNull Function super S, ? extends F> getter,
+ @NonNull Validator super F, ? extends E> validator) {
+ //noinspection unchecked
+ return subject -> new ValidationResultContainer<>(subject, (List) validator.validate(getter.apply(subject)).getErrors());
+ }
+}
--
cgit v1.2.3