Skip to content

Commit 9d1e93e

Browse files
committed
fix(advice): enhance validation error handling to include schema validation errors
1 parent 57bd6dd commit 9d1e93e

File tree

2 files changed

+46
-12
lines changed

2 files changed

+46
-12
lines changed

src/main/java/org/springframework/samples/petclinic/rest/advice/ExceptionControllerAdvice.java

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@
3131
import org.springframework.web.bind.annotation.ResponseBody;
3232

3333
import java.net.URI;
34+
import java.util.List;
35+
import java.util.Map;
3436
import java.time.Instant;
37+
import java.util.Objects;
3538

3639
/**
3740
* Global Exception handler for REST controllers.
@@ -53,16 +56,17 @@ public class ExceptionControllerAdvice {
5356
* Private method for constructing the {@link ProblemDetail} object passing the name and details of the exception
5457
* class.
5558
*
56-
* @param ex Object referring to the thrown exception.
59+
* @param e Object referring to the thrown exception.
5760
* @param status HTTP response status.
5861
* @param url URL request.
5962
*/
60-
private ProblemDetail detailBuild(Exception ex, HttpStatus status, StringBuffer url, String detail) {
63+
private ProblemDetail detailBuild(Exception e, HttpStatus status, StringBuffer url, String detail) {
6164
ProblemDetail problemDetail = ProblemDetail.forStatus(status);
6265
problemDetail.setType(URI.create(url.toString()));
63-
problemDetail.setTitle(ex.getClass().getSimpleName());
66+
problemDetail.setTitle(e.getClass().getSimpleName());
6467
problemDetail.setDetail(detail);
6568
problemDetail.setProperty("timestamp", Instant.now());
69+
problemDetail.setProperty("schemaValidationErrors", List.of());
6670
return problemDetail;
6771
}
6872

@@ -76,7 +80,7 @@ private ProblemDetail detailBuild(Exception ex, HttpStatus status, StringBuffer
7680
@ExceptionHandler(Exception.class)
7781
@ResponseBody
7882
public ResponseEntity<ProblemDetail> handleGeneralException(Exception e, HttpServletRequest request) {
79-
logger.error("Unexpected error occurred", e);
83+
logger.error("Unexpected error at {} {}", request.getMethod(), request.getRequestURI(), e);
8084
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
8185
ProblemDetail detail = this.detailBuild(e, status, request.getRequestURL(), ERROR_UNEXPECTED);
8286
return ResponseEntity.status(status).body(detail);
@@ -93,7 +97,11 @@ public ResponseEntity<ProblemDetail> handleGeneralException(Exception e, HttpSer
9397
@ExceptionHandler(DataIntegrityViolationException.class)
9498
@ResponseBody
9599
public ResponseEntity<ProblemDetail> handleDataIntegrityViolationException(DataIntegrityViolationException e, HttpServletRequest request) {
96-
logger.error("Data integrity violation: {}", e.getMessage());
100+
logger.warn("Data integrity violation at {} {}: {}",
101+
request.getMethod(),
102+
request.getRequestURI(),
103+
e.getMessage());
104+
logger.debug("Data integrity violation stacktrace", e);
97105
HttpStatus status = HttpStatus.NOT_FOUND;
98106
ProblemDetail detail = this.detailBuild(e, status, request.getRequestURL(), ERROR_DATA_INTEGRITY);
99107
return ResponseEntity.status(status).body(detail);
@@ -112,13 +120,27 @@ public ResponseEntity<ProblemDetail> handleMethodArgumentNotValidException(Metho
112120
HttpStatus status = HttpStatus.BAD_REQUEST;
113121
BindingErrorsResponse errors = new BindingErrorsResponse();
114122
BindingResult bindingResult = e.getBindingResult();
123+
ProblemDetail detail = this.detailBuild(e, status, request.getRequestURL(), ERROR_INVALID_REQUEST);
115124
if (bindingResult.hasErrors()) {
116125
errors.addAllErrors(bindingResult);
117-
logger.error("Invalid request: {}", bindingResult.getAllErrors());
118-
ProblemDetail detail = this.detailBuild(e, status, request.getRequestURL(), ERROR_INVALID_REQUEST);
126+
List<Map<String, String>> schemaValidationErrors = bindingResult.getFieldErrors().stream()
127+
.map(fieldError -> Map.of(
128+
"field", fieldError.getField(),
129+
"rejectedValue", Objects.toString(fieldError.getRejectedValue(), "null"),
130+
"defaultMessage", Objects.toString(fieldError.getDefaultMessage(), "Validation failed"),
131+
"message", "Field '%s' %s (rejected value: %s)".formatted(
132+
fieldError.getField(),
133+
Objects.toString(fieldError.getDefaultMessage(), "Validation failed"),
134+
Objects.toString(fieldError.getRejectedValue(), "null"))))
135+
.toList();
136+
logger.debug("Validation error at {} {}: {}",
137+
request.getMethod(),
138+
request.getRequestURI(),
139+
bindingResult.getFieldErrors());
140+
detail.setProperty("schemaValidationErrors", schemaValidationErrors);
119141
return ResponseEntity.status(status).body(detail);
120142
}
121-
return ResponseEntity.status(status).build();
143+
return ResponseEntity.status(status).body(detail);
122144
}
123145

124146
}

src/test/java/org/springframework/samples/petclinic/rest/controller/OwnerRestControllerTests.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,14 @@ void testCreatePetWithNullTypeShouldReturnBadRequestWithGenericDetail() throws E
423423
.andExpect(status().isBadRequest())
424424
.andExpect(jsonPath("$.detail").value("The request contains invalid or missing parameters"))
425425
.andExpect(jsonPath("$.title").value("MethodArgumentNotValidException"))
426-
.andExpect(jsonPath("$.schemaValidationErrors").doesNotExist());
426+
.andExpect(jsonPath("$.schemaValidationErrors").isArray())
427+
.andExpect(jsonPath("$.schemaValidationErrors[0].message").exists())
428+
.andExpect(jsonPath("$.schemaValidationErrors[0].field").exists())
429+
.andExpect(jsonPath("$.schemaValidationErrors[0].rejectedValue").exists())
430+
.andExpect(jsonPath("$.schemaValidationErrors[0].defaultMessage").exists());
427431
}
428432

429-
@Test
433+
@Test
430434
@WithMockUser(roles = "OWNER_ADMIN")
431435
void testCreatePetWithEmptyTypeNameShouldReturnBadRequestWithGenericDetail() throws Exception {
432436
PetDto newPet = pets.get(0);
@@ -442,7 +446,11 @@ void testCreatePetWithEmptyTypeNameShouldReturnBadRequestWithGenericDetail() thr
442446
.andExpect(status().isBadRequest())
443447
.andExpect(jsonPath("$.detail").value("The request contains invalid or missing parameters"))
444448
.andExpect(jsonPath("$.title").value("MethodArgumentNotValidException"))
445-
.andExpect(jsonPath("$.schemaValidationErrors").doesNotExist());
449+
.andExpect(jsonPath("$.schemaValidationErrors").isArray())
450+
.andExpect(jsonPath("$.schemaValidationErrors[0].message").exists())
451+
.andExpect(jsonPath("$.schemaValidationErrors[0].field").exists())
452+
.andExpect(jsonPath("$.schemaValidationErrors[0].rejectedValue").exists())
453+
.andExpect(jsonPath("$.schemaValidationErrors[0].defaultMessage").exists());
446454
}
447455

448456
@Test
@@ -461,7 +469,11 @@ void testCreatePetWithNullTypeIdShouldReturnBadRequestWithGenericDetail() throws
461469
.andExpect(status().isBadRequest())
462470
.andExpect(jsonPath("$.detail").value("The request contains invalid or missing parameters"))
463471
.andExpect(jsonPath("$.title").value("MethodArgumentNotValidException"))
464-
.andExpect(jsonPath("$.schemaValidationErrors").doesNotExist());
472+
.andExpect(jsonPath("$.schemaValidationErrors").isArray())
473+
.andExpect(jsonPath("$.schemaValidationErrors[0].message").exists())
474+
.andExpect(jsonPath("$.schemaValidationErrors[0].field").exists())
475+
.andExpect(jsonPath("$.schemaValidationErrors[0].rejectedValue").exists())
476+
.andExpect(jsonPath("$.schemaValidationErrors[0].defaultMessage").exists());
465477
}
466478

467479
@Test

0 commit comments

Comments
 (0)