Consistent processing of binding/validation failures for data classes
Includes an extension of SmartValidator for candidate value validation, as well as nullability refinements in Validator and BindingResult. Issue: SPR-16840 Issue: SPR-16841 Issue: SPR-16854
This commit is contained in:
@@ -32,6 +32,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
@@ -72,6 +73,7 @@ import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.format.support.DefaultFormattingConversionService;
|
||||
import org.springframework.format.support.FormattingConversionServiceFactoryBean;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@@ -1863,7 +1865,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
request.addParameter("param1", "value1");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
getServlet().service(request, response);
|
||||
assertEquals("value1-null-null", response.getContentAsString());
|
||||
assertEquals("1:value1-null-null", response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1875,7 +1877,7 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
request.addParameter("param2", "x");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
getServlet().service(request, response);
|
||||
assertEquals("value1-x-null", response.getContentAsString());
|
||||
assertEquals("1:value1-x-null", response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1884,10 +1886,21 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind");
|
||||
request.addParameter("param2", "true");
|
||||
request.addParameter("param3", "3");
|
||||
request.addParameter("param3", "0");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
getServlet().service(request, response);
|
||||
assertEquals("null-true-3", response.getContentAsString());
|
||||
assertEquals("1:null-true-0", response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataClassBindingWithValidationErrorAndConversionError() throws Exception {
|
||||
initServletWithControllers(ValidatedDataClassController.class);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind");
|
||||
request.addParameter("param2", "x");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
getServlet().service(request, response);
|
||||
assertEquals("2:null-x-null", response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1965,6 +1978,17 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
assertEquals("value1-false-0", response.getContentAsString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dataClassBindingWithLocalDate() throws Exception {
|
||||
initServletWithControllers(DateClassController.class);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/bind");
|
||||
request.addParameter("date", "2010-01-01");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
getServlet().service(request, response);
|
||||
assertEquals("2010-01-01", response.getContentAsString());
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
static class ControllerWithEmptyValueMapping {
|
||||
@@ -2915,14 +2939,12 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(Class<?> clazz, HttpInputMessage inputMessage)
|
||||
throws IOException, HttpMessageNotReadableException {
|
||||
throw new HttpMessageNotReadableException("Could not read");
|
||||
public Object read(Class<?> clazz, HttpInputMessage inputMessage) {
|
||||
throw new HttpMessageNotReadableException("Could not read", inputMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(Object o, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMessageNotWritableException {
|
||||
public void write(Object o, @Nullable MediaType contentType, HttpOutputMessage outputMessage) {
|
||||
throw new UnsupportedOperationException("Not implemented");
|
||||
}
|
||||
}
|
||||
@@ -3672,28 +3694,13 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
@RequestMapping("/bind")
|
||||
public BindStatusView handle(@Valid DataClass data, BindingResult result) {
|
||||
if (result.hasErrors()) {
|
||||
return new BindStatusView(result.getFieldValue("param1") + "-" +
|
||||
return new BindStatusView(result.getErrorCount() + ":" + result.getFieldValue("param1") + "-" +
|
||||
result.getFieldValue("param2") + "-" + result.getFieldValue("param3"));
|
||||
}
|
||||
return new BindStatusView(data.param1 + "-" + data.param2 + "-" + data.param3);
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class OptionalDataClassController {
|
||||
|
||||
@RequestMapping("/bind")
|
||||
public String handle(Optional<DataClass> optionalData, BindingResult result) {
|
||||
if (result.hasErrors()) {
|
||||
assertNotNull(optionalData);
|
||||
assertFalse(optionalData.isPresent());
|
||||
return result.getFieldValue("param1") + "-" + result.getFieldValue("param2") + "-" +
|
||||
result.getFieldValue("param3");
|
||||
}
|
||||
return optionalData.map(data -> data.param1 + "-" + data.param2 + "-" + data.param3).orElse("");
|
||||
}
|
||||
}
|
||||
|
||||
public static class BindStatusView extends AbstractView {
|
||||
|
||||
private final String content;
|
||||
@@ -3714,4 +3721,52 @@ public class ServletAnnotationControllerHandlerMethodTests extends AbstractServl
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class OptionalDataClassController {
|
||||
|
||||
@RequestMapping("/bind")
|
||||
public String handle(Optional<DataClass> optionalData, BindingResult result) {
|
||||
if (result.hasErrors()) {
|
||||
assertNotNull(optionalData);
|
||||
assertFalse(optionalData.isPresent());
|
||||
return result.getFieldValue("param1") + "-" + result.getFieldValue("param2") + "-" +
|
||||
result.getFieldValue("param3");
|
||||
}
|
||||
return optionalData.map(data -> data.param1 + "-" + data.param2 + "-" + data.param3).orElse("");
|
||||
}
|
||||
}
|
||||
|
||||
public static class DateClass {
|
||||
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd")
|
||||
public LocalDate date;
|
||||
|
||||
public DateClass(LocalDate date) {
|
||||
this.date = date;
|
||||
}
|
||||
}
|
||||
|
||||
@RestController
|
||||
public static class DateClassController {
|
||||
|
||||
@InitBinder
|
||||
public void initBinder(WebDataBinder binder) {
|
||||
binder.initDirectFieldAccess();
|
||||
binder.setConversionService(new DefaultFormattingConversionService());
|
||||
}
|
||||
|
||||
@RequestMapping("/bind")
|
||||
public String handle(DateClass data, BindingResult result) {
|
||||
if (result.hasErrors()) {
|
||||
return result.getFieldError().toString();
|
||||
}
|
||||
assertNotNull(data);
|
||||
assertNotNull(data.date);
|
||||
assertEquals(2010, data.date.getYear());
|
||||
assertEquals(1, data.date.getMonthValue());
|
||||
assertEquals(1, data.date.getDayOfMonth());
|
||||
return result.getFieldValue("date").toString();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user