diff --git a/.gitignore b/.gitignore index 6003fdb1..f210ce2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,35 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + ### IntelliJ ### +*.iws *.iml +*.ipr .idea/ -### Maven ### -target/ -/.mvn/wrapper/*.jar +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ *.log diff --git a/problem-spring-web-autoconfigure/pom.xml b/problem-spring-web-autoconfigure/pom.xml index 0c5a0997..9b3ff69e 100644 --- a/problem-spring-web-autoconfigure/pom.xml +++ b/problem-spring-web-autoconfigure/pom.xml @@ -48,34 +48,13 @@ true - + - - - org.junit.jupiter - junit-jupiter-engine - test - - - org.mockito - mockito-core - test - com.jayway.jsonpath json-path-assert test - - org.junit.jupiter - junit-jupiter-params - test - - - org.springframework - spring-test - test - org.springframework.boot spring-boot-starter-test diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/AdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/AdviceTrait.java index c5b73304..4b1925ae 100644 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/AdviceTrait.java +++ b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/AdviceTrait.java @@ -22,10 +22,8 @@ import org.zalando.problem.spring.common.AdviceTraits; import org.zalando.problem.spring.web.advice.custom.CustomAdviceTrait; import org.zalando.problem.spring.web.advice.general.GeneralAdviceTrait; -import org.zalando.problem.spring.web.advice.http.HttpAdviceTrait; import org.zalando.problem.spring.web.advice.io.IOAdviceTrait; import org.zalando.problem.spring.web.advice.network.NetworkAdviceTrait; -import org.zalando.problem.spring.web.advice.routing.RoutingAdviceTrait; import org.zalando.problem.spring.web.advice.validation.ValidationAdviceTrait; import jakarta.servlet.http.HttpServletResponse; @@ -59,10 +57,8 @@ * @see ProblemHandling * @see CustomAdviceTrait * @see GeneralAdviceTrait - * @see HttpAdviceTrait * @see IOAdviceTrait * @see NetworkAdviceTrait - * @see RoutingAdviceTrait * @see ValidationAdviceTrait */ @API(status = STABLE) diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/ProblemHandling.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/ProblemHandling.java index 79a91efe..2f14bc22 100644 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/ProblemHandling.java +++ b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/ProblemHandling.java @@ -3,10 +3,8 @@ import org.apiguardian.api.API; import org.zalando.problem.spring.web.advice.custom.CustomAdviceTrait; import org.zalando.problem.spring.web.advice.general.GeneralAdviceTrait; -import org.zalando.problem.spring.web.advice.http.HttpAdviceTrait; import org.zalando.problem.spring.web.advice.io.IOAdviceTrait; import org.zalando.problem.spring.web.advice.network.NetworkAdviceTrait; -import org.zalando.problem.spring.web.advice.routing.RoutingAdviceTrait; import org.zalando.problem.spring.web.advice.validation.ValidationAdviceTrait; import static org.apiguardian.api.API.Status.STABLE; @@ -23,18 +21,14 @@ * @see AdviceTrait * @see CustomAdviceTrait * @see GeneralAdviceTrait - * @see HttpAdviceTrait * @see IOAdviceTrait - * @see RoutingAdviceTrait * @see ValidationAdviceTrait */ @API(status = STABLE) public interface ProblemHandling extends GeneralAdviceTrait, - HttpAdviceTrait, IOAdviceTrait, NetworkAdviceTrait, - RoutingAdviceTrait, ValidationAdviceTrait { } diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/ZalandoProblemExceptionHandler.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/ZalandoProblemExceptionHandler.java new file mode 100644 index 00000000..0e5b8009 --- /dev/null +++ b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/ZalandoProblemExceptionHandler.java @@ -0,0 +1,122 @@ +package org.zalando.problem.spring.web.advice; + +import java.util.List; +import java.util.Map; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ProblemDetail; +import org.springframework.http.ResponseEntity; +import org.springframework.validation.BindException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.context.request.WebRequest; +import org.springframework.web.multipart.MultipartException; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; +import org.zalando.problem.ThrowableProblem; +import org.zalando.problem.spring.web.advice.validation.ConstraintViolationAdviceTrait; +import org.zalando.problem.spring.web.advice.validation.ValidationAdviceTrait; +import org.zalando.problem.violations.ConstraintViolationProblem; +import org.zalando.problem.violations.Violation; + +import jakarta.validation.ConstraintViolationException; + +/** + * A controller advice that integrates with Spring's Problem JSON support and + * also overwrites functionality where Zalando Problem JSON does better handling + * especially when failing with validation errors. + */ +@ControllerAdvice +public class ZalandoProblemExceptionHandler extends ResponseEntityExceptionHandler { + + private ValidationAdviceTrait validationTrait = new ValidationAdviceTrait() { + }; + + private ConstraintViolationAdviceTrait cveTrait = new ConstraintViolationAdviceTrait() { + }; + + /** + * Overridden to add violations to the problem details. + */ + @Override + protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, + HttpHeaders headers, HttpStatusCode status, WebRequest request) { + List violations = validationTrait.createViolations(ex.getBindingResult()); + + ProblemDetail body = ex.getBody(); + body.setType(ConstraintViolationProblem.TYPE); + body.setTitle("Constraint Violation"); + body.setProperty("violations", violations); + + return handleExceptionInternal(ex, body, headers, status, request); + } + + /** + * Overridden to add violations to the problem details. + */ + @Override + @Deprecated(since = "6.0", forRemoval = true) + protected ResponseEntity handleBindException(BindException ex, HttpHeaders headers, HttpStatusCode status, + WebRequest request) { + List violations = validationTrait.createViolations(ex.getBindingResult()); + + ProblemDetail body = ProblemDetail.forStatusAndDetail(status, "Failed to bind request"); + body.setType(ConstraintViolationProblem.TYPE); + body.setTitle("Constraint Violation"); + body.setProperty("violations", violations); + + return handleExceptionInternal(ex, body, headers, status, request); + } + + /** + * Handles Spring's MultipartException. + */ + @ExceptionHandler + public ResponseEntity handleValidationException(MultipartException ex, WebRequest request) + throws Exception { + HttpStatus status = HttpStatus.BAD_REQUEST; + + ProblemDetail body = ProblemDetail.forStatusAndDetail(status, "Current request is not a multipart request"); + + return handleExceptionInternal(ex, body, HttpHeaders.EMPTY, status, request); + } + + /** + * Handles Jakarta ConstraintViolationExceptions thrown within the application code and not within the Spring Framework. + */ + @ExceptionHandler + public ResponseEntity handleValidationException(ConstraintViolationException ex, WebRequest request) + throws Exception { + List violations = cveTrait.createViolations(ex.getConstraintViolations()); + + ProblemDetail body = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Invalid request content."); + body.setType(ConstraintViolationProblem.TYPE); + body.setTitle("Constraint Violation"); + body.setProperty("violations", violations); + + return handleExceptionInternal(ex, body, HttpHeaders.EMPTY, HttpStatus.BAD_REQUEST, request); + } + + /** + * Handles ThrowableProblem exceptions to map them to Spring Problem JSON support. + */ + @ExceptionHandler + public ResponseEntity handleThrowableProblemException(ThrowableProblem ex, WebRequest request) + throws Exception { + HttpStatus status = HttpStatus.valueOf(ex.getStatus().getStatusCode()); + + ProblemDetail body = ProblemDetail.forStatusAndDetail(status, ex.getDetail()); + body.setType(ex.getType()); + body.setTitle(ex.getTitle()); + body.setInstance(ex.getInstance()); + for (Map.Entry entry : ex.getParameters().entrySet()) { + body.setProperty(entry.getKey(), entry.getValue()); + } + + return handleExceptionInternal(ex, body, HttpHeaders.EMPTY, status, request); + } + + +} \ No newline at end of file diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/HttpAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/HttpAdviceTrait.java deleted file mode 100644 index 6563366a..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/HttpAdviceTrait.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.zalando.problem.spring.web.advice.http; - -import org.apiguardian.api.API; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see AdviceTrait - */ -@API(status = STABLE) -public interface HttpAdviceTrait extends - NotAcceptableAdviceTrait, - UnsupportedMediaTypeAdviceTrait, - MethodNotAllowedAdviceTrait { - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTrait.java deleted file mode 100644 index d0ba6ff4..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTrait.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.zalando.problem.spring.web.advice.http; - -import com.google.gag.annotation.remark.Facepalm; -import com.google.gag.annotation.remark.WTF; -import org.apiguardian.api.API; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import javax.annotation.Nullable; - -import static java.util.Objects.requireNonNull; -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface MethodNotAllowedAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleRequestMethodNotSupportedException( - final HttpRequestMethodNotSupportedException exception, - final NativeWebRequest request) { - - @WTF - @Facepalm("Nullable arrays... great work from Spring :/") - @Nullable final String[] methods = exception.getSupportedMethods(); - - if (methods == null || methods.length == 0) { - return create(Status.METHOD_NOT_ALLOWED, exception, request); - } - - final HttpHeaders headers = new HttpHeaders(); - headers.setAllow(requireNonNull(exception.getSupportedHttpMethods())); - - return create(Status.METHOD_NOT_ALLOWED, exception, request, headers); - } - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTrait.java deleted file mode 100644 index a383df47..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTrait.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.zalando.problem.spring.web.advice.http; - -import org.apiguardian.api.API; -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; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see HttpMediaTypeNotAcceptableException - * @see Status#NOT_ACCEPTABLE - */ -@API(status = STABLE) -public interface NotAcceptableAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleMediaTypeNotAcceptable( - final HttpMediaTypeNotAcceptableException exception, - final NativeWebRequest request) { - return create(Status.NOT_ACCEPTABLE, exception, request); - } - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTrait.java deleted file mode 100644 index 0e948a85..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTrait.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.zalando.problem.spring.web.advice.http; - -import org.apiguardian.api.API; -import org.springframework.http.HttpHeaders; -import org.springframework.http.ResponseEntity; -import org.springframework.web.HttpMediaTypeNotSupportedException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see HttpMediaTypeNotSupportedException - * @see Status#UNSUPPORTED_MEDIA_TYPE - */ -@API(status = STABLE) -public interface UnsupportedMediaTypeAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleMediaTypeNotSupportedException( - final HttpMediaTypeNotSupportedException exception, - final NativeWebRequest request) { - - final HttpHeaders headers = new HttpHeaders(); - headers.setAccept(exception.getSupportedMediaTypes()); - - return create(Status.UNSUPPORTED_MEDIA_TYPE, exception, request, headers); - } - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/package-info.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/package-info.java deleted file mode 100644 index c3650d44..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/http/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -@ParametersAreNonnullByDefault -package org.zalando.problem.spring.web.advice.http; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/IOAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/IOAdviceTrait.java index f7a7f5b5..1afa1470 100644 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/IOAdviceTrait.java +++ b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/IOAdviceTrait.java @@ -10,7 +10,5 @@ */ @API(status = STABLE) public interface IOAdviceTrait extends - MessageNotReadableAdviceTrait, - MultipartAdviceTrait, - TypeMismatchAdviceTrait { + MultipartAdviceTrait { } diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTrait.java deleted file mode 100644 index 1161e240..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTrait.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.zalando.problem.spring.web.advice.io; - -import org.apiguardian.api.API; -import org.springframework.http.ResponseEntity; -import org.springframework.http.converter.HttpMessageNotReadableException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see HttpMessageNotReadableException - * @see Status#BAD_REQUEST - */ -@API(status = STABLE) -public interface MessageNotReadableAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleMessageNotReadableException( - final HttpMessageNotReadableException exception, - final NativeWebRequest request) { - return create(Status.BAD_REQUEST, exception, request); - } - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMismatchAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMismatchAdviceTrait.java deleted file mode 100644 index 70adba1c..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMismatchAdviceTrait.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.zalando.problem.spring.web.advice.io; - -import org.apiguardian.api.API; -import org.springframework.beans.TypeMismatchException; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see TypeMismatchException - * @see Status#BAD_REQUEST - */ -@API(status = STABLE) -public interface TypeMismatchAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleTypeMismatch( - final TypeMismatchException exception, - final NativeWebRequest request) { - return create(Status.BAD_REQUEST, exception, request); - } -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMistmatchAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMistmatchAdviceTrait.java deleted file mode 100644 index 2fd0b83a..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/io/TypeMistmatchAdviceTrait.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.zalando.problem.spring.web.advice.io; - -import org.apiguardian.api.API; - -import static org.apiguardian.api.API.Status.DEPRECATED; - -@API(status = DEPRECATED) -@Deprecated -@SuppressWarnings("SpellCheckingInspection") -public interface TypeMistmatchAdviceTrait extends TypeMismatchAdviceTrait { -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTrait.java deleted file mode 100644 index 38c7afdb..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTrait.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.apiguardian.api.API; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.MissingServletRequestParameterException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see MissingServletRequestParameterException - * @see Status#BAD_REQUEST - */ -@API(status = STABLE) -public interface MissingServletRequestParameterAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleMissingServletRequestParameter( - final MissingServletRequestParameterException exception, - final NativeWebRequest request) { - return create(Status.BAD_REQUEST, exception, request); - } - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTrait.java deleted file mode 100644 index 64d2c6c5..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTrait.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.apiguardian.api.API; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.multipart.support.MissingServletRequestPartException; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see MissingServletRequestPartException - * @see Status#BAD_REQUEST - */ -@API(status = STABLE) -public interface MissingServletRequestPartAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleMissingServletRequestPart( - final MissingServletRequestPartException exception, - final NativeWebRequest request) { - return create(Status.BAD_REQUEST, exception, request); - } - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTrait.java deleted file mode 100644 index 15dcae24..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTrait.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.apiguardian.api.API; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.servlet.NoHandlerFoundException; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -/** - * Transforms {@link NoHandlerFoundException NoHandlerFoundExceptions} into {@link Status#NOT_FOUND not-found} - * {@link Problem problems}. - *

- * Note: This requires {@link DispatcherServlet#setThrowExceptionIfNoHandlerFound(boolean)} being set - * to true. - *

- * - * @see NoHandlerFoundException - * @see Status#NOT_FOUND - * @see DispatcherServlet#setThrowExceptionIfNoHandlerFound(boolean) - */ -@API(status = STABLE) -public interface NoHandlerFoundAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleNoHandlerFound( - final NoHandlerFoundException exception, - final NativeWebRequest request) { - return create(Status.NOT_FOUND, exception, request); - } - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/RoutingAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/RoutingAdviceTrait.java deleted file mode 100644 index f3631398..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/RoutingAdviceTrait.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.apiguardian.api.API; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see AdviceTrait - */ -@API(status = STABLE) -public interface RoutingAdviceTrait extends - MissingServletRequestParameterAdviceTrait, - MissingServletRequestPartAdviceTrait, - NoHandlerFoundAdviceTrait, - ServletRequestBindingAdviceTrait { -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTrait.java deleted file mode 100644 index 064efbae..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTrait.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.apiguardian.api.API; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.ServletRequestBindingException; -import org.springframework.web.bind.annotation.ExceptionHandler; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.Problem; -import org.zalando.problem.Status; -import org.zalando.problem.spring.web.advice.AdviceTrait; - -import static org.apiguardian.api.API.Status.INTERNAL; -import static org.apiguardian.api.API.Status.STABLE; - -/** - * @see ServletRequestBindingException - * @see Status#BAD_REQUEST - */ -@API(status = STABLE) -public interface ServletRequestBindingAdviceTrait extends AdviceTrait { - - @API(status = INTERNAL) - @ExceptionHandler - default ResponseEntity handleServletRequestBinding( - final ServletRequestBindingException exception, - final NativeWebRequest request) { - return create(Status.BAD_REQUEST, exception, request); - } - -} diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/package-info.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/package-info.java deleted file mode 100644 index 1ed00ec1..00000000 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/routing/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -@ParametersAreNonnullByDefault -package org.zalando.problem.spring.web.advice.routing; - -import javax.annotation.ParametersAreNonnullByDefault; - diff --git a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/validation/ConstraintViolationAdviceTrait.java b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/validation/ConstraintViolationAdviceTrait.java index c866d374..30fc97eb 100644 --- a/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/validation/ConstraintViolationAdviceTrait.java +++ b/problem-spring-web/src/main/java/org/zalando/problem/spring/web/advice/validation/ConstraintViolationAdviceTrait.java @@ -11,6 +11,7 @@ import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; import java.util.List; +import java.util.Set; import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.INTERNAL; @@ -32,14 +33,16 @@ default ResponseEntity handleConstraintViolation( final ConstraintViolationException exception, final NativeWebRequest request) { - final List violations = exception.getConstraintViolations().stream() - .map(this::createViolation) - .collect(toList()); + final List violations = createViolations(exception.getConstraintViolations()); return newConstraintViolationProblem(exception, violations, request); } - default Violation createViolation(final ConstraintViolation violation) { + default List createViolations(Set> violations) { + return violations.stream().map(this::createViolation).collect(toList()); + } + + default Violation createViolation(final ConstraintViolation violation) { return new Violation(formatFieldName(violation.getPropertyPath().toString()), violation.getMessage()); } diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/AdviceTraitTesting.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/AdviceTraitTesting.java index 1e01d6ca..27a637bc 100644 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/AdviceTraitTesting.java +++ b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/AdviceTraitTesting.java @@ -2,7 +2,9 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.http.converter.ByteArrayHttpMessageConverter; +import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.http.converter.json.ProblemDetailJacksonMixin; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -14,6 +16,8 @@ import static java.util.Collections.singletonList; import static org.springframework.http.MediaType.APPLICATION_JSON; +import org.springframework.http.ProblemDetail; + public interface AdviceTraitTesting { default ProblemHandling unit() { @@ -25,9 +29,10 @@ default MockMvc mvc() { return MockMvcBuilders .standaloneSetup(new ExampleRestController()) - .setContentNegotiationManager(new ContentNegotiationManager(singletonList( - new FixedContentNegotiationStrategy(APPLICATION_JSON)))) - .setControllerAdvice(unit()) + //.setContentNegotiationManager(new ContentNegotiationManager(singletonList( + // new FixedContentNegotiationStrategy(APPLICATION_JSON)))) + //.setControllerAdvice(unit()) + .setControllerAdvice(new ZalandoProblemExceptionHandler()) .setMessageConverters( new MappingJackson2HttpMessageConverter(mapper), new MappingJackson2XmlHttpMessageConverter(), @@ -36,7 +41,7 @@ default MockMvc mvc() { } default ObjectMapper mapper() { - return new ObjectMapper().registerModule(new ProblemModule()); + return Jackson2ObjectMapperBuilder.json().build(); } } diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/general/ProblemAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/general/ProblemAdviceTraitTest.java index cda8b849..555fc2de 100644 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/general/ProblemAdviceTraitTest.java +++ b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/general/ProblemAdviceTraitTest.java @@ -17,7 +17,7 @@ void throwableProblem() throws Exception { mvc().perform(request(GET, "http://localhost/api/handler-problem")) .andExpect(status().isConflict()) .andExpect(header().string("Content-Type", is("application/problem+json"))) - .andExpect(jsonPath("$.type").doesNotExist()) + .andExpect(jsonPath("$.type", is("about:blank"))) .andExpect(jsonPath("$.title", is("Expected"))) .andExpect(jsonPath("$.status", is(409))) .andExpect(jsonPath("$.detail", is("Nothing out of the ordinary"))); diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/general/ResponseStatusAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/general/ResponseStatusAdviceTraitTest.java index 3d0dfc6f..2fc44b11 100644 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/general/ResponseStatusAdviceTraitTest.java +++ b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/general/ResponseStatusAdviceTraitTest.java @@ -17,7 +17,7 @@ void throwableProblem() throws Exception { mvc().perform(request(GET, "http://localhost/api/handler-throwable-extended")) .andExpect(status().isNotImplemented()) .andExpect(header().string("Content-Type", is("application/problem+json"))) - .andExpect(jsonPath("$.type").doesNotExist()) + .andExpect(jsonPath("$.type", is("about:blank"))) .andExpect(jsonPath("$.title", is("Not Implemented"))) .andExpect(jsonPath("$.status", is(501))); } diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTraitTest.java deleted file mode 100644 index 78d887d2..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/MethodNotAllowedAdviceTraitTest.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.zalando.problem.spring.web.advice.http; - -import org.junit.jupiter.api.Test; -import org.springframework.http.ResponseEntity; -import org.springframework.web.HttpRequestMethodNotSupportedException; -import org.springframework.web.context.request.NativeWebRequest; -import org.zalando.problem.Problem; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.collection.IsMapContaining.hasKey; -import static org.mockito.Mockito.mock; -import static org.springframework.http.HttpMethod.POST; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class MethodNotAllowedAdviceTraitTest implements AdviceTraitTesting { - - @Test - public void methodNotAllowed() throws Exception { - mvc().perform(request(POST, "http://localhost/api/handler-problem") - .accept("application/x.bla+json", "application/problem+json")) - .andExpect(status().isMethodNotAllowed()) - .andExpect(header().string("Content-Type", is("application/problem+json"))) - .andExpect(header().string("Allow", is("GET"))) - .andExpect(jsonPath("$.type").doesNotExist()) - .andExpect(jsonPath("$.title", is("Method Not Allowed"))) - .andExpect(jsonPath("$.status", is(405))) - .andExpect(jsonPath("$.detail", containsString("not supported"))); - } - - @Test - void noAllowIfNullAllowed() { - final MethodNotAllowedAdviceTrait unit = new MethodNotAllowedAdviceTrait() { - }; - final ResponseEntity entity = unit.handleRequestMethodNotSupportedException( - new HttpRequestMethodNotSupportedException("non allowed"), mock(NativeWebRequest.class)); - - assertThat(entity.getHeaders(), not(hasKey("Allow"))); - } - - @Test - void noAllowIfNoneAllowed() { - final MethodNotAllowedAdviceTrait unit = new MethodNotAllowedAdviceTrait() { - }; - final ResponseEntity entity = unit.handleRequestMethodNotSupportedException( - new HttpRequestMethodNotSupportedException("non allowed", new String[]{}), mock(NativeWebRequest.class)); - - assertThat(entity.getHeaders(), not(hasKey("Allow"))); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTraitTest.java deleted file mode 100644 index 0cd5a3a6..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/NotAcceptableAdviceTraitTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.zalando.problem.spring.web.advice.http; - -import org.junit.jupiter.api.Test; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import static org.hamcrest.Matchers.containsString; -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.content; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class NotAcceptableAdviceTraitTest implements AdviceTraitTesting { - - @Test - void notAcceptable() throws Exception { - mvc().perform(request(GET, "http://localhost/api/handler-ok") - .accept("application/x.vnd.specific+json")) - .andExpect(status().isNotAcceptable()) - .andExpect(content().contentType("application/problem+json")) - .andExpect(jsonPath("$.type").doesNotExist()) - .andExpect(jsonPath("$.title", is("Not Acceptable"))) - .andExpect(jsonPath("$.status", is(406))) - .andExpect(jsonPath("$.detail", containsString("No acceptable representation"))); - } - - @Test - void notAcceptableNoProblem() throws Exception { - mvc().perform(request(GET, "http://localhost/api/handler-ok") - .accept("image/png")) - .andExpect(status().isNotAcceptable()) - .andExpect(content().contentType("application/problem+json")) - .andExpect(jsonPath("$.type").doesNotExist()) - .andExpect(jsonPath("$.title", is("Not Acceptable"))) - .andExpect(jsonPath("$.status", is(406))) - .andExpect(jsonPath("$.detail", containsString("No acceptable representation"))); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTraitTest.java deleted file mode 100644 index 4bdafa3f..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/http/UnsupportedMediaTypeAdviceTraitTest.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.zalando.problem.spring.web.advice.http; - -import org.junit.jupiter.api.Test; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.springframework.http.HttpMethod.PUT; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class UnsupportedMediaTypeAdviceTraitTest implements AdviceTraitTesting { - - @Test - void unsupportedMediaType() throws Exception { - mvc().perform(request(PUT, "http://localhost/api/handler-put") - .contentType("application/atom+xml")) - .andExpect(status().isUnsupportedMediaType()) - .andExpect(header().string("Content-Type", is("application/problem+json"))) - .andExpect(header().string("Accept", containsString("application/json"))) - .andExpect(jsonPath("$.type").doesNotExist()) - .andExpect(jsonPath("$.title", is("Unsupported Media Type"))) - .andExpect(jsonPath("$.status", is(415))) - .andExpect(jsonPath("$.detail", containsString("application/atom+xml"))); - } - - @Test - void acceptHeaderIfSupported() throws Exception { - mvc().perform(request(PUT, "http://localhost/api/handler-put") - .contentType("application/atom+xml")) - .andExpect(status().isUnsupportedMediaType()) - .andExpect(header().string("Accept", containsString("application/json"))) - .andExpect(header().string("Accept", containsString("application/xml"))); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTraitTest.java deleted file mode 100644 index d3e54253..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/MessageNotReadableAdviceTraitTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.zalando.problem.spring.web.advice.io; - -import org.junit.jupiter.api.Test; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.springframework.http.HttpMethod.PUT; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class MessageNotReadableAdviceTraitTest implements AdviceTraitTesting { - - @Test - void missingRequestBody() throws Exception { - mvc().perform(request(PUT, "http://localhost/api/handler-put") - .contentType("application/json")) - .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("request body is missing"))); - } - - @Test - 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"))); - } - - @Test - 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("java.math.BigDecimal"))) - .andExpect(jsonPath("$.detail", containsString("foobar"))); - } - - @Test - 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("org.zalando.problem.spring.web.advice.example.User"))); - } - - @Test - 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("java.util.LinkedHashMap"))) - .andExpect(jsonPath("$.detail", containsString("START_ARRAY"))); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/MultipartAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/MultipartAdviceTraitTest.java index bbb63a83..88117df2 100644 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/MultipartAdviceTraitTest.java +++ b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/MultipartAdviceTraitTest.java @@ -18,7 +18,7 @@ void multipart() throws Exception { mvc().perform(request(POST, "http://localhost/api/handler-multipart")) .andExpect(status().isBadRequest()) .andExpect(header().string("Content-Type", is("application/problem+json"))) - .andExpect(jsonPath("$.type").doesNotExist()) + .andExpect(jsonPath("$.type", is("about:blank"))) .andExpect(jsonPath("$.title", is("Bad Request"))) .andExpect(jsonPath("$.status", is(400))) .andExpect(jsonPath("$.detail", containsString("not a multipart request"))); diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/TypeMismatchAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/TypeMismatchAdviceTraitTest.java deleted file mode 100644 index 5723d193..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/io/TypeMismatchAdviceTraitTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.zalando.problem.spring.web.advice.io; - -import org.junit.jupiter.api.Test; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import static org.hamcrest.Matchers.containsString; -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.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class TypeMismatchAdviceTraitTest implements AdviceTraitTesting { - - @Test - void typeMismatch() throws Exception { - mvc().perform(request(GET, "http://localhost/api/handler-conversion?dateTime=abc")) - .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("Failed to convert"))); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTraitTest.java deleted file mode 100644 index fe1562fe..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestParameterAdviceTraitTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.junit.jupiter.api.Test; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import static org.hamcrest.Matchers.containsString; -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.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class MissingServletRequestParameterAdviceTraitTest implements AdviceTraitTesting { - - @Test - void missingServletRequestParameter() throws Exception { - mvc().perform(request(GET, "http://localhost/api/handler-params")) - .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("params1"))); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTraitTest.java deleted file mode 100644 index 01d43774..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/MissingServletRequestPartAdviceTraitTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.junit.jupiter.api.Test; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.is; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class MissingServletRequestPartAdviceTraitTest implements AdviceTraitTesting { - - @Test - void handlesMultipart() throws Exception { - mvc().perform(multipart("http://localhost/api/handler-multipart") - .file("payload1", new byte[]{0x1})) - .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("payload2"))); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTraitTest.java deleted file mode 100644 index 5e8a7bf5..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/NoHandlerFoundAdviceTraitTest.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.junit.jupiter.api.Test; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.web.servlet.DispatcherServlet; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import java.lang.reflect.Field; - -import static org.hamcrest.Matchers.containsString; -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.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class NoHandlerFoundAdviceTraitTest implements AdviceTraitTesting { - - @Test - void noHandlerInController() throws Exception { - final MockMvc mvc = mvc(); - throwExceptionIfNoHandlerFound(mvc); - - mvc.perform(request(GET, "http://localhost/api/no-handler")) - .andExpect(status().isNotFound()) - .andExpect(header().string("Content-Type", is("application/problem+json"))) - .andExpect(jsonPath("$.type").doesNotExist()) - .andExpect(jsonPath("$.title", is("Not Found"))) - .andExpect(jsonPath("$.status", is(404))) - .andExpect(jsonPath("$.detail", containsString("No endpoint GET"))); - } - - @Test - void noHandler() throws Exception { - final MockMvc mvc = mvc(); - throwExceptionIfNoHandlerFound(mvc); - - mvc.perform(request(GET, "http://localhost/no-handler")) - .andExpect(status().isNotFound()) - .andExpect(header().string("Content-Type", is("application/problem+json"))) - .andExpect(jsonPath("$.type").doesNotExist()) - .andExpect(jsonPath("$.title", is("Not Found"))) - .andExpect(jsonPath("$.status", is(404))) - .andExpect(jsonPath("$.detail", containsString("No endpoint GET"))); - } - - private void throwExceptionIfNoHandlerFound(final MockMvc mvc) throws NoSuchFieldException, IllegalAccessException { - final Field field = MockMvc.class.getDeclaredField("servlet"); - field.setAccessible(true); - final DispatcherServlet servlet = (DispatcherServlet) field.get(mvc); - servlet.setThrowExceptionIfNoHandlerFound(true); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTraitTest.java deleted file mode 100644 index fbc63b82..00000000 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/routing/ServletRequestBindingAdviceTraitTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.zalando.problem.spring.web.advice.routing; - -import org.junit.jupiter.api.Test; -import org.zalando.problem.spring.web.advice.AdviceTraitTesting; - -import static org.hamcrest.Matchers.containsString; -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.header; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -final class ServletRequestBindingAdviceTraitTest implements AdviceTraitTesting { - - @Test - void servletRequestBinding() throws Exception { - mvc().perform(request(GET, "http://localhost/api/handler-headers")) - .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("X-Custom-Header"))); - } - -} diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/validation/BindAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/validation/BindAdviceTraitTest.java index 04731a75..7595fa3b 100644 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/validation/BindAdviceTraitTest.java +++ b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/validation/BindAdviceTraitTest.java @@ -22,10 +22,10 @@ void invalidRequestQueryParams() throws Exception { .andExpect(jsonPath("$.title", is("Constraint Violation"))) .andExpect(jsonPath("$.status", is(400))) .andExpect(jsonPath("$.violations", hasSize(2))) - .andExpect(jsonPath("$.violations[0].field", is("page"))) - .andExpect(jsonPath("$.violations[0].message", is("must be greater than or equal to 0"))) - .andExpect(jsonPath("$.violations[1].field", is("size"))) - .andExpect(jsonPath("$.violations[1].message", is("must be greater than or equal to 1"))); + .andExpect(jsonPath("$.violations[1].field", is("page"))) + .andExpect(jsonPath("$.violations[1].message", is("must be greater than or equal to 0"))) + .andExpect(jsonPath("$.violations[0].field", is("size"))) + .andExpect(jsonPath("$.violations[0].message", is("must be greater than or equal to 1"))); } } diff --git a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/validation/MethodArgumentNotValidAdviceTraitTest.java b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/validation/MethodArgumentNotValidAdviceTraitTest.java index 487fdbff..b121bdd7 100644 --- a/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/validation/MethodArgumentNotValidAdviceTraitTest.java +++ b/problem-spring-web/src/test/java/org/zalando/problem/spring/web/advice/validation/MethodArgumentNotValidAdviceTraitTest.java @@ -40,7 +40,7 @@ void invalidRequestBody() throws Exception { .andExpect(jsonPath("$.title", is("Constraint Violation"))) .andExpect(jsonPath("$.status", is(400))) .andExpect(jsonPath("$.violations", hasSize(1))) - .andExpect(jsonPath("$.violations[0].field", is("user_request"))) + .andExpect(jsonPath("$.violations[0].field", is("userRequest"))) .andExpect(jsonPath("$.violations[0].message", is("must not be called Bob"))); }