From b6869c1b17c65594f65c3ad5e53461082e5c3088 Mon Sep 17 00:00:00 2001 From: pacien Date: Fri, 29 Mar 2019 01:31:15 +0100 Subject: initial impl --- build.gradle | 35 +++++- settings.gradle | 19 ++- .../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 +++++++++++++ .../org/pacien/lemonad/attempt/AttemptTest.java | 114 ++++++++++++++++++ .../lemonad/validation/ValidationResultTest.java | 75 ++++++++++++ .../pacien/lemonad/validation/ValidatorTest.java | 61 ++++++++++ 12 files changed, 793 insertions(+), 5 deletions(-) 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 create mode 100644 src/test/java/org/pacien/lemonad/attempt/AttemptTest.java create mode 100644 src/test/java/org/pacien/lemonad/validation/ValidationResultTest.java create mode 100644 src/test/java/org/pacien/lemonad/validation/ValidatorTest.java diff --git a/build.gradle b/build.gradle index fb83c0a..b4b5242 100644 --- a/build.gradle +++ b/build.gradle @@ -1,16 +1,43 @@ +/* + * 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 . + */ + plugins { - id 'java' + id 'java' } group 'org.pacien' version '1.0-SNAPSHOT' -sourceCompatibility = 1.8 +sourceCompatibility = 1.11 + +test { + useJUnitPlatform() +} repositories { - mavenCentral() + mavenCentral() } dependencies { - testCompile group: 'junit', name: 'junit', version: '4.12' + compileOnly 'org.projectlombok:lombok:1.18.6' + testCompileOnly 'org.projectlombok:lombok:1.18.6' + annotationProcessor 'org.projectlombok:lombok:1.18.6' + + testCompile 'org.junit.jupiter:junit-jupiter-api:5.4.0' + testRuntime 'org.junit.jupiter:junit-jupiter-engine:5.4.0' } diff --git a/settings.gradle b/settings.gradle index cc93670..3796e5b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,19 @@ -rootProject.name = 'lemonad' +/* + * 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 . + */ +rootProject.name = 'lemonad' 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 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 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> 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> 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, ? extends Attempt> 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 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 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> 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, ? extends ValidationResult> 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> 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 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... 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 getter, + @NonNull Validator validator) { + //noinspection unchecked + return subject -> new ValidationResultContainer<>(subject, (List) validator.validate(getter.apply(subject)).getErrors()); + } +} diff --git a/src/test/java/org/pacien/lemonad/attempt/AttemptTest.java b/src/test/java/org/pacien/lemonad/attempt/AttemptTest.java new file mode 100644 index 0000000..1b165e8 --- /dev/null +++ b/src/test/java/org/pacien/lemonad/attempt/AttemptTest.java @@ -0,0 +1,114 @@ +/* + * 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 org.junit.jupiter.api.Test; + +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author pacien + */ +class AttemptTest { + @Test void testSimpleSuccess() { + var result = "result"; + var success = Attempt.success(result); + assertFalse(success.isFailure()); + assertTrue(success.isSuccess()); + assertThrows(NoSuchElementException.class, success::getError); + assertEquals(result, success.getResult()); + success.ifFailure(__ -> fail()); + success.ifSuccess(innerResult -> assertEquals(result, innerResult)); + } + + @Test void testSimpleFailure() { + var fault = 0; + var failure = Attempt.failure(fault); + assertTrue(failure.isFailure()); + assertFalse(failure.isSuccess()); + assertEquals(fault, failure.getError()); + assertThrows(NoSuchElementException.class, failure::getResult); + failure.ifFailure(innerFault -> assertEquals(fault, innerFault)); + failure.ifSuccess(__ -> fail()); + } + + @Test void testNormalAttempt() { + var result = "result"; + var success = Attempt.attempt(() -> result); + assertFalse(success.isFailure()); + assertTrue(success.isSuccess()); + assertThrows(NoSuchElementException.class, success::getError); + assertEquals(result, success.getResult()); + success.ifFailure(__ -> fail()); + success.ifSuccess(innerResult -> assertEquals(result, innerResult)); + } + + @Test void testFailedAttempt() { + var exception = new Exception(); + var failure = Attempt.attempt(() -> { + throw exception; + }); + assertTrue(failure.isFailure()); + assertFalse(failure.isSuccess()); + assertEquals(exception, failure.getError()); + assertThrows(NoSuchElementException.class, failure::getResult); + failure.ifFailure(innerFault -> assertEquals(exception, innerFault)); + failure.ifSuccess(__ -> fail()); + } + + @Test void testTransformationFlow() { + var result0 = 0; + var result1 = "res"; + var result2 = 0L; + var fault0 = 0; + var fault1 = 1; + var fault2 = 2; + + Attempt.success(result0) + .mapFailure(__ -> fail()) + .mapResult(res -> Attempt.success(result1)) + .mapResult(res -> { + assertEquals(result1, res); + return Attempt.failure(fault0); + }) + .ifSuccess(__ -> fail()) + .mapResult(__ -> fail()) + .mapFailure(f -> { + assertEquals(fault0, f); + return Attempt.failure(fault1); + }) + .mapFailure(f -> { + assertEquals(fault1, f); + return Attempt.success(result2); + }) + .ifFailure(__ -> fail()) + .flatMap(attempt -> { + assertEquals(result2, attempt.getResult()); + return Attempt.failure(fault2); + }) + .ifSuccess(__ -> fail()) + .ifFailure(f -> assertEquals(fault2, f)); + } +} diff --git a/src/test/java/org/pacien/lemonad/validation/ValidationResultTest.java b/src/test/java/org/pacien/lemonad/validation/ValidationResultTest.java new file mode 100644 index 0000000..65b2cb3 --- /dev/null +++ b/src/test/java/org/pacien/lemonad/validation/ValidationResultTest.java @@ -0,0 +1,75 @@ +/* + * 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.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +/** + * @author pacien + */ +class ValidationResultTest { + @Test void testValidResult() { + var subject = "subject"; + var validationResult = ValidationResult.valid(subject); + assertTrue(validationResult.getErrors().isEmpty()); + assertTrue(validationResult.isValid()); + assertFalse(validationResult.isInvalid()); + validationResult.ifValid(innerSubject -> assertEquals(subject, innerSubject)); + validationResult.ifInvalid((__, ___) -> fail()); + } + + @Test void testInvalidResult() { + var subject = "subject"; + var errors = List.of(0, 1); + var validationResult = ValidationResult.invalid(subject, 0, 1); + assertEquals(errors, validationResult.getErrors()); + assertFalse(validationResult.isValid()); + assertTrue(validationResult.isInvalid()); + validationResult.ifValid(Assertions::fail); + validationResult.ifInvalid((innerSubject, innerErrors) -> { + assertEquals(subject, innerSubject); + assertEquals(errors, innerErrors); + }); + } + + @Test void testFlatMap() { + ValidationResult.valid("subject") + .ifInvalid((__, ___) -> fail()) + .flatMap(res -> ValidationResult.invalid(res.getSubject(), 0)) + .ifValid(innerSubject -> fail()); + } + + @Test void testMerge() { + var subject = "subject"; + assertEquals(List.of(0, 1, 2, 3), ValidationResult.merge(subject, Stream.of( + ValidationResult.valid(subject), + ValidationResult.invalid(subject, 0, 1), + ValidationResult.invalid(subject, 2, 3)) + ).getErrors()); + } +} diff --git a/src/test/java/org/pacien/lemonad/validation/ValidatorTest.java b/src/test/java/org/pacien/lemonad/validation/ValidatorTest.java new file mode 100644 index 0000000..55927b5 --- /dev/null +++ b/src/test/java/org/pacien/lemonad/validation/ValidatorTest.java @@ -0,0 +1,61 @@ +/* + * 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.junit.jupiter.api.Test; + +import java.util.List; + +import static java.util.function.Predicate.not; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author pacien + */ +class ValidatorTest { + @Test void testValidatorEnsuringPredicate() { + var emptyError = 0; + var validator = Validator.ensuringPredicate(not(String::isEmpty), emptyError); + assertEquals(List.of(emptyError), validator.validate("").getErrors()); + assertEquals(List.of(), validator.validate("test").getErrors()); + } + + @Test void testValidatorValidatingAll() { + var emptyError = 0; + var tooLongError = 1; + var containsBadLetterError = 2; + + var validator = Validator.validatingAll( + Validator.ensuringPredicate(not(String::isEmpty), emptyError), + Validator.ensuringPredicate((String str) -> str.length() < 10, tooLongError), + Validator.ensuringPredicate((String str) -> !str.contains("e"), containsBadLetterError)); + + assertEquals(List.of(emptyError), validator.validate("").getErrors()); + assertEquals(List.of(tooLongError, containsBadLetterError), validator.validate("test test test").getErrors()); + assertEquals(List.of(), validator.validate("potato").getErrors()); + } + + @Test void testValidatingField() { + var emptyError = 0; + var fieldValidator = Validator.ensuringPredicate((Integer len) -> len > 0, emptyError); + var validator = Validator.validatingField(String::length, fieldValidator); + assertEquals(List.of(emptyError), validator.validate("").getErrors()); + assertEquals(List.of(), validator.validate("test").getErrors()); + } +} -- cgit v1.2.3