diff --git a/pom.xml b/pom.xml index f7050cb0..d96482be 100644 --- a/pom.xml +++ b/pom.xml @@ -68,6 +68,12 @@ jsr305 3.0.1 + + org.projectlombok + lombok + 1.16.10 + provided + com.google.guava guava diff --git a/src/main/java/org/zalando/problem/spring/web/advice/AdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/AdviceTrait.java index ae2835ba..f5547985 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/AdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/AdviceTrait.java @@ -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; @@ -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; @@ -63,13 +66,12 @@ public interface AdviceTrait { default ResponseEntity create(final StatusType status, final Throwable throwable, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(status, throwable, request, new HttpHeaders()); } default ResponseEntity 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); } @@ -105,48 +107,47 @@ default boolean isCausalChainsEnabled() { return false; } - default ResponseEntity create(final ThrowableProblem problem, final NativeWebRequest request) - throws HttpMediaTypeNotAcceptableException { + default ResponseEntity create(final ThrowableProblem problem, final NativeWebRequest request) { return create(problem, request, new HttpHeaders()); } default ResponseEntity create(final ThrowableProblem problem, final NativeWebRequest request, - final HttpHeaders headers) throws HttpMediaTypeNotAcceptableException { + final HttpHeaders headers) { return create(problem, problem, request, headers); } default ResponseEntity create(final Throwable throwable, final Problem problem, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(throwable, problem, request, new HttpHeaders()); } default ResponseEntity 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 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 negotiate(final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + @SneakyThrows(HttpMediaTypeNotAcceptableException.class) + default Optional negotiate(final NativeWebRequest request) { final HeaderContentNegotiationStrategy negotiator = new HeaderContentNegotiationStrategy(); + final List mediaTypes = negotiator.resolveMediaTypes(request); if (mediaTypes.isEmpty()) { diff --git a/src/main/java/org/zalando/problem/spring/web/advice/HttpStatusAdapter.java b/src/main/java/org/zalando/problem/spring/web/advice/HttpStatusAdapter.java index c2c23adf..cb7b6288 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/HttpStatusAdapter.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/HttpStatusAdapter.java @@ -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}. diff --git a/src/main/java/org/zalando/problem/spring/web/advice/SpringAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/SpringAdviceTrait.java index 5b132374..dfdace25 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/SpringAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/SpringAdviceTrait.java @@ -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; @@ -19,13 +18,12 @@ public interface SpringAdviceTrait extends AdviceTrait { default ResponseEntity create(final HttpStatus status, final Throwable throwable, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(status, throwable, request, new HttpHeaders()); } default ResponseEntity 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); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/general/ProblemAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/general/ProblemAdviceTrait.java index 8213b631..452cab70 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/general/ProblemAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/general/ProblemAdviceTrait.java @@ -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; @@ -17,7 +16,7 @@ public interface ProblemAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleProblem( final ThrowableProblem problem, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(problem, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/general/ThrowableAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/general/ThrowableAdviceTrait.java index 8f98ddb5..aea88251 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/general/ThrowableAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/general/ThrowableAdviceTrait.java @@ -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; @@ -19,7 +18,7 @@ public interface ThrowableAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleThrowable( final Throwable throwable, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.INTERNAL_SERVER_ERROR, throwable, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/general/UnsupportedOperationAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/general/UnsupportedOperationAdviceTrait.java index 3cc7164f..c51bcd78 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/general/UnsupportedOperationAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/general/UnsupportedOperationAdviceTrait.java @@ -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; @@ -18,7 +17,7 @@ public interface UnsupportedOperationAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleUnsupportedOperation( final UnsupportedOperationException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.NOT_IMPLEMENTED, exception, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTrait.java index 225b066a..a2822daf 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTrait.java @@ -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; @@ -19,7 +18,7 @@ public interface MethodNotAllowedAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleRequestMethodNotSupportedException( final HttpRequestMethodNotSupportedException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { @WTF @Facepalm("Nullable arrays... great work from Spring :/") diff --git a/src/main/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTrait.java index c844e316..4ee554be 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTrait.java @@ -18,7 +18,7 @@ public interface NotAcceptableAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleMediaTypeNotAcceptable( final HttpMediaTypeNotAcceptableException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.NOT_ACCEPTABLE, exception, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTrait.java index 661320fd..536d307a 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTrait.java @@ -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; @@ -20,7 +19,7 @@ public interface UnsupportedMediaTypeAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleMediaTypeNotSupportedException( final HttpMediaTypeNotSupportedException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { final HttpHeaders headers = new HttpHeaders(); headers.setAccept(exception.getSupportedMediaTypes()); diff --git a/src/main/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTrait.java index f2703993..fcfb5d4a 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTrait.java @@ -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; @@ -11,8 +10,6 @@ import javax.ws.rs.core.Response.Status; /** - * TODO support for different causes - * * @see HttpMessageNotReadableException * @see Status#BAD_REQUEST */ @@ -21,7 +18,7 @@ public interface MessageNotReadableAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleMessageNotReadableException( final HttpMessageNotReadableException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.BAD_REQUEST, exception, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/io/MultipartAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/io/MultipartAdviceTrait.java index 10d3e842..5454ea03 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/io/MultipartAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/io/MultipartAdviceTrait.java @@ -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; @@ -16,7 +15,7 @@ public interface MultipartAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleMultipart( final MultipartException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.BAD_REQUEST, exception, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMistmatchAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMistmatchAdviceTrait.java index f807d62b..cee376f3 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMistmatchAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMistmatchAdviceTrait.java @@ -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; @@ -19,7 +18,7 @@ public interface TypeMistmatchAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleTypeMismatch( final TypeMismatchException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.BAD_REQUEST, exception, request); } } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTrait.java index 111e250e..472b53cc 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTrait.java @@ -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; @@ -19,7 +18,7 @@ public interface MissingServletRequestParameterAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleMissingServletRequestParameter( final MissingServletRequestParameterException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.BAD_REQUEST, exception, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTrait.java index 2d1c3828..b2d0f92c 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTrait.java @@ -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; @@ -19,7 +18,7 @@ public interface MissingServletRequestPartAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleMissingServletRequestPart( final MissingServletRequestPartException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.BAD_REQUEST, exception, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTrait.java index 138456d1..b235453d 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTrait.java @@ -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; @@ -29,7 +28,7 @@ public interface NoHandlerFoundAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleNoHandlerFound( final NoHandlerFoundException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.NOT_FOUND, exception, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTrait.java index 235eb90a..b824ea74 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTrait.java @@ -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; @@ -19,7 +18,7 @@ public interface ServletRequestBindingAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleServletRequestBinding( final ServletRequestBindingException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Status.BAD_REQUEST, exception, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/security/AuthenticationAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/security/AuthenticationAdviceTrait.java index b211c082..21e11d52 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/security/AuthenticationAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/security/AuthenticationAdviceTrait.java @@ -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; @@ -14,7 +13,7 @@ public interface AuthenticationAdviceTrait extends AdviceTrait { @ExceptionHandler default ResponseEntity handleAuthentication(final AuthenticationException e, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { return create(Response.Status.FORBIDDEN, e, request); } diff --git a/src/main/java/org/zalando/problem/spring/web/advice/validation/BaseValidationAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/validation/BaseValidationAdviceTrait.java index 3b29962f..e2047de8 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/validation/BaseValidationAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/validation/BaseValidationAdviceTrait.java @@ -1,7 +1,6 @@ package org.zalando.problem.spring.web.advice.validation; 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.spring.web.advice.AdviceTrait; @@ -31,8 +30,7 @@ default String formatFieldName(final String fieldName) { } default ResponseEntity newConstraintViolationProblem(final Throwable throwable, - final Collection stream, final NativeWebRequest request) - throws HttpMediaTypeNotAcceptableException { + final Collection stream, final NativeWebRequest request) { final StatusType status = defaultConstraintViolationStatus(); final List violations = stream.stream() diff --git a/src/main/java/org/zalando/problem/spring/web/advice/validation/ConstraintViolationAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/validation/ConstraintViolationAdviceTrait.java index 6edb0201..10345ccf 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/validation/ConstraintViolationAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/validation/ConstraintViolationAdviceTrait.java @@ -1,7 +1,6 @@ package org.zalando.problem.spring.web.advice.validation; 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.MoreStatus; @@ -29,7 +28,7 @@ default Violation createViolation(final ConstraintViolation violation) { @ExceptionHandler default ResponseEntity handleConstraintViolation( final ConstraintViolationException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { final List violations = exception.getConstraintViolations().stream() .map(this::createViolation) diff --git a/src/main/java/org/zalando/problem/spring/web/advice/validation/MethodArgumentNotValidAdviceTrait.java b/src/main/java/org/zalando/problem/spring/web/advice/validation/MethodArgumentNotValidAdviceTrait.java index 2165a705..107484ae 100644 --- a/src/main/java/org/zalando/problem/spring/web/advice/validation/MethodArgumentNotValidAdviceTrait.java +++ b/src/main/java/org/zalando/problem/spring/web/advice/validation/MethodArgumentNotValidAdviceTrait.java @@ -3,7 +3,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; -import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.context.request.NativeWebRequest; @@ -37,7 +36,7 @@ default Violation createViolation(final ObjectError error) { @ExceptionHandler default ResponseEntity handleMethodArgumentNotValid( final MethodArgumentNotValidException exception, - final NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { + final NativeWebRequest request) { final List violations = Stream.concat( exception.getBindingResult().getFieldErrors().stream().map(this::createViolation), diff --git a/src/test/java/org/zalando/problem/spring/web/advice/AdviceTraitTest.java b/src/test/java/org/zalando/problem/spring/web/advice/AdviceTraitTest.java index 72320b1d..955e0750 100644 --- a/src/test/java/org/zalando/problem/spring/web/advice/AdviceTraitTest.java +++ b/src/test/java/org/zalando/problem/spring/web/advice/AdviceTraitTest.java @@ -5,7 +5,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; @@ -13,7 +12,6 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import java.util.List; -import java.util.Optional; import java.util.stream.Stream; import static java.util.stream.Collectors.toList; @@ -38,7 +36,7 @@ public class AdviceTraitTest { }; @Test - public void buildsOnProblem() throws HttpMediaTypeNotAcceptableException { + public void buildsOnProblem() { final ThrowableProblem problem = mock(ThrowableProblem.class); when(problem.getStatus()).thenReturn(Status.RESET_CONTENT); @@ -50,7 +48,7 @@ public void buildsOnProblem() throws HttpMediaTypeNotAcceptableException { } @Test - public void buildsOnThrowable() throws HttpMediaTypeNotAcceptableException { + public void buildsOnThrowable() { final ResponseEntity result = unit.create(Status.RESET_CONTENT, new IllegalStateException("Message"), request()); @@ -61,7 +59,7 @@ public void buildsOnThrowable() throws HttpMediaTypeNotAcceptableException { } @Test - public void buildsOnMessage() throws HttpMediaTypeNotAcceptableException { + public void buildsOnMessage() { final ResponseEntity result = unit.create(Status.RESET_CONTENT, new IllegalStateException("Message"), request()); @@ -72,7 +70,7 @@ public void buildsOnMessage() throws HttpMediaTypeNotAcceptableException { } @Test - public void buildsIfIncludes() throws HttpMediaTypeNotAcceptableException { + public void buildsIfIncludes() { final String message = "Message"; final ResponseEntity result = unit.create(Status.RESET_CONTENT, @@ -86,7 +84,7 @@ public void buildsIfIncludes() throws HttpMediaTypeNotAcceptableException { } @Test - public void buildsStacktrace() throws HttpMediaTypeNotAcceptableException { + public void buildsStacktrace() { final Throwable throwable; try { @@ -163,7 +161,7 @@ private NullPointerException newNullPointer() { } @Test - public void mapsStatus() throws HttpMediaTypeNotAcceptableException { + public void mapsStatus() { final HttpStatus expected = HttpStatus.BAD_REQUEST; final Response.StatusType input = Status.BAD_REQUEST; final ResponseEntity entity = unit.create(input, @@ -173,7 +171,7 @@ public void mapsStatus() throws HttpMediaTypeNotAcceptableException { } @Test(expected = IllegalArgumentException.class) - public void throwsOnUnknownStatus() throws HttpMediaTypeNotAcceptableException { + public void throwsOnUnknownStatus() { final Response.StatusType input = mock(Response.StatusType.class); when(input.getReasonPhrase()).thenReturn("L33t"); when(input.getStatusCode()).thenReturn(1337); diff --git a/src/test/java/org/zalando/problem/spring/web/advice/ContentNegotiationTest.java b/src/test/java/org/zalando/problem/spring/web/advice/ContentNegotiationTest.java index 72e80ee4..f606b81e 100644 --- a/src/test/java/org/zalando/problem/spring/web/advice/ContentNegotiationTest.java +++ b/src/test/java/org/zalando/problem/spring/web/advice/ContentNegotiationTest.java @@ -3,6 +3,9 @@ import org.junit.Ignore; import org.junit.Test; +import org.springframework.web.HttpMediaTypeNotAcceptableException; + +import javax.servlet.ServletException; import static org.springframework.http.HttpMethod.GET; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; @@ -52,12 +55,19 @@ public void specificJsonGivesProblem() throws Exception { } @Test - public void nonJsonGivesEmpty() throws Exception { + public void nonJsonIsNotAcceptable() throws Exception { mvc().perform(request(GET, url) - .accept("application/atom+xml")) + .header("Accept", "application/atom+xml")) .andExpect(status().isNotAcceptable()) .andExpect(content().string("")) .andExpect(header().doesNotExist("Content-Type")); } + @Test(expected = ServletException.class) + public void invalidMediaTypeIsNotAcceptable() throws Exception { + mvc().perform(request(GET, url) + .header("Accept", "application/")); + + } + } diff --git a/src/test/java/org/zalando/problem/spring/web/advice/FallbackTest.java b/src/test/java/org/zalando/problem/spring/web/advice/FallbackTest.java index dcc5c831..c046f004 100644 --- a/src/test/java/org/zalando/problem/spring/web/advice/FallbackTest.java +++ b/src/test/java/org/zalando/problem/spring/web/advice/FallbackTest.java @@ -11,7 +11,9 @@ import static org.hamcrest.Matchers.is; import static org.springframework.http.HttpMethod.GET; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public final class FallbackTest implements AdviceTraitTesting { diff --git a/src/test/java/org/zalando/problem/spring/web/advice/SpringAdviceTraitTest.java b/src/test/java/org/zalando/problem/spring/web/advice/SpringAdviceTraitTest.java index b61943b8..988885ca 100644 --- a/src/test/java/org/zalando/problem/spring/web/advice/SpringAdviceTraitTest.java +++ b/src/test/java/org/zalando/problem/spring/web/advice/SpringAdviceTraitTest.java @@ -4,13 +4,10 @@ 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; -import java.util.Optional; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.containsString; @@ -29,7 +26,7 @@ public final class SpringAdviceTraitTest { }; @Test - public void buildsOnThrowable() throws HttpMediaTypeNotAcceptableException { + public void buildsOnThrowable() { final HttpStatusAdapter adapter = new HttpStatusAdapter(RESET_CONTENT); final ResponseEntity result = unit.create(HttpStatus.RESET_CONTENT, diff --git a/src/test/java/org/zalando/problem/spring/web/advice/example/ExampleRestController.java b/src/test/java/org/zalando/problem/spring/web/advice/example/ExampleRestController.java index d8af9669..d0642538 100644 --- a/src/test/java/org/zalando/problem/spring/web/advice/example/ExampleRestController.java +++ b/src/test/java/org/zalando/problem/spring/web/advice/example/ExampleRestController.java @@ -32,7 +32,9 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.math.BigDecimal; import java.time.OffsetDateTime; +import java.util.Map; import java.util.Set; import static org.springframework.http.MediaType.APPLICATION_JSON_VALUE; @@ -92,6 +94,21 @@ public ResponseEntity put(@RequestBody final String body) { return ResponseEntity.ok(body); } + @RequestMapping(path = "/json-object") + public void jsonObject(@RequestBody final Map body) { + + } + + @RequestMapping(path = "/json-decimal") + public void bigDecimal(@RequestBody final BigDecimal bigDecimal) { + + } + + @RequestMapping(path = "/json-user") + public void user(@RequestBody final User user) { + + } + @RequestMapping(path = "/handler-params", method = GET) public ResponseEntity params( @RequestParam("params1") final String[] params1, @@ -119,7 +136,7 @@ public ResponseEntity conversion( @RequestMapping(path = "/handler-invalid-param", method = POST) public void validRequestParam(@RequestBody final UserRequest user) { - @Hack("I couldn't make Spring throw this implicitely using annotations...") + @Hack("I couldn't make Spring throw this implicitly using annotations...") @Facepalm @OhNoYouDidnt final Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); diff --git a/src/test/java/org/zalando/problem/spring/web/advice/example/User.java b/src/test/java/org/zalando/problem/spring/web/advice/example/User.java new file mode 100644 index 00000000..b3c9cb07 --- /dev/null +++ b/src/test/java/org/zalando/problem/spring/web/advice/example/User.java @@ -0,0 +1,10 @@ +package org.zalando.problem.spring.web.advice.example; + +import lombok.Value; + +@Value +public class User { + + String name; + +} diff --git a/src/test/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTraitTest.java b/src/test/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTraitTest.java index 4c7eba52..2df50629 100644 --- a/src/test/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTraitTest.java +++ b/src/test/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTraitTest.java @@ -2,7 +2,6 @@ import org.junit.Test; import org.springframework.http.ResponseEntity; -import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.context.request.NativeWebRequest; import org.zalando.problem.Problem; @@ -36,7 +35,7 @@ public void methodNotAllowed() throws Exception { } @Test - public void noAllowIfNullAllowed() throws HttpMediaTypeNotAcceptableException { + public void noAllowIfNullAllowed() { final MethodNotAllowedAdviceTrait unit = new MethodNotAllowedAdviceTrait() { }; final ResponseEntity entity = unit.handleRequestMethodNotSupportedException( @@ -46,7 +45,7 @@ public void noAllowIfNullAllowed() throws HttpMediaTypeNotAcceptableException { } @Test - public void noAllowIfNoneAllowed() throws HttpMediaTypeNotAcceptableException { + public void noAllowIfNoneAllowed() { final MethodNotAllowedAdviceTrait unit = new MethodNotAllowedAdviceTrait() { }; final ResponseEntity entity = unit.handleRequestMethodNotSupportedException( diff --git a/src/test/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTraitTest.java b/src/test/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTraitTest.java index 2eb6a01e..008763c1 100644 --- a/src/test/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTraitTest.java +++ b/src/test/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTraitTest.java @@ -26,6 +26,61 @@ public void missingRequestBody() throws Exception { .andExpect(jsonPath("$.detail", containsString("request body is missing"))); } - // TODO: jackson stuff tests + @Test + public void malformedJsonRequestBody() throws Exception { + mvc().perform(request(PUT, "http://localhost/api/json-object") + .contentType("application/json") + .content("{")) + .andExpect(status().isBadRequest()) + .andExpect(header().string("Content-Type", is("application/problem+json"))) + .andExpect(jsonPath("$.type").doesNotExist()) + .andExpect(jsonPath("$.title", is("Bad Request"))) + .andExpect(jsonPath("$.status", is(400))) + .andExpect(jsonPath("$.detail", containsString("Unexpected end-of-input: expected close marker for OBJECT"))) + .andExpect(jsonPath("$.detail", containsString("line: 1, column: 0"))); + } + + @Test + public void invalidFormat() throws Exception { + mvc().perform(request(PUT, "http://localhost/api/json-decimal") + .contentType("application/json") + .content("\"foobar\"")) + .andExpect(status().isBadRequest()) + .andExpect(header().string("Content-Type", is("application/problem+json"))) + .andExpect(jsonPath("$.type").doesNotExist()) + .andExpect(jsonPath("$.title", is("Bad Request"))) + .andExpect(jsonPath("$.status", is(400))) + .andExpect(jsonPath("$.detail", containsString("Can not construct instance of java.math.BigDecimal from String value 'foobar': not a valid representation"))); + } + + @Test + public void noConstructor() throws Exception { + mvc().perform(request(PUT, "http://localhost/api/json-user") + .contentType("application/json") + .content("{}")) + .andExpect(status().isBadRequest()) + .andExpect(header().string("Content-Type", is("application/problem+json"))) + .andExpect(jsonPath("$.type").doesNotExist()) + .andExpect(jsonPath("$.title", is("Bad Request"))) + .andExpect(jsonPath("$.status", is(400))) + .andExpect(jsonPath("$.detail", containsString("Could not read document"))) + .andExpect(jsonPath("$.detail", containsString("No suitable constructor found for type [simple type, class org.zalando.problem.spring.web.advice.example.User]"))) + .andExpect(jsonPath("$.detail", containsString("can not instantiate from JSON object"))) + .andExpect(jsonPath("$.detail", containsString("missing default constructor or creator, or perhaps need to add/enable type information?"))); + } + + @Test + public void wrongJsonTypeRequestBody() throws Exception { + mvc().perform(request(PUT, "http://localhost/api/json-object") + .contentType("application/json") + .content("[]")) + .andExpect(status().isBadRequest()) + .andExpect(header().string("Content-Type", is("application/problem+json"))) + .andExpect(jsonPath("$.type").doesNotExist()) + .andExpect(jsonPath("$.title", is("Bad Request"))) + .andExpect(jsonPath("$.status", is(400))) + .andExpect(jsonPath("$.detail", containsString("Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token"))) + .andExpect(jsonPath("$.detail", containsString("line: 1, column: 1"))); + } } \ No newline at end of file