Skip to content

Commit

Permalink
Merge pull request #65 from zalando/feature/json-error
Browse files Browse the repository at this point in the history
Added tests for details of HttpMessageNotReadableException
  • Loading branch information
whiskeysierra authored Sep 13, 2016
2 parents 61b0f38 + b7cfc93 commit cafebb3
Show file tree
Hide file tree
Showing 29 changed files with 159 additions and 85 deletions.
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@
<artifactId>jsr305</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.zalando.problem.spring.web.advice;

import lombok.SneakyThrows;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
Expand All @@ -21,12 +22,14 @@
import org.zalando.problem.spring.web.advice.validation.ValidationAdviceTrait;

import javax.ws.rs.core.Response.StatusType;
import java.net.URI;
import java.util.List;
import java.util.Optional;

import static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Arrays.asList;
import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION;
import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
import static org.springframework.http.HttpStatus.NOT_ACCEPTABLE;
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;
import static org.zalando.problem.spring.web.advice.Lists.lengthOfTrailingPartialSubList;
Expand Down Expand Up @@ -63,13 +66,12 @@
public interface AdviceTrait {

default ResponseEntity<Problem> create(final StatusType status, final Throwable throwable,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(status, throwable, request, new HttpHeaders());
}

default ResponseEntity<Problem> create(final StatusType status, final Throwable throwable,
final NativeWebRequest request, final HttpHeaders headers)
throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request, final HttpHeaders headers) {
return create(throwable, toProblem(throwable, status), request, headers);
}

Expand Down Expand Up @@ -105,48 +107,47 @@ default boolean isCausalChainsEnabled() {
return false;
}

default ResponseEntity<Problem> create(final ThrowableProblem problem, final NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
default ResponseEntity<Problem> create(final ThrowableProblem problem, final NativeWebRequest request) {
return create(problem, request, new HttpHeaders());
}

default ResponseEntity<Problem> create(final ThrowableProblem problem, final NativeWebRequest request,
final HttpHeaders headers) throws HttpMediaTypeNotAcceptableException {
final HttpHeaders headers) {
return create(problem, problem, request, headers);
}

default ResponseEntity<Problem> create(final Throwable throwable, final Problem problem,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(throwable, problem, request, new HttpHeaders());
}

default ResponseEntity<Problem> create(final Throwable throwable, final Problem problem,
final NativeWebRequest request, final HttpHeaders headers) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request, final HttpHeaders headers) {

final HttpStatus status = HttpStatus.valueOf(firstNonNull(
problem.getStatus(), INTERNAL_SERVER_ERROR).getStatusCode());

// TODO magic! always?
if (problem.getStatus().getStatusCode() == 500) {
if (status == HttpStatus.INTERNAL_SERVER_ERROR) {
request.setAttribute(ERROR_EXCEPTION, throwable, SCOPE_REQUEST);
}

return process(negotiate(request).map(contentType -> {
final int statusCode = problem.getStatus().getStatusCode();
final HttpStatus status = HttpStatus.valueOf(statusCode);

return ResponseEntity.status(status)
.headers(headers)
.contentType(contentType)
.body(problem);
}).orElseGet(() -> fallback(throwable, problem, request, headers)));

return process(negotiate(request).map(contentType ->
ResponseEntity.status(status)
.headers(headers)
.contentType(contentType)
.body(problem))
.orElseGet(() -> fallback(throwable, problem, request, headers)));
}

default ResponseEntity<Problem> fallback(final Throwable throwable, final Problem problem,
final NativeWebRequest request, final HttpHeaders headers) {
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(null);
return ResponseEntity.status(NOT_ACCEPTABLE).body(null);
}

default Optional<MediaType> negotiate(final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
@SneakyThrows(HttpMediaTypeNotAcceptableException.class)
default Optional<MediaType> negotiate(final NativeWebRequest request) {
final HeaderContentNegotiationStrategy negotiator = new HeaderContentNegotiationStrategy();

final List<MediaType> mediaTypes = negotiator.resolveMediaTypes(request);

if (mediaTypes.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package org.zalando.problem.spring.web.advice;

import java.util.Objects;
import org.springframework.http.HttpStatus;

import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status.Family;
import java.util.Objects;

/**
* An implementation of {@link javax.ws.rs.core.Response.StatusType} to map {@link HttpStatus}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
import org.zalando.problem.ThrowableProblem;
Expand All @@ -19,13 +18,12 @@
public interface SpringAdviceTrait extends AdviceTrait {

default ResponseEntity<Problem> create(final HttpStatus status, final Throwable throwable,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(status, throwable, request, new HttpHeaders());
}

default ResponseEntity<Problem> create(final HttpStatus status, final Throwable throwable,
final NativeWebRequest request, final HttpHeaders headers)
throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request, final HttpHeaders headers) {
return create(toStatus(status), throwable, request, headers);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.zalando.problem.spring.web.advice.general;

import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
Expand All @@ -17,7 +16,7 @@ public interface ProblemAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleProblem(
final ThrowableProblem problem,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(problem, request);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.zalando.problem.spring.web.advice.general;

import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
Expand All @@ -19,7 +18,7 @@ public interface ThrowableAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleThrowable(
final Throwable throwable,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.INTERNAL_SERVER_ERROR, throwable, request);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.zalando.problem.spring.web.advice.general;

import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
Expand All @@ -18,7 +17,7 @@ public interface UnsupportedOperationAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleUnsupportedOperation(
final UnsupportedOperationException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.NOT_IMPLEMENTED, exception, request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.google.gag.annotation.remark.WTF;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
Expand All @@ -19,7 +18,7 @@ public interface MethodNotAllowedAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleRequestMethodNotSupportedException(
final HttpRequestMethodNotSupportedException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {

@WTF
@Facepalm("Nullable arrays... great work from Spring :/")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public interface NotAcceptableAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMediaTypeNotAcceptable(
final HttpMediaTypeNotAcceptableException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.NOT_ACCEPTABLE, exception, request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
Expand All @@ -20,7 +19,7 @@ public interface UnsupportedMediaTypeAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMediaTypeNotSupportedException(
final HttpMediaTypeNotSupportedException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {

final HttpHeaders headers = new HttpHeaders();
headers.setAccept(exception.getSupportedMediaTypes());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
Expand All @@ -11,8 +10,6 @@
import javax.ws.rs.core.Response.Status;

/**
* TODO support for different causes
*
* @see HttpMessageNotReadableException
* @see Status#BAD_REQUEST
*/
Expand All @@ -21,7 +18,7 @@ public interface MessageNotReadableAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMessageNotReadableException(
final HttpMessageNotReadableException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.BAD_REQUEST, exception, request);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.zalando.problem.spring.web.advice.io;

import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.multipart.MultipartException;
Expand All @@ -16,7 +15,7 @@ public interface MultipartAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMultipart(
final MultipartException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.BAD_REQUEST, exception, request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.springframework.beans.TypeMismatchException;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
Expand All @@ -19,7 +18,7 @@ public interface TypeMistmatchAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleTypeMismatch(
final TypeMismatchException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.BAD_REQUEST, exception, request);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.zalando.problem.spring.web.advice.routing;

import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
Expand All @@ -19,7 +18,7 @@ public interface MissingServletRequestParameterAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMissingServletRequestParameter(
final MissingServletRequestParameterException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.BAD_REQUEST, exception, request);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.zalando.problem.spring.web.advice.routing;

import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
Expand All @@ -19,7 +18,7 @@ public interface MissingServletRequestPartAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleMissingServletRequestPart(
final MissingServletRequestPartException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.BAD_REQUEST, exception, request);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.zalando.problem.spring.web.advice.routing;

import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.DispatcherServlet;
Expand Down Expand Up @@ -29,7 +28,7 @@ public interface NoHandlerFoundAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleNoHandlerFound(
final NoHandlerFoundException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.NOT_FOUND, exception, request);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.zalando.problem.spring.web.advice.routing;

import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
Expand All @@ -19,7 +18,7 @@ public interface ServletRequestBindingAdviceTrait extends AdviceTrait {
@ExceptionHandler
default ResponseEntity<Problem> handleServletRequestBinding(
final ServletRequestBindingException exception,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Status.BAD_REQUEST, exception, request);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import org.springframework.http.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
Expand All @@ -14,7 +13,7 @@ public interface AuthenticationAdviceTrait extends AdviceTrait {

@ExceptionHandler
default ResponseEntity<Problem> handleAuthentication(final AuthenticationException e,
final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
final NativeWebRequest request) {
return create(Response.Status.FORBIDDEN, e, request);
}

Expand Down
Loading

0 comments on commit cafebb3

Please sign in to comment.