-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ExceptionMapper for RuntimeJsonException (#486)
* Add RuntimeJsonExceptionMapper * Extract duplicate code to package-private JsonExceptionMapper * Add response assertions to JsonProcessingExceptionMapperTest Closes #478
- Loading branch information
1 parent
54332b4
commit fbf794b
Showing
5 changed files
with
211 additions
and
29 deletions.
There are no files selected for viewing
39 changes: 39 additions & 0 deletions
39
src/main/java/org/kiwiproject/dropwizard/util/exception/JsonExceptionMappers.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package org.kiwiproject.dropwizard.util.exception; | ||
|
||
import com.fasterxml.jackson.core.JsonGenerationException; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; | ||
import jakarta.ws.rs.core.Response; | ||
import lombok.experimental.UtilityClass; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.kiwiproject.jaxrs.exception.JaxrsBadRequestException; | ||
import org.kiwiproject.jaxrs.exception.JaxrsException; | ||
import org.kiwiproject.jaxrs.exception.JaxrsExceptionMapper; | ||
|
||
@UtilityClass | ||
@Slf4j | ||
class JsonExceptionMappers { | ||
|
||
static final String DEFAULT_MSG = "Unable to process JSON"; | ||
|
||
static Response toResponse(JsonProcessingException exception) { | ||
JaxrsException e; | ||
|
||
if (exception instanceof JsonGenerationException || exception instanceof InvalidDefinitionException) { | ||
LOG.warn("Error generating JSON", exception); | ||
e = new JaxrsException(exception); | ||
} else { | ||
var message = exception.getOriginalMessage(); | ||
|
||
if (message.startsWith("No suitable constructor found")) { | ||
LOG.error("Unable to deserialize the specific type", exception); | ||
e = new JaxrsException(exception); | ||
} else { | ||
LOG.debug(DEFAULT_MSG, exception); | ||
e = new JaxrsBadRequestException(message, exception); | ||
} | ||
} | ||
|
||
return JaxrsExceptionMapper.buildResponse(e); | ||
} | ||
} |
26 changes: 2 additions & 24 deletions
26
src/main/java/org/kiwiproject/dropwizard/util/exception/JsonProcessingExceptionMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,45 +1,23 @@ | ||
package org.kiwiproject.dropwizard.util.exception; | ||
|
||
import com.fasterxml.jackson.core.JsonGenerationException; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; | ||
import jakarta.ws.rs.core.Response; | ||
import jakarta.ws.rs.ext.ExceptionMapper; | ||
import jakarta.ws.rs.ext.Provider; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.kiwiproject.jaxrs.exception.JaxrsBadRequestException; | ||
import org.kiwiproject.jaxrs.exception.JaxrsException; | ||
import org.kiwiproject.jaxrs.exception.JaxrsExceptionMapper; | ||
|
||
/** | ||
* Override default Dropwizard mapper to use kiwi's {@link org.kiwiproject.jaxrs.exception.ErrorMessage ErrorMessage}. | ||
* The response entity is built using {@link JaxrsExceptionMapper#buildResponseEntity(JaxrsException)}. | ||
*/ | ||
@Slf4j | ||
@Provider | ||
public class JsonProcessingExceptionMapper implements ExceptionMapper<JsonProcessingException> { | ||
|
||
public static final String DEFAULT_MSG = "Unable to process JSON"; | ||
public static final String DEFAULT_MSG = JsonExceptionMappers.DEFAULT_MSG; | ||
|
||
@Override | ||
public Response toResponse(JsonProcessingException exception) { | ||
JaxrsException e; | ||
|
||
if (exception instanceof JsonGenerationException || exception instanceof InvalidDefinitionException) { | ||
LOG.warn("Error generating JSON", exception); | ||
e = new JaxrsException(exception); | ||
} else { | ||
var message = exception.getOriginalMessage(); | ||
|
||
if (message.startsWith("No suitable constructor found")) { | ||
LOG.error("Unable to deserialize the specific type", exception); | ||
e = new JaxrsException(exception); | ||
} else { | ||
LOG.debug(DEFAULT_MSG, exception); | ||
e = new JaxrsBadRequestException(message, exception); | ||
} | ||
} | ||
|
||
return JaxrsExceptionMapper.buildResponse(e); | ||
return JsonExceptionMappers.toResponse(exception); | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
src/main/java/org/kiwiproject/dropwizard/util/exception/RuntimeJsonExceptionMapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package org.kiwiproject.dropwizard.util.exception; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import jakarta.ws.rs.core.Response; | ||
import jakarta.ws.rs.ext.ExceptionMapper; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.kiwiproject.jaxrs.exception.JaxrsException; | ||
import org.kiwiproject.jaxrs.exception.JaxrsExceptionMapper; | ||
import org.kiwiproject.json.RuntimeJsonException; | ||
|
||
/** | ||
* Map {@link RuntimeJsonException} to {@link Response}. | ||
* <p> | ||
* If the cause of the {@link RuntimeJsonException} is a {@link JsonProcessingException} then | ||
* the behavior is the same as {@link JsonProcessingExceptionMapper}. Otherwise, the mapped | ||
* response is a 500 Internal Server Error. | ||
* <p> | ||
*/ | ||
@Slf4j | ||
public class RuntimeJsonExceptionMapper implements ExceptionMapper<RuntimeJsonException> { | ||
|
||
public static final String DEFAULT_MSG = JsonExceptionMappers.DEFAULT_MSG; | ||
|
||
@Override | ||
public Response toResponse(RuntimeJsonException runtimeJsonException) { | ||
var throwable = runtimeJsonException.getCause(); | ||
|
||
if (throwable instanceof JsonProcessingException jsonProcessingException) { | ||
return JsonExceptionMappers.toResponse(jsonProcessingException); | ||
} | ||
|
||
LOG.warn("Cause of RuntimeJsonException was not JsonProcessingException, but was {}. Returning a 500.", | ||
throwable.getClass().getName()); | ||
return JaxrsExceptionMapper.buildResponse(JaxrsException.buildJaxrsException(throwable)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
116 changes: 116 additions & 0 deletions
116
src/test/java/org/kiwiproject/dropwizard/util/exception/RuntimeJsonExceptionMapperTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
package org.kiwiproject.dropwizard.util.exception; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
import static org.kiwiproject.dropwizard.util.exception.ErrorMessageAssertions.assertAndGetErrorMessage; | ||
import static org.kiwiproject.test.constants.KiwiTestConstants.JSON_HELPER; | ||
import static org.kiwiproject.test.jaxrs.JaxrsTestHelper.assertBadRequest; | ||
import static org.kiwiproject.test.jaxrs.JaxrsTestHelper.assertInternalServerErrorResponse; | ||
import static org.mockito.Mockito.mock; | ||
|
||
import com.fasterxml.jackson.core.JsonGenerationException; | ||
import com.fasterxml.jackson.core.JsonGenerator; | ||
import com.fasterxml.jackson.core.JsonParseException; | ||
import com.fasterxml.jackson.core.JsonParser; | ||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.JavaType; | ||
import com.fasterxml.jackson.databind.exc.InvalidDefinitionException; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
import org.junit.jupiter.api.BeforeEach; | ||
import org.junit.jupiter.api.DisplayName; | ||
import org.junit.jupiter.api.Test; | ||
import org.kiwiproject.json.RuntimeJsonException; | ||
|
||
import java.io.IOException; | ||
|
||
@DisplayName("RuntimeJsonExceptionMapper") | ||
class RuntimeJsonExceptionMapperTest { | ||
|
||
private RuntimeJsonExceptionMapper mapper; | ||
|
||
@BeforeEach | ||
void setUp() { | ||
mapper = new RuntimeJsonExceptionMapper(); | ||
} | ||
|
||
@Test | ||
void shouldProcessExceptionHavingCauseOfJsonProcessingException() { | ||
var runtimeJsonException = createRuntimeJsonException(); | ||
var jsonException = (JsonProcessingException) runtimeJsonException.getCause(); | ||
var response = mapper.toResponse(runtimeJsonException); | ||
assertBadRequest(response); | ||
|
||
var errorMessage = assertAndGetErrorMessage(response); | ||
assertThat(errorMessage.getMessage()).isEqualTo(jsonException.getOriginalMessage()); | ||
} | ||
|
||
private static RuntimeJsonException createRuntimeJsonException() { | ||
// missing comma before 'last' property | ||
var badJson = """ | ||
{ | ||
"first": "Bob" | ||
"last": "Jones" | ||
} | ||
"""; | ||
try { | ||
JSON_HELPER.toObject(badJson, Person.class); | ||
} catch (RuntimeJsonException e) { | ||
return e; | ||
} | ||
throw new RuntimeException("somehow didn't get a RuntimeJsonException parsing the bad JSON"); | ||
} | ||
|
||
@Test | ||
void shouldProcessExceptionHavingCauseOfJsonGenerationException() { | ||
var jsonException = new JsonGenerationException("Problem generating", mock(JsonGenerator.class)); | ||
var runtimeJsonException = new RuntimeJsonException(jsonException); | ||
var response = mapper.toResponse(runtimeJsonException); | ||
assertInternalServerErrorResponse(response); | ||
|
||
var errorMessage = assertAndGetErrorMessage(response); | ||
assertThat(errorMessage.getMessage()).isEqualTo(jsonException.getOriginalMessage()); | ||
} | ||
|
||
@Test | ||
void shouldProcessExceptionHavingCauseOfInvalidDefinitionException() { | ||
var jsonException = InvalidDefinitionException.from(mock(JsonParser.class), | ||
"Problem generating", mock(JavaType.class)); | ||
var runtimeJsonException = new RuntimeJsonException(jsonException); | ||
|
||
var response = mapper.toResponse(runtimeJsonException); | ||
assertInternalServerErrorResponse(response); | ||
|
||
var errorMessage = assertAndGetErrorMessage(response); | ||
assertThat(errorMessage.getMessage()).isEqualTo(jsonException.getOriginalMessage()); | ||
} | ||
|
||
@Test | ||
void shouldProcessExceptionHavingCauseOfJsonProcessingException_WithSpecificMessage() { | ||
var jsonException = new JsonParseException(mock(JsonParser.class), "No suitable constructor found for Foo"); | ||
var runtimeJsonException = new RuntimeJsonException(jsonException); | ||
|
||
var response = mapper.toResponse(runtimeJsonException); | ||
assertInternalServerErrorResponse(response); | ||
|
||
var errorMessage = assertAndGetErrorMessage(response); | ||
assertThat(errorMessage.getMessage()).isEqualTo(jsonException.getOriginalMessage()); | ||
} | ||
|
||
@Test | ||
void shouldProcessExceptionHavingCauseOfUnsupportedType() { | ||
var ioException = new IOException("some weird character encoding problem..."); | ||
var runtimeJsonException = new RuntimeJsonException(ioException); | ||
var response = mapper.toResponse(runtimeJsonException); | ||
assertInternalServerErrorResponse(response); | ||
|
||
var errorMessage = assertAndGetErrorMessage(response); | ||
assertThat(errorMessage.getMessage()).isEqualTo(ioException.getMessage()); | ||
} | ||
|
||
@Getter | ||
@Setter | ||
private static class Person { | ||
private String first; | ||
private String last; | ||
} | ||
} |