aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/org/pacien/lemonad/validation/Validation.java
blob: 98a4496210a2751d6fb293031e51e6dc01151341 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
/*
 * 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 <https://www.gnu.org/licenses/>.
 */

package org.pacien.lemonad.validation;

import org.pacien.lemonad.attempt.Attempt;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;

import lombok.NonNull;

import static java.util.function.Function.identity;
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 <S> the subject type,
 * @param <E> the error type.
 * @author pacien
 */
public interface Validation<S, E> {
  /**
   * @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<E> getErrors();

  /**
   * @param consumer a subject consumer called if the validation is successful.
   * @return the current object.
   */
  default Validation<S, E> 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 has failed.
   * @return the current object.
   */
  default Validation<S, E> ifInvalid(@NonNull BiConsumer<? super S, ? super List<? super E>> consumer) {
    if (isInvalid()) consumer.accept(getSubject(), getErrors());
    return this;
  }

  /**
   * @param predicate the validation predicate testing the validity of a subject.
   * @param error     the error to return if the subject does not pass the test.
   * @return an updated {@link Validation}.
   */
  default Validation<S, E> validate(@NonNull Predicate<? super S> predicate, @NonNull E error) {
    return validate(identity(), predicate, error);
  }

  /**
   * @param mapper    the field getter mapping the validation subject.
   * @param predicate the validation predicate testing the validity of a subject.
   * @param error     the error to return if the subject does not pass the test.
   * @return an updated {@link Validation}.
   */
  default <F> Validation<S, E> validate(
    @NonNull Function<? super S, ? extends F> mapper,
    @NonNull Predicate<? super F> predicate,
    E error
  ) {
    return validate(mapper, field -> predicate.test(field) ? List.of() : List.of(error));
  }

  /**
   * @param validator the validating function to use, returning a potentially empty list of errors.
   * @return an updated {@link Validation}.
   */
  default Validation<S, E> validate(@NonNull Function<? super S, ? extends List<? extends E>> validator) {
    var errors = validator.apply(getSubject());
    return errors.isEmpty() ? this : merge(errors);
  }

  /**
   * @param mapper    the field getter mapping the validation subject.
   * @param validator the validating function to use, returning a potentially empty list of errors.
   * @return an updated {@link Validation}.
   */
  default <F> Validation<S, E> validate(
    @NonNull Function<? super S, ? extends F> mapper,
    @NonNull Function<? super F, ? extends List<? extends E>> validator
  ) {
    return validate(validator.compose(mapper));
  }

  /**
   * @param validator a subject validating function returning a {@link Validation}.
   * @return an updated {@link Validation}.
   */
  default Validation<S, E> merge(@NonNull Function<? super S, ? extends Validation<?, ? extends E>> validator) {
    return merge(validator.apply(getSubject()));
  }

  /**
   * @param mapper    the field getter mapping the validation subject.
   * @param validator a subject validating function returning a {@link Validation}.
   * @return an updated {@link Validation}.
   */
  default <F> Validation<S, E> merge(
    @NonNull Function<? super S, ? extends F> mapper,
    @NonNull Function<? super F, ? extends Validation<?, ? extends E>> validator
  ) {
    return merge(validator.compose(mapper));
  }

  /**
   * @param validation another validation to merge into the current one.
   * @return an updated {@link Validation}.
   */
  @SuppressWarnings("unchecked")
  default Validation<S, E> merge(@NonNull Validation<?, ? extends E> validation) {
    if (validation.isValid()) return this;
    if (this.isValid()) return Validation.of(this.getSubject(), (List<E>) validation.getErrors());
    return merge(validation.getErrors());
  }

  /**
   * @param errors a potentially empty list of additional errors to take into account.
   * @return an updated {@link Validation}.
   */
  default Validation<S, E> merge(@NonNull Collection<? extends E> errors) {
    var combinedErrors = new ArrayList<E>(getErrors().size() + errors.size());
    combinedErrors.addAll(getErrors());
    combinedErrors.addAll(errors);
    return new ValidationContainer<>(getSubject(), combinedErrors);
  }

  /**
   * @param mapper a function transforming a {@link Validation}.
   * @return the transformed {@link Validation}.
   */
  default <SS, EE> Validation<SS, EE> flatMap(
    @NonNull Function<? super Validation<? super S, ? super E>, ? extends Validation<? extends SS, ? extends EE>> mapper
  ) {
    //noinspection unchecked
    return (Validation<SS, EE>) mapper.apply(this);
  }

  /**
   * @return an {@link Attempt} with a state corresponding to the one of the validation.
   */
  default Attempt<S, List<E>> toAttempt() {
    return isValid() ? success(getSubject()) : failure(getErrors());
  }

  /**
   * @param subject the subject of the validation.
   * @param errors  some optional validation errors.
   * @return a {@link Validation}.
   */
  @SafeVarargs static <S, E> Validation<S, E> of(S subject, E... errors) {
    return Validation.of(subject, List.of(errors));
  }

  /**
   * @param subject the subject of the validation.
   * @param errors  some optional validation errors.
   * @return a {@link Validation}.
   */
  static <S, E> Validation<S, E> of(S subject, @NonNull List<E> errors) {
    return new ValidationContainer<>(subject, errors);
  }
}