Skip to content

Commit

Permalink
Add convenience methods for binding error messages
Browse files Browse the repository at this point in the history
Closes gh-29574
  • Loading branch information
rstoyanchev committed Dec 6, 2022
1 parent 5214bd3 commit 918edab
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 17 deletions.
Expand Up @@ -17,8 +17,10 @@
package org.springframework.web.bind;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.Function;

import org.springframework.context.MessageSource;
Expand Down Expand Up @@ -97,20 +99,41 @@ public String getMessage() {

@Override
public Object[] getDetailMessageArguments() {
return new Object[] {
errorsToStringList(getBindingResult().getGlobalErrors()),
errorsToStringList(getBindingResult().getFieldErrors())
};
return new Object[] {errorsToStringList(getGlobalErrors()), errorsToStringList(getFieldErrors())};
}

@Override
public Object[] getDetailMessageArguments(MessageSource messageSource, Locale locale) {
return new Object[] {
errorsToStringList(getBindingResult().getGlobalErrors(), messageSource, locale),
errorsToStringList(getBindingResult().getFieldErrors(), messageSource, locale)
errorsToStringList(getGlobalErrors(), messageSource, locale),
errorsToStringList(getFieldErrors(), messageSource, locale)
};
}

/**
* Resolve global and field errors to messages with the given
* {@link MessageSource} and {@link Locale}.
* @return a Map with errors as key and resolves messages as value
* @since 6.0.3
*/
public Map<ObjectError, String> resolveErrorMessages(MessageSource messageSource, Locale locale) {
Map<ObjectError, String> map = new LinkedHashMap<>();
addMessages(map, getGlobalErrors(), messageSource, locale);
addMessages(map, getFieldErrors(), messageSource, locale);
return map;
}

private static void addMessages(
Map<ObjectError, String> map, List<? extends ObjectError> errors,
MessageSource messageSource, Locale locale) {

List<String> messages = errorsToStringList(errors, messageSource, locale);
for (int i = 0; i < errors.size(); i++) {
map.put(errors.get(i), messages.get(i));
}
}


/**
* Convert each given {@link ObjectError} to a String in single quotes, taking
* either the error's default message, or its error code.
Expand Down
Expand Up @@ -17,6 +17,7 @@
package org.springframework.web.bind.support;

import java.beans.PropertyEditor;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand Down Expand Up @@ -292,8 +293,8 @@ public String getMessage() {
StringBuilder sb = new StringBuilder("Validation failed for argument at index ")
.append(parameter.getParameterIndex()).append(" in method: ")
.append(parameter.getExecutable().toGenericString())
.append(", with ").append(this.bindingResult.getErrorCount()).append(" error(s): ");
for (ObjectError error : this.bindingResult.getAllErrors()) {
.append(", with ").append(getErrorCount()).append(" error(s): ");
for (ObjectError error : getAllErrors()) {
sb.append('[').append(error).append("] ");
}
return sb.toString();
Expand All @@ -302,11 +303,35 @@ public String getMessage() {
@Override
public Object[] getDetailMessageArguments(MessageSource source, Locale locale) {
return new Object[] {
MethodArgumentNotValidException.errorsToStringList(this.bindingResult.getGlobalErrors(), source, locale),
MethodArgumentNotValidException.errorsToStringList(this.bindingResult.getFieldErrors(), source, locale)
MethodArgumentNotValidException.errorsToStringList(getGlobalErrors(), source, locale),
MethodArgumentNotValidException.errorsToStringList(getFieldErrors(), source, locale)
};
}

/**
* Resolve global and field errors to messages with the given
* {@link MessageSource} and {@link Locale}.
* @return a Map with errors as key and resolves messages as value
* @since 6.0.3
*/
public Map<ObjectError, String> resolveErrorMessages(MessageSource messageSource, Locale locale) {
Map<ObjectError, String> map = new LinkedHashMap<>();
addMessages(map, getGlobalErrors(), messageSource, locale);
addMessages(map, getFieldErrors(), messageSource, locale);
return map;
}

private static void addMessages(
Map<ObjectError, String> map, List<? extends ObjectError> errors,
MessageSource messageSource, Locale locale) {

List<String> messages = MethodArgumentNotValidException.errorsToStringList(errors, messageSource, locale);
for (int i = 0; i < errors.size(); i++) {
map.put(errors.get(i), messages.get(i));
}
}


@Override
public boolean equals(@Nullable Object other) {
return (this == other || this.bindingResult.equals(other));
Expand Down
Expand Up @@ -20,10 +20,13 @@
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.function.BiFunction;

import org.junit.jupiter.api.Test;

import org.springframework.beans.testfixture.beans.TestBean;
import org.springframework.context.MessageSource;
import org.springframework.context.support.StaticMessageSource;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
Expand All @@ -35,6 +38,7 @@
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.validation.BindException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingMatrixVariableException;
import org.springframework.web.bind.MissingPathVariableException;
Expand Down Expand Up @@ -243,11 +247,12 @@ void methodArgumentNotValidException() {
MessageSourceTestHelper messageSourceHelper = new MessageSourceTestHelper(MethodArgumentNotValidException.class);
BindingResult bindingResult = messageSourceHelper.initBindingResult();

ErrorResponse ex = new MethodArgumentNotValidException(this.methodParameter, bindingResult);
MethodArgumentNotValidException ex = new MethodArgumentNotValidException(this.methodParameter, bindingResult);

assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Invalid request content.");
messageSourceHelper.assertDetailMessage(ex);
messageSourceHelper.assertErrorMessages(ex::resolveErrorMessages);

assertThat(ex.getHeaders()).isEmpty();
}
Expand Down Expand Up @@ -361,6 +366,7 @@ void webExchangeBindException() {
assertStatus(ex, HttpStatus.BAD_REQUEST);
assertDetail(ex, "Invalid request content.");
messageSourceHelper.assertDetailMessage(ex);
messageSourceHelper.assertErrorMessages(ex::resolveErrorMessages);

assertThat(ex.getHeaders()).isEmpty();
}
Expand Down Expand Up @@ -444,12 +450,8 @@ public BindingResult initBindingResult() {
}

private void assertDetailMessage(ErrorResponse ex) {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage(this.code, Locale.UK, "Failures {0}. nested failures: {1}");
messageSource.addMessage("bean.invalid.A", Locale.UK, "Bean A message");
messageSource.addMessage("bean.invalid.B", Locale.UK, "Bean B message");
messageSource.addMessage("name.required", Locale.UK, "Required name message");
messageSource.addMessage("age.min", Locale.UK, "Minimum age message");

StaticMessageSource messageSource = initMessageSource();

String message = messageSource.getMessage(
ex.getDetailMessageCode(), ex.getDetailMessageArguments(), Locale.UK);
Expand All @@ -465,6 +467,24 @@ private void assertDetailMessage(ErrorResponse ex) {
"Failures ['Bean A message', 'Bean B message']. " +
"nested failures: [name: 'Required name message', age: 'Minimum age message']");
}

private void assertErrorMessages(BiFunction<MessageSource, Locale, Map<ObjectError, String>> expectedMessages) {
StaticMessageSource messageSource = initMessageSource();
Map<ObjectError, String> map = expectedMessages.apply(messageSource, Locale.UK);

assertThat(map).hasSize(4).containsValues(
"'Bean A message'", "'Bean B message'", "name: 'Required name message'", "age: 'Minimum age message'");
}

private StaticMessageSource initMessageSource() {
StaticMessageSource messageSource = new StaticMessageSource();
messageSource.addMessage(this.code, Locale.UK, "Failures {0}. nested failures: {1}");
messageSource.addMessage("bean.invalid.A", Locale.UK, "Bean A message");
messageSource.addMessage("bean.invalid.B", Locale.UK, "Bean B message");
messageSource.addMessage("name.required", Locale.UK, "Required name message");
messageSource.addMessage("age.min", Locale.UK, "Minimum age message");
return messageSource;
}
}

}

0 comments on commit 918edab

Please sign in to comment.