Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Spring Specific Exception Handling #867

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 28 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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

Expand Down
23 changes: 1 addition & 22 deletions problem-spring-web-autoconfigure/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,34 +48,13 @@
<optional>true</optional>
</dependency>

<!-- test-->
<!-- test-->


<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -59,10 +57,8 @@
* @see ProblemHandling
* @see CustomAdviceTrait
* @see GeneralAdviceTrait
* @see HttpAdviceTrait
* @see IOAdviceTrait
* @see NetworkAdviceTrait
* @see RoutingAdviceTrait
* @see ValidationAdviceTrait
*/
@API(status = STABLE)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

}
Original file line number Diff line number Diff line change
@@ -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<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatusCode status, WebRequest request) {
List<Violation> 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<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatusCode status,
WebRequest request) {
List<Violation> 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<Object> 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<Object> handleValidationException(ConstraintViolationException ex, WebRequest request)
throws Exception {
List<Violation> 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<Object> 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<String, Object> entry : ex.getParameters().entrySet()) {
body.setProperty(entry.getKey(), entry.getValue());
}

return handleExceptionInternal(ex, body, HttpHeaders.EMPTY, status, request);
}


}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,5 @@
*/
@API(status = STABLE)
public interface IOAdviceTrait extends
MessageNotReadableAdviceTrait,
MultipartAdviceTrait,
TypeMismatchAdviceTrait {
MultipartAdviceTrait {
}
Loading