Skip to content

Commit 2663353

Browse files
committed
Extract BindingResult if necessary
Previously, no `errors` attribute is made available in the standard JSON error document if a request body object is invalid. This is due to the fact that the framework throws a `MethodArgumentNotValidException holding a `BindingResult` object that was not detected. We now make sure to extract the `BindingResult` from such exception. Closes gh-4166
1 parent 17fde26 commit 2663353

File tree

3 files changed

+68
-5
lines changed

3 files changed

+68
-5
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DefaultErrorAttributes.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
import org.springframework.util.StringUtils;
3333
import org.springframework.validation.BindingResult;
3434
import org.springframework.validation.ObjectError;
35+
import org.springframework.web.bind.MethodArgumentNotValidException;
3536
import org.springframework.web.context.request.RequestAttributes;
3637
import org.springframework.web.servlet.HandlerExceptionResolver;
3738
import org.springframework.web.servlet.ModelAndView;
@@ -52,6 +53,7 @@
5253
*
5354
* @author Phillip Webb
5455
* @author Dave Syer
56+
* @author Stephane Nicoll
5557
* @since 1.1.0
5658
* @see ErrorAttributes
5759
*/
@@ -130,11 +132,11 @@ private void addErrorDetails(Map<String, Object> errorAttributes,
130132
}
131133

132134
private void addErrorMessage(Map<String, Object> errorAttributes, Throwable error) {
133-
if (!(error instanceof BindingResult)) {
135+
BindingResult result = extractBindingResult(error);
136+
if (result == null) {
134137
errorAttributes.put("message", error.getMessage());
135138
return;
136139
}
137-
BindingResult result = (BindingResult) error;
138140
if (result.getErrorCount() > 0) {
139141
errorAttributes.put("errors", result.getAllErrors());
140142
errorAttributes.put("message",
@@ -146,6 +148,16 @@ private void addErrorMessage(Map<String, Object> errorAttributes, Throwable erro
146148
}
147149
}
148150

151+
private BindingResult extractBindingResult(Throwable error) {
152+
if (error instanceof BindingResult) {
153+
return (BindingResult) error;
154+
}
155+
if (error instanceof MethodArgumentNotValidException) {
156+
return ((MethodArgumentNotValidException) error).getBindingResult();
157+
}
158+
return null;
159+
}
160+
149161
private void addStackTrace(Map<String, Object> errorAttributes, Throwable error) {
150162
StringWriter stackTrace = new StringWriter();
151163
error.printStackTrace(new PrintWriter(stackTrace));

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/BasicErrorControllerIntegrationTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
import javax.servlet.http.HttpServletRequest;
2626
import javax.servlet.http.HttpServletResponse;
27+
import javax.validation.Valid;
28+
import javax.validation.constraints.NotNull;
2729

2830
import org.junit.After;
2931
import org.junit.Test;
@@ -45,7 +47,9 @@
4547
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
4648
import org.springframework.test.context.web.WebAppConfiguration;
4749
import org.springframework.validation.BindException;
50+
import org.springframework.web.bind.annotation.RequestBody;
4851
import org.springframework.web.bind.annotation.RequestMapping;
52+
import org.springframework.web.bind.annotation.RequestMethod;
4953
import org.springframework.web.bind.annotation.ResponseStatus;
5054
import org.springframework.web.bind.annotation.RestController;
5155
import org.springframework.web.servlet.View;
@@ -164,6 +168,20 @@ public void testBindingExceptionForMachineClient() throws Exception {
164168
assertThat(resp, containsString("org.springframework.validation.BindException"));
165169
}
166170

171+
@Test
172+
@SuppressWarnings("rawtypes")
173+
public void testRequestBodyValidationForMachineClient() throws Exception {
174+
load();
175+
RequestEntity request = RequestEntity.post(URI.create(createUrl("/bodyValidation")))
176+
.contentType(MediaType.APPLICATION_JSON).body("{}");
177+
ResponseEntity<Map> entity = new TestRestTemplate().exchange(request, Map.class);
178+
String resp = entity.getBody().toString();
179+
assertThat(resp, containsString("Error count: 1"));
180+
assertThat(resp, containsString("errors=[{"));
181+
assertThat(resp, containsString("codes=["));
182+
assertThat(resp, containsString("org.springframework.web.bind.MethodArgumentNotValidException"));
183+
}
184+
167185
private void assertErrorAttributes(Map<?, ?> content, String status, String error,
168186
Class<?> exception, String message, String path) {
169187
assertEquals("Wrong status", status, content.get("status"));
@@ -239,6 +257,11 @@ public String bind() throws Exception {
239257
throw error;
240258
}
241259

260+
@RequestMapping(path = "/bodyValidation", method = RequestMethod.POST, produces = "application/json")
261+
public String bodyValidation(@Valid @RequestBody DummyBody body) {
262+
return body.content;
263+
}
264+
242265
@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "Expected!")
243266
@SuppressWarnings("serial")
244267
private static class ExpectedException extends RuntimeException {
@@ -255,6 +278,20 @@ private static class NoReasonExpectedException extends RuntimeException {
255278

256279
}
257280

281+
private static class DummyBody {
282+
283+
@NotNull
284+
private String content;
285+
286+
public String getContent() {
287+
return this.content;
288+
}
289+
290+
public void setContent(String content) {
291+
this.content = content;
292+
}
293+
}
294+
258295
}
259296

260297
}

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/DefaultErrorAttributesTests.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2014 the original author or authors.
2+
* Copyright 2012-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
import org.springframework.validation.BindingResult;
3030
import org.springframework.validation.MapBindingResult;
3131
import org.springframework.validation.ObjectError;
32+
import org.springframework.web.bind.MethodArgumentNotValidException;
3233
import org.springframework.web.context.request.RequestAttributes;
3334
import org.springframework.web.context.request.ServletRequestAttributes;
3435
import org.springframework.web.servlet.ModelAndView;
@@ -162,7 +163,20 @@ public void extractBindingResultErrors() throws Exception {
162163
BindingResult bindingResult = new MapBindingResult(
163164
Collections.singletonMap("a", "b"), "objectName");
164165
bindingResult.addError(new ObjectError("c", "d"));
165-
BindException ex = new BindException(bindingResult);
166+
Exception ex = new BindException(bindingResult);
167+
testBindingResult(bindingResult, ex);
168+
}
169+
170+
@Test
171+
public void extractMethodArgumentNotValidExceptionBindingResultErrors() throws Exception {
172+
BindingResult bindingResult = new MapBindingResult(
173+
Collections.singletonMap("a", "b"), "objectName");
174+
bindingResult.addError(new ObjectError("c", "d"));
175+
Exception ex = new MethodArgumentNotValidException(null, bindingResult);
176+
testBindingResult(bindingResult, ex);
177+
}
178+
179+
private void testBindingResult(BindingResult bindingResult, Exception ex) {
166180
this.request.setAttribute("javax.servlet.error.exception", ex);
167181
Map<String, Object> attributes = this.errorAttributes
168182
.getErrorAttributes(this.requestAttributes, false);

0 commit comments

Comments
 (0)