• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.43
today-context/src/main/java/infra/validation/Errors.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.validation;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.util.List;
23
import java.util.Optional;
24
import java.util.function.Function;
25
import java.util.stream.Stream;
26

27
import infra.beans.PropertyAccessor;
28

29
/**
30
 * Stores and exposes information about data-binding and validation errors
31
 * for a specific object.
32
 *
33
 * <p>Field names are typically properties of the target object (e.g. "name"
34
 * when binding to a customer object). Implementations may also support nested
35
 * fields in case of nested objects (e.g. "address.street"), in conjunction
36
 * with subtree navigation via {@link #setNestedPath}: for example, an
37
 * {@code AddressValidator} may validate "address", not being aware that this
38
 * is a nested object of a top-level customer object.
39
 *
40
 * <p>Note: {@code Errors} objects are single-threaded.
41
 *
42
 * @author Rod Johnson
43
 * @author Juergen Hoeller
44
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
45
 * @see Validator
46
 * @see ValidationUtils
47
 * @see SimpleErrors
48
 * @see BindingResult
49
 * @since 4.0
50
 */
51
public interface Errors {
52

53
  /**
54
   * The separator between path elements in a nested path,
55
   * for example in "customer.name" or "customer.address.street".
56
   * <p>"." = same as the
57
   * {@link PropertyAccessor#NESTED_PROPERTY_SEPARATOR nested property separator}
58
   * in the beans package.
59
   */
60
  String NESTED_PATH_SEPARATOR = PropertyAccessor.NESTED_PROPERTY_SEPARATOR;
61

62
  /**
63
   * Return the name of the bound root object.
64
   */
65
  String getObjectName();
66

67
  /**
68
   * Allow context to be changed so that standard validators can validate
69
   * subtrees. Reject calls prepend the given path to the field names.
70
   * <p>For example, an address validator could validate the subobject
71
   * "address" of a customer object.
72
   * <p>The default implementation throws {@code UnsupportedOperationException}
73
   * since not all {@code Errors} implementations support nested paths.
74
   *
75
   * @param nestedPath nested path within this object,
76
   * e.g. "address" (defaults to "", {@code null} is also acceptable).
77
   * Can end with a dot: both "address" and "address." are valid.
78
   * @see #getNestedPath()
79
   */
80
  default void setNestedPath(String nestedPath) {
81
    throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support nested paths");
×
82
  }
83

84
  /**
85
   * Return the current nested path of this {@link Errors} object.
86
   * <p>Returns a nested path with a dot, i.e. "address.", for easy
87
   * building of concatenated paths. Default is an empty String.
88
   *
89
   * @see #setNestedPath(String)
90
   */
91
  default String getNestedPath() {
92
    return "";
×
93
  }
94

95
  /**
96
   * Push the given sub path onto the nested path stack.
97
   * <p>A {@link #popNestedPath()} call will reset the original
98
   * nested path before the corresponding
99
   * {@code pushNestedPath(String)} call.
100
   * <p>Using the nested path stack allows to set temporary nested paths
101
   * for subobjects without having to worry about a temporary path holder.
102
   * <p>For example: current path "spouse.", pushNestedPath("child") &rarr;
103
   * result path "spouse.child."; popNestedPath() &rarr; "spouse." again.
104
   * <p>The default implementation throws {@code UnsupportedOperationException}
105
   * since not all {@code Errors} implementations support nested paths.
106
   *
107
   * @param subPath the sub path to push onto the nested path stack
108
   * @see #popNestedPath()
109
   */
110
  default void pushNestedPath(String subPath) {
111
    throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support nested paths");
×
112
  }
113

114
  /**
115
   * Pop the former nested path from the nested path stack.
116
   *
117
   * @throws IllegalStateException if there is no former nested path on the stack
118
   * @see #pushNestedPath(String)
119
   */
120
  default void popNestedPath() throws IllegalStateException {
121
    throw new IllegalStateException("Cannot pop nested path: no nested path on stack");
×
122
  }
123

124
  /**
125
   * Register a global error for the entire target object,
126
   * using the given error description.
127
   *
128
   * @param errorCode error code, interpretable as a message key
129
   * @see #reject(String, Object[], String)
130
   */
131
  default void reject(String errorCode) {
132
    reject(errorCode, null, null);
5✔
133
  }
1✔
134

135
  /**
136
   * Register a global error for the entire target object,
137
   * using the given error description.
138
   *
139
   * @param errorCode error code, interpretable as a message key
140
   * @param defaultMessage fallback default message
141
   * @see #reject(String, Object[], String)
142
   */
143
  default void reject(String errorCode, String defaultMessage) {
144
    reject(errorCode, null, defaultMessage);
5✔
145
  }
1✔
146

147
  /**
148
   * Register a global error for the entire target object,
149
   * using the given error description.
150
   *
151
   * @param errorCode error code, interpretable as a message key
152
   * @param errorArgs error arguments, for argument binding via MessageFormat
153
   * (can be {@code null})
154
   * @param defaultMessage fallback default message
155
   * @see #rejectValue(String, String, Object[], String)
156
   */
157
  void reject(String errorCode, Object @Nullable [] errorArgs, @Nullable String defaultMessage);
158

159
  /**
160
   * Register a field error for the specified field of the current object
161
   * (respecting the current nested path, if any), using the given error
162
   * description.
163
   * <p>The field name may be {@code null} or empty String to indicate
164
   * the current object itself rather than a field of it. This may result
165
   * in a corresponding field error within the nested object graph or a
166
   * global error if the current object is the top object.
167
   *
168
   * @param field the field name (may be {@code null} or empty String)
169
   * @param errorCode error code, interpretable as a message key
170
   * @see #rejectValue(String, String, Object[], String)
171
   */
172
  default void rejectValue(@Nullable String field, String errorCode) {
173
    rejectValue(field, errorCode, null, null);
6✔
174
  }
1✔
175

176
  /**
177
   * Register a field error for the specified field of the current object
178
   * (respecting the current nested path, if any), using the given error
179
   * description.
180
   * <p>The field name may be {@code null} or empty String to indicate
181
   * the current object itself rather than a field of it. This may result
182
   * in a corresponding field error within the nested object graph or a
183
   * global error if the current object is the top object.
184
   *
185
   * @param field the field name (may be {@code null} or empty String)
186
   * @param errorCode error code, interpretable as a message key
187
   * @param defaultMessage fallback default message
188
   * @see #rejectValue(String, String, Object[], String)
189
   */
190
  default void rejectValue(@Nullable String field, String errorCode, String defaultMessage) {
191
    rejectValue(field, errorCode, null, defaultMessage);
6✔
192
  }
1✔
193

194
  /**
195
   * Register a field error for the specified field of the current object
196
   * (respecting the current nested path, if any), using the given error
197
   * description.
198
   * <p>The field name may be {@code null} or empty String to indicate
199
   * the current object itself rather than a field of it. This may result
200
   * in a corresponding field error within the nested object graph or a
201
   * global error if the current object is the top object.
202
   *
203
   * @param field the field name (may be {@code null} or empty String)
204
   * @param errorCode error code, interpretable as a message key
205
   * @param errorArgs error arguments, for argument binding via MessageFormat
206
   * (can be {@code null})
207
   * @param defaultMessage fallback default message
208
   * @see #reject(String, Object[], String)
209
   */
210
  void rejectValue(@Nullable String field, String errorCode,
211
          Object @Nullable [] errorArgs, @Nullable String defaultMessage);
212

213
  /**
214
   * Add all errors from the given {@code Errors} instance to this
215
   * {@code Errors} instance.
216
   * <p>This is a convenience method to avoid repeated {@code reject(..)}
217
   * calls for merging an {@code Errors} instance into another
218
   * {@code Errors} instance.
219
   * <p>Note that the passed-in {@code Errors} instance is supposed
220
   * to refer to the same target object, or at least contain compatible errors
221
   * that apply to the target object of this {@code Errors} instance.
222
   * <p>The default implementation throws {@code UnsupportedOperationException}
223
   * since not all {@code Errors} implementations support {@code #addAllErrors}.
224
   *
225
   * @param errors the {@code Errors} instance to merge in
226
   * @see #getAllErrors()
227
   */
228
  default void addAllErrors(Errors errors) {
229
    throw new UnsupportedOperationException(getClass().getSimpleName() + " does not support addAllErrors");
×
230
  }
231

232
  /**
233
   * Throw the mapped exception with a message summarizing the recorded errors.
234
   *
235
   * @param messageToException a function mapping the message to the exception,
236
   * e.g. {@code IllegalArgumentException::new} or {@code IllegalStateException::new}
237
   * @param <T> the exception type to be thrown
238
   * @see #toString()
239
   */
240
  default <T extends Throwable> void failOnError(Function<String, T> messageToException) throws T {
241
    if (hasErrors()) {
×
242
      throw messageToException.apply(toString());
×
243
    }
244
  }
×
245

246
  /**
247
   * Determine if there were any errors.
248
   *
249
   * @see #hasGlobalErrors()
250
   * @see #hasFieldErrors()
251
   */
252
  default boolean hasErrors() {
253
    return (!getGlobalErrors().isEmpty() || !getFieldErrors().isEmpty());
12!
254
  }
255

256
  /**
257
   * Determine the total number of errors.
258
   *
259
   * @see #getGlobalErrorCount()
260
   * @see #getFieldErrorCount()
261
   */
262
  default int getErrorCount() {
263
    return getGlobalErrors().size() + getFieldErrors().size();
8✔
264
  }
265

266
  /**
267
   * Get all errors, both global and field ones.
268
   *
269
   * @return a list of {@link ObjectError}/{@link FieldError} instances
270
   * @see #getGlobalErrors()
271
   * @see #getFieldErrors()
272
   */
273
  default List<ObjectError> getAllErrors() {
274
    return Stream.concat(getGlobalErrors().stream(), getFieldErrors().stream()).toList();
9✔
275
  }
276

277
  /**
278
   * Determine if there were any global errors.
279
   *
280
   * @see #hasFieldErrors()
281
   */
282
  default boolean hasGlobalErrors() {
283
    return !getGlobalErrors().isEmpty();
8✔
284
  }
285

286
  /**
287
   * Determine the number of global errors.
288
   *
289
   * @see #getFieldErrorCount()
290
   */
291
  default int getGlobalErrorCount() {
292
    return getGlobalErrors().size();
4✔
293
  }
294

295
  /**
296
   * Get all global errors.
297
   *
298
   * @return a list of {@link ObjectError} instances
299
   * @see #getFieldErrors()
300
   */
301
  List<ObjectError> getGlobalErrors();
302

303
  /**
304
   * Get the <i>first</i> global error, if any.
305
   *
306
   * @return the global error, or {@code null}
307
   * @see #getFieldError()
308
   */
309
  @Nullable
310
  default ObjectError getGlobalError() {
311
    return getGlobalErrors().stream().findFirst().orElse(null);
8✔
312
  }
313

314
  /**
315
   * Determine if there were any errors associated with a field.
316
   *
317
   * @see #hasGlobalErrors()
318
   */
319
  default boolean hasFieldErrors() {
320
    return !getFieldErrors().isEmpty();
8✔
321
  }
322

323
  /**
324
   * Determine the number of errors associated with a field.
325
   *
326
   * @see #getGlobalErrorCount()
327
   */
328
  default int getFieldErrorCount() {
329
    return getFieldErrors().size();
4✔
330
  }
331

332
  /**
333
   * Get all errors associated with a field.
334
   *
335
   * @return a List of {@link FieldError} instances
336
   * @see #getGlobalErrors()
337
   */
338
  List<FieldError> getFieldErrors();
339

340
  /**
341
   * Get the <i>first</i> error associated with a field, if any.
342
   *
343
   * @return the field-specific error, or {@code null}
344
   * @see #getGlobalError()
345
   */
346
  @Nullable
347
  default FieldError getFieldError() {
348
    return getFieldErrors().stream().findFirst().orElse(null);
8✔
349
  }
350

351
  /**
352
   * Determine if there were any errors associated with the given field.
353
   *
354
   * @param field the field name
355
   * @see #hasFieldErrors()
356
   */
357
  default boolean hasFieldErrors(String field) {
358
    return getFieldError(field) != null;
8✔
359
  }
360

361
  /**
362
   * Determine the number of errors associated with the given field.
363
   *
364
   * @param field the field name
365
   * @see #getFieldErrorCount()
366
   */
367
  default int getFieldErrorCount(String field) {
368
    return getFieldErrors(field).size();
5✔
369
  }
370

371
  /**
372
   * Get all errors associated with the given field.
373
   * <p>Implementations may support not only full field names like
374
   * "address.street" but also pattern matches like "address.*".
375
   *
376
   * @param field the field name
377
   * @return a List of {@link FieldError} instances
378
   * @see #getFieldErrors()
379
   */
380
  default List<FieldError> getFieldErrors(String field) {
381
    return getFieldErrors().stream().filter(error -> field.equals(error.getField())).toList();
13✔
382
  }
383

384
  /**
385
   * Get the first error associated with the given field, if any.
386
   *
387
   * @param field the field name
388
   * @return the field-specific error, or {@code null}
389
   * @see #getFieldError()
390
   */
391
  @Nullable
392
  default FieldError getFieldError(String field) {
393
    return getFieldErrors().stream().filter(error -> field.equals(error.getField())).findFirst().orElse(null);
16✔
394
  }
395

396
  /**
397
   * Return the current value of the given field, either the current
398
   * bean property value or a rejected update from the last binding.
399
   * <p>Allows for convenient access to user-specified field values,
400
   * even if there were type mismatches.
401
   *
402
   * @param field the field name
403
   * @return the current value of the given field
404
   * @see #getFieldType(String)
405
   */
406
  @Nullable
407
  Object getFieldValue(String field);
408

409
  /**
410
   * Determine the type of the given field, as far as possible.
411
   * <p>Implementations should be able to determine the type even
412
   * when the field value is {@code null}, for example from some
413
   * associated descriptor.
414
   *
415
   * @param field the field name
416
   * @return the type of the field, or {@code null} if not determinable
417
   * @see #getFieldValue(String)
418
   */
419
  @Nullable
420
  default Class<?> getFieldType(String field) {
421
    return Optional.ofNullable(getFieldValue(field)).map(Object::getClass).orElse(null);
×
422
  }
423

424
  /**
425
   * Return a summary of the recorded errors,
426
   * e.g. for inclusion in an exception message.
427
   *
428
   * @see #failOnError(Function)
429
   */
430
  String toString();
431

432
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc