Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,7 @@ public void invalidJsonNested() {
.body("issues[0].title", equalTo("Input value is invalid with respect to the schema"))
.body("issues[0].detail", equalTo("JSON syntax error"))
.body("issues[0].in", equalTo("body"))
.body("issues[0].name", emptyOrNullString());
.body("issues[0].name", equalTo("nested"));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;

import io.github.belgif.rest.problem.BadRequestProblem;
import io.github.belgif.rest.problem.internal.Jackson2Util;
Expand All @@ -17,15 +19,21 @@
@Order(1)
// @Order(1) to take precedence over org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
// and io.github.belgif.rest.problem.spring.ProblemExceptionHandler
public class RoutingExceptionsHandler extends AbstractRoutingExceptionsHandler<MismatchedInputException> {
public class RoutingExceptionsHandler extends AbstractRoutingExceptionsHandler<JacksonException> {

public RoutingExceptionsHandler() {
super(MismatchedInputException.class);
super(JacksonException.class);
}

@Override
protected BadRequestProblem toBadRequestProblem(MismatchedInputException mismatchedInputException) {
return Jackson2Util.toBadRequestProblem(mismatchedInputException);
protected BadRequestProblem toBadRequestProblem(JacksonException jacksonException) {
if (jacksonException instanceof JsonMappingException jsonMappingException) {
return Jackson2Util.toBadRequestProblem(jsonMappingException);
} else if (jacksonException instanceof JsonParseException jsonParseException) {
return Jackson2Util.toBadRequestProblem(jsonParseException);
} else {
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

import io.github.belgif.rest.problem.BadRequestProblem;
import io.github.belgif.rest.problem.internal.Jackson3Util;
import tools.jackson.databind.exc.MismatchedInputException;
import tools.jackson.core.JacksonException;
import tools.jackson.core.exc.StreamReadException;
import tools.jackson.databind.DatabindException;

/**
* RestController exception handler for routing-related exceptions.
Expand All @@ -16,15 +18,21 @@
@Order(1)
// @Order(1) to take precedence over org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
// and io.github.belgif.rest.problem.spring.ProblemExceptionHandler
public class RoutingExceptionsHandler extends AbstractRoutingExceptionsHandler<MismatchedInputException> {
public class RoutingExceptionsHandler extends AbstractRoutingExceptionsHandler<JacksonException> {

public RoutingExceptionsHandler() {
super(MismatchedInputException.class);
super(JacksonException.class);
}

@Override
protected BadRequestProblem toBadRequestProblem(MismatchedInputException mismatchedInputException) {
return Jackson3Util.toBadRequestProblem(mismatchedInputException);
protected BadRequestProblem toBadRequestProblem(JacksonException jacksonException) {
if (jacksonException instanceof DatabindException databindException) {
return Jackson3Util.toBadRequestProblem(databindException);
} else if (jacksonException instanceof StreamReadException streamReadException) {
return Jackson3Util.toBadRequestProblem(streamReadException);
} else {
return null;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@
/**
* RestController exception handler for routing-related exceptions.
*
* @param <T> the type of the Jackson MismatchedInputException
* @param <T> the type of the Jackson exception
*/
public abstract class AbstractRoutingExceptionsHandler<T extends Exception> {

private static final Logger LOGGER = LoggerFactory.getLogger(AbstractRoutingExceptionsHandler.class);

private static final ProblemExceptionHandler DEFAULT_PROBLEM_EXCEPTION_HANDLER = new ProblemExceptionHandler();

private final Class<T> jacksonMismatchedInputExceptionClass;
private final Class<T> jacksonExceptionClass;

protected AbstractRoutingExceptionsHandler(Class<T> jacksonMismatchedInputExceptionClass) {
this.jacksonMismatchedInputExceptionClass = jacksonMismatchedInputExceptionClass;
protected AbstractRoutingExceptionsHandler(Class<T> jacksonExceptionClass) {
this.jacksonExceptionClass = jacksonExceptionClass;
}

@ExceptionHandler(MissingServletRequestParameterException.class)
Expand All @@ -59,28 +59,27 @@ public ResponseEntity<Problem> handleMissingRequestHeaderException(MissingReques
@ExceptionHandler(HttpMessageNotReadableException.class)
@SuppressWarnings("unchecked")
public ResponseEntity<Problem> handleHttpMessageNotReadable(HttpMessageNotReadableException exception) {
if (exception.getCause() != null
&& jacksonMismatchedInputExceptionClass.isAssignableFrom(exception.getCause().getClass())) {
T mismatchedInputException = (T) exception.getCause();
if (Arrays.stream(mismatchedInputException.getStackTrace())
if (exception.getCause() != null && jacksonExceptionClass.isAssignableFrom(exception.getCause().getClass())) {
T jacksonException = (T) exception.getCause();
if (Arrays.stream(jacksonException.getStackTrace())
.anyMatch(e -> e.getClassName().startsWith("org.springframework.web.client"))) {
// When the MismatchedInputException originates from a REST Client API, it relates to an
// invalid inbound response and should be mapped to HTTP 500 Internal Server Error
return DEFAULT_PROBLEM_EXCEPTION_HANDLER.handleException(exception);
} else {
// Otherwise, it relates to an invalid inbound request and should be mapped to HTTP 400 Bad Request
return ProblemMediaType.INSTANCE.toResponse(toBadRequestProblem(mismatchedInputException));
}
} else {
LOGGER.info("Transforming HttpMessageNotReadableException " +
"to a BadRequestProblem with sanitized detail message", exception);
return ProblemMediaType.INSTANCE
.toResponse(new BadRequestProblem(InputValidationIssues.schemaViolation(InEnum.BODY, null, null,
getSanitizedProblemDetailMessage(exception))));
BadRequestProblem problem = toBadRequestProblem(jacksonException);
if (problem != null) {
return ProblemMediaType.INSTANCE.toResponse(problem);
}
}
LOGGER.info("Transforming HttpMessageNotReadableException "
+ "to a BadRequestProblem with sanitized detail message", exception);
return ProblemMediaType.INSTANCE
.toResponse(new BadRequestProblem(InputValidationIssues.schemaViolation(InEnum.BODY, null, null,
getSanitizedProblemDetailMessage(exception))));
}

protected abstract BadRequestProblem toBadRequestProblem(T mismatchedInputException);
protected abstract BadRequestProblem toBadRequestProblem(T jacksonException);

private String getSanitizedProblemDetailMessage(HttpMessageNotReadableException exception) {
if (exception.getMessage() != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.client.RestTemplate;

import com.fasterxml.jackson.core.JacksonException;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;

Expand All @@ -33,11 +35,17 @@

class AbstractRoutingExceptionsHandlerTest {

private final AbstractRoutingExceptionsHandler<MismatchedInputException> handler =
new AbstractRoutingExceptionsHandler<>(MismatchedInputException.class) {
private final AbstractRoutingExceptionsHandler<JacksonException> handler =
new AbstractRoutingExceptionsHandler<>(JacksonException.class) {
@Override
protected BadRequestProblem toBadRequestProblem(MismatchedInputException mismatchedInputException) {
return Jackson2Util.toBadRequestProblem(mismatchedInputException);
protected BadRequestProblem toBadRequestProblem(JacksonException jacksonException) {
if (jacksonException instanceof JsonMappingException jsonMappingException) {
return Jackson2Util.toBadRequestProblem(jsonMappingException);
} else if (jacksonException instanceof JsonParseException jsonParseException) {
return Jackson2Util.toBadRequestProblem(jsonParseException);
} else {
return null;
}
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ public static BadRequestProblem toBadRequestProblem(JsonParseException e) {
*/
public static BadRequestProblem toBadRequestProblem(JsonMappingException e) {
if (e.getCause() instanceof JsonParseException) {
return toBadRequestProblem((JsonParseException) e.getCause());
return new BadRequestProblem(schemaViolation(InEnum.BODY, getName(e.getPath()),
null, InputValidationIssues.DETAIL_JSON_SYNTAX_ERROR));
} else {
return new BadRequestProblem(
InputValidationIssues.schemaViolation(InEnum.BODY, getName(e.getPath()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ private Jackson3Util() {
* @return the BadRequestProblem
*/
public static BadRequestProblem toBadRequestProblem(StreamReadException e) {
return new BadRequestProblem(schemaViolation(InEnum.BODY, null,
return new BadRequestProblem(schemaViolation(InEnum.BODY, getName(e.getPath()),
null, InputValidationIssues.DETAIL_JSON_SYNTAX_ERROR));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ void jsonParseExceptionWrappedJsonMappingException() {
InputValidationIssue issue = problem.getIssues().get(0);
assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation");
assertThat(issue.getIn()).isEqualTo(InEnum.BODY);
assertThat(issue.getName()).isNull();
assertThat(issue.getName()).isEqualTo("model");
assertThat(issue.getValue()).isNull();
assertThat(issue.getDetail()).isEqualTo("JSON syntax error");
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ void mismatchedInput() {
@Test
void mismatchedInputType() {
assertThatExceptionOfType(MismatchedInputException.class).isThrownBy(() -> {
new ObjectMapper().readValue("{ \"id\": \"123\", \"nbr\": \"twenty-two\" }", Jackson2UtilTest.Model.class);
new ObjectMapper().readValue("{ \"id\": \"123\", \"nbr\": \"twenty-two\" }", Model.class);
}).satisfies(e -> {
BadRequestProblem problem = Jackson3Util.toBadRequestProblem(e);
InputValidationIssue issue = problem.getIssues().get(0);
Expand All @@ -55,7 +55,7 @@ void mismatchedInputType() {
@Test
void valueInstantiationException() {
assertThatExceptionOfType(ValueInstantiationException.class).isThrownBy(() -> {
new ObjectMapper().readValue("{ \"id\": \"123\", \"size\": \"XXL\" }", Jackson2UtilTest.Model.class);
new ObjectMapper().readValue("{ \"id\": \"123\", \"size\": \"XXL\" }", Model.class);
}).satisfies(e -> {
BadRequestProblem problem = Jackson3Util.toBadRequestProblem(e);
InputValidationIssue issue = problem.getIssues().get(0);
Expand All @@ -70,7 +70,7 @@ void valueInstantiationException() {
@Test
void invalidFormatException() {
assertThatExceptionOfType(InvalidFormatException.class).isThrownBy(() -> {
new ObjectMapper().readValue("{ \"id\": \"123\", \"size2\": \"XXL\" }", Jackson2UtilTest.Model.class);
new ObjectMapper().readValue("{ \"id\": \"123\", \"size2\": \"XXL\" }", Model.class);
}).satisfies(e -> {
BadRequestProblem problem = Jackson3Util.toBadRequestProblem(e);
InputValidationIssue issue = problem.getIssues().get(0);
Expand All @@ -91,7 +91,7 @@ void streamReadException() {
InputValidationIssue issue = problem.getIssues().get(0);
assertThat(issue.getType()).hasToString("urn:problem-type:belgif:input-validation:schemaViolation");
assertThat(issue.getIn()).isEqualTo(InEnum.BODY);
assertThat(issue.getName()).isNull();
assertThat(issue.getName()).isEqualTo("model");
assertThat(issue.getValue()).isNull();
assertThat(issue.getDetail()).isEqualTo("JSON syntax error");
});
Expand Down
1 change: 1 addition & 0 deletions src/main/asciidoc/release-notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

* Disable stacktrace on Problem exception class by default, as performance optimization.
You can configure `io.github.belgif.rest.problem.stack-trace-enabled` = true to opt out of this.
* Add name with JSON location to schemaViolation issue for JSON syntax error

*belgif-rest-problem-spring-boot-4:*

Expand Down