aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpacien2019-04-13 21:24:40 +0200
committerpacien2019-04-13 21:24:40 +0200
commit7ded9823fb54b81cbd73a133fd1e1fe036f31b04 (patch)
treed6f05d089507290d9cc47f24b6799e81ba31a5d8
parent80f7acccde921fb387a17d2a5712babf3001b974 (diff)
downloadjava-lemonad-7ded9823fb54b81cbd73a133fd1e1fe036f31b04.tar.gz
refactor validation
-rw-r--r--readme.md20
-rw-r--r--src/main/java/org/pacien/lemonad/validation/Validation.java133
-rw-r--r--src/main/java/org/pacien/lemonad/validation/ValidationContainer.java6
-rw-r--r--src/main/java/org/pacien/lemonad/validation/Validator.java80
-rw-r--r--src/test/java/org/pacien/lemonad/validation/ValidationTest.java39
-rw-r--r--src/test/java/org/pacien/lemonad/validation/ValidatorTest.java61
6 files changed, 143 insertions, 196 deletions
diff --git a/readme.md b/readme.md
index 8a1a720..cad76ee 100644
--- a/readme.md
+++ b/readme.md
@@ -28,28 +28,22 @@ the use of which being problematic in performance-sensitive contexts.
28import static org.pacien.lemonad.attempt.Attempt.*; 28import static org.pacien.lemonad.attempt.Attempt.*;
29 29
30(tree.hasLemon() ? success(tree.getLemon()) : failure("No lemon.")) 30(tree.hasLemon() ? success(tree.getLemon()) : failure("No lemon."))
31 .recoverError(__ -> store.buyLemon()) 31 .recoverError(error -> store.buyLemon())
32 .transformResult(this::makeLemonade) 32 .transformResult(this::makeLemonade)
33 .ifSuccess(this::drink); 33 .ifSuccess(this::drink);
34``` 34```
35 35
36### Validation 36### Validation
37 37
38The `Validation` monad represents a validation of a subject which can be either valid or invalid. 38The `Validation` monad represents a validation of a subject which can be successful or failed with errors.
39In the latter case, the monad wraps one or multiple validation errors in addition to the subject of the validation. 39Those errors are aggregated from all the checks that have failed.
40
41The `Validator` functional interface represents a function which performs verification operations on a supplied subject and returns
42a `Validation`.
43`Validator`s can be composed to perform verifications against multiple criteria and obtain an aggregated `Validation`.
44 40
45```java 41```java
46import static org.pacien.lemonad.validation.Validator.*; 42import org.pacien.lemonad.validation.Validation;
47
48var validator = validatingAll(
49 ensuringPredicate(not(Lemon::isRotten), "Bad lemon."),
50 validatingField(Lemon::juiceContent, ensuringPredicate(mL -> mL >= 40, "Not juicy.")));
51 43
52validator.validate(lemon) 44Validation.of(lemon)
45 .validate(not(Lemon::isRotten), "Bad lemon.")
46 .validate(Lemon::juiceContent, mL -> mL >= 40, "Not juicy")
53 .ifValid(this::makeLemonade) 47 .ifValid(this::makeLemonade)
54 .ifInvalid(errors -> makeLifeTakeTheLemonBack()); 48 .ifInvalid(errors -> makeLifeTakeTheLemonBack());
55``` 49```
diff --git a/src/main/java/org/pacien/lemonad/validation/Validation.java b/src/main/java/org/pacien/lemonad/validation/Validation.java
index 04c1ed0..98a4496 100644
--- a/src/main/java/org/pacien/lemonad/validation/Validation.java
+++ b/src/main/java/org/pacien/lemonad/validation/Validation.java
@@ -20,17 +20,17 @@ package org.pacien.lemonad.validation;
20 20
21import org.pacien.lemonad.attempt.Attempt; 21import org.pacien.lemonad.attempt.Attempt;
22 22
23import java.util.Arrays; 23import java.util.ArrayList;
24import java.util.Collection;
24import java.util.List; 25import java.util.List;
25import java.util.Objects;
26import java.util.function.BiConsumer; 26import java.util.function.BiConsumer;
27import java.util.function.Consumer; 27import java.util.function.Consumer;
28import java.util.function.Function; 28import java.util.function.Function;
29import java.util.stream.Stream; 29import java.util.function.Predicate;
30 30
31import lombok.NonNull; 31import lombok.NonNull;
32 32
33import static java.util.stream.Collectors.toUnmodifiableList; 33import static java.util.function.Function.identity;
34import static org.pacien.lemonad.attempt.Attempt.failure; 34import static org.pacien.lemonad.attempt.Attempt.failure;
35import static org.pacien.lemonad.attempt.Attempt.success; 35import static org.pacien.lemonad.attempt.Attempt.success;
36 36
@@ -72,58 +72,133 @@ public interface Validation<S, E> {
72 } 72 }
73 73
74 /** 74 /**
75 * @param consumer the consumer called with the validation subject and reported errors if the validation is failed. 75 * @param consumer the consumer called with the validation subject and reported errors if the validation has failed.
76 * @return the current object. 76 * @return the current object.
77 */ 77 */
78 default Validation<S, E> ifInvalid(@NonNull BiConsumer<? super S, ? super List<? super E>> consumer) { 78 default Validation<S, E> ifInvalid(@NonNull BiConsumer<? super S, ? super List<? super E>> consumer) {
79 if (!isValid()) consumer.accept(getSubject(), getErrors()); 79 if (isInvalid()) consumer.accept(getSubject(), getErrors());
80 return this; 80 return this;
81 } 81 }
82 82
83 /** 83 /**
84 * @return an {@link Attempt} with a state corresponding to the one of the validation. 84 * @param predicate the validation predicate testing the validity of a subject.
85 * @param error the error to return if the subject does not pass the test.
86 * @return an updated {@link Validation}.
85 */ 87 */
86 default Attempt<S, List<E>> toAttempt() { 88 default Validation<S, E> validate(@NonNull Predicate<? super S> predicate, @NonNull E error) {
87 return isValid() ? success(getSubject()) : failure(getErrors()); 89 return validate(identity(), predicate, error);
90 }
91
92 /**
93 * @param mapper the field getter mapping the validation subject.
94 * @param predicate the validation predicate testing the validity of a subject.
95 * @param error the error to return if the subject does not pass the test.
96 * @return an updated {@link Validation}.
97 */
98 default <F> Validation<S, E> validate(
99 @NonNull Function<? super S, ? extends F> mapper,
100 @NonNull Predicate<? super F> predicate,
101 E error
102 ) {
103 return validate(mapper, field -> predicate.test(field) ? List.of() : List.of(error));
104 }
105
106 /**
107 * @param validator the validating function to use, returning a potentially empty list of errors.
108 * @return an updated {@link Validation}.
109 */
110 default Validation<S, E> validate(@NonNull Function<? super S, ? extends List<? extends E>> validator) {
111 var errors = validator.apply(getSubject());
112 return errors.isEmpty() ? this : merge(errors);
113 }
114
115 /**
116 * @param mapper the field getter mapping the validation subject.
117 * @param validator the validating function to use, returning a potentially empty list of errors.
118 * @return an updated {@link Validation}.
119 */
120 default <F> Validation<S, E> validate(
121 @NonNull Function<? super S, ? extends F> mapper,
122 @NonNull Function<? super F, ? extends List<? extends E>> validator
123 ) {
124 return validate(validator.compose(mapper));
125 }
126
127 /**
128 * @param validator a subject validating function returning a {@link Validation}.
129 * @return an updated {@link Validation}.
130 */
131 default Validation<S, E> merge(@NonNull Function<? super S, ? extends Validation<?, ? extends E>> validator) {
132 return merge(validator.apply(getSubject()));
133 }
134
135 /**
136 * @param mapper the field getter mapping the validation subject.
137 * @param validator a subject validating function returning a {@link Validation}.
138 * @return an updated {@link Validation}.
139 */
140 default <F> Validation<S, E> merge(
141 @NonNull Function<? super S, ? extends F> mapper,
142 @NonNull Function<? super F, ? extends Validation<?, ? extends E>> validator
143 ) {
144 return merge(validator.compose(mapper));
145 }
146
147 /**
148 * @param validation another validation to merge into the current one.
149 * @return an updated {@link Validation}.
150 */
151 @SuppressWarnings("unchecked")
152 default Validation<S, E> merge(@NonNull Validation<?, ? extends E> validation) {
153 if (validation.isValid()) return this;
154 if (this.isValid()) return Validation.of(this.getSubject(), (List<E>) validation.getErrors());
155 return merge(validation.getErrors());
156 }
157
158 /**
159 * @param errors a potentially empty list of additional errors to take into account.
160 * @return an updated {@link Validation}.
161 */
162 default Validation<S, E> merge(@NonNull Collection<? extends E> errors) {
163 var combinedErrors = new ArrayList<E>(getErrors().size() + errors.size());
164 combinedErrors.addAll(getErrors());
165 combinedErrors.addAll(errors);
166 return new ValidationContainer<>(getSubject(), combinedErrors);
88 } 167 }
89 168
90 /** 169 /**
91 * @param mapper a function transforming a {@link Validation}. 170 * @param mapper a function transforming a {@link Validation}.
92 * @return the transformed {@link Validation}. 171 * @return the transformed {@link Validation}.
93 */ 172 */
94 default <SS, EE> Validation<SS, EE> flatMap(@NonNull Function<? super Validation<? super S, ? super E>, ? extends Validation<? extends SS, ? extends EE>> mapper) { 173 default <SS, EE> Validation<SS, EE> flatMap(
174 @NonNull Function<? super Validation<? super S, ? super E>, ? extends Validation<? extends SS, ? extends EE>> mapper
175 ) {
95 //noinspection unchecked 176 //noinspection unchecked
96 return (Validation<SS, EE>) mapper.apply(this); 177 return (Validation<SS, EE>) mapper.apply(this);
97 } 178 }
98 179
99 /** 180 /**
100 * @param subject an overriding subject. 181 * @return an {@link Attempt} with a state corresponding to the one of the validation.
101 * @param validatio