From 06656507ca295b761f43ef868c6f866c917a8e10 Mon Sep 17 00:00:00 2001 From: henridb <henridb@stud.ntnu.no> Date: Wed, 17 Apr 2024 09:38:07 +0200 Subject: [PATCH] feat: add exception classes and handler class --- .../exception/ExceptionResponse.java | 29 ++++ .../exception/GlobalExceptionHandler.java | 129 ++++++++++++++++++ .../auth/InvalidCredentialsException.java | 14 ++ .../user/PermissionDeniedException.java | 14 ++ .../user/UserAlreadyExistsException.java | 14 ++ .../exception/user/UserNotFoundException.java | 21 +++ .../user/UsernameTakenException.java | 14 ++ 7 files changed, 235 insertions(+) create mode 100644 src/main/java/no/ntnu/idi/stud/savingsapp/exception/ExceptionResponse.java create mode 100644 src/main/java/no/ntnu/idi/stud/savingsapp/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/no/ntnu/idi/stud/savingsapp/exception/auth/InvalidCredentialsException.java create mode 100644 src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/PermissionDeniedException.java create mode 100644 src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UserAlreadyExistsException.java create mode 100644 src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UserNotFoundException.java create mode 100644 src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UsernameTakenException.java diff --git a/src/main/java/no/ntnu/idi/stud/savingsapp/exception/ExceptionResponse.java b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/ExceptionResponse.java new file mode 100644 index 0000000..157d8f5 --- /dev/null +++ b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/ExceptionResponse.java @@ -0,0 +1,29 @@ +package no.ntnu.idi.stud.savingsapp.exception; + +import lombok.AllArgsConstructor; +import lombok.Data; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; + +/** + * Represents an exception response containing status code and message. + */ +@Data +@AllArgsConstructor +public class ExceptionResponse { + + private int status; + private String message; + + /** + * Creates a ResponseEntity containing the exception response. + * + * @param status The HTTP status code. + * @param message The message describing the exception. + * @return A ResponseEntity containing the exception response. + */ + protected static ResponseEntity<ExceptionResponse> toResponseEntity(HttpStatus status, String message) { + ExceptionResponse response = new ExceptionResponse(status.value(), message); + return ResponseEntity.status(status).body(response); + } +} diff --git a/src/main/java/no/ntnu/idi/stud/savingsapp/exception/GlobalExceptionHandler.java b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..007bdeb --- /dev/null +++ b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/GlobalExceptionHandler.java @@ -0,0 +1,129 @@ +package no.ntnu.idi.stud.savingsapp.exception; + +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import no.ntnu.idi.stud.savingsapp.exception.auth.InvalidCredentialsException; +import no.ntnu.idi.stud.savingsapp.exception.user.PermissionDeniedException; +import no.ntnu.idi.stud.savingsapp.exception.user.UserAlreadyExistsException; +import no.ntnu.idi.stud.savingsapp.exception.user.UserNotFoundException; +import no.ntnu.idi.stud.savingsapp.exception.user.UsernameTakenException; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.authentication.CredentialsExpiredException; +import org.springframework.security.core.AuthenticationException; +import org.springframework.validation.FieldError; +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.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +/** + * Global exception handler that handles various exceptions thrown by the application. + * Customizes the response for different types of exceptions. + */ +@ControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + /** + * Handles validation errors for method arguments. + * + * @param e The MethodArgumentNotValidException containing validation errors. + * @param headers The headers for the response. + * @param status The HTTP status code. + * @param request The current web request. + * @return A ResponseEntity containing the error response. + */ + @Override + protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e, HttpHeaders headers, HttpStatusCode status, WebRequest request) { + String error = e.getMessage(); + FieldError fieldError = e.getFieldError(); + if (fieldError != null) { + error = fieldError.getDefaultMessage(); + } + ExceptionResponse response = new ExceptionResponse(HttpStatus.BAD_REQUEST.value(), error); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + + /** + * Handles exceptions by returning an {@link HttpStatus#BAD_REQUEST} response. + * + * @param e The exception. + * @return A ResponseEntity containing the error response. + */ + @ExceptionHandler({ConstraintViolationException.class}) + public ResponseEntity<ExceptionResponse> handleBadRequest(Exception e) { + String error = e.getMessage(); + if (e instanceof ConstraintViolationException constraintViolationException) { + for (ConstraintViolation<?> violation : constraintViolationException.getConstraintViolations()) { + error = violation.getMessage(); + break; + } + } + return ExceptionResponse.toResponseEntity(HttpStatus.BAD_REQUEST, error); + } + + /** + * Handles exceptions by returning an {@link HttpStatus#UNAUTHORIZED} response. + * + * @param e The exception. + * @return A ResponseEntity containing the error response. + */ + @ExceptionHandler({InvalidCredentialsException.class, AccessDeniedException.class, + AuthenticationException.class, CredentialsExpiredException.class, PermissionDeniedException.class}) + public ResponseEntity<ExceptionResponse> handleUnauthorized(Exception e) { + return ExceptionResponse.toResponseEntity(HttpStatus.UNAUTHORIZED, e.getMessage()); + } + + /** + * Handles exceptions by returning an {@link HttpStatus#CONFLICT} response. + * + * @param e The exception. + * @return A ResponseEntity containing the error response. + */ + @ExceptionHandler({UserAlreadyExistsException.class, UsernameTakenException.class}) + public ResponseEntity<ExceptionResponse> handleConflict(Exception e) { + return ExceptionResponse.toResponseEntity(HttpStatus.CONFLICT, e.getMessage()); + } + + /** + * Handles exceptions by returning an {@link HttpStatus#NOT_FOUND} response. + * + * @param e The exception. + * @return A ResponseEntity containing the error response. + */ + @ExceptionHandler({UserNotFoundException.class}) + public ResponseEntity<ExceptionResponse> handleNotFound(Exception e) { + return ExceptionResponse.toResponseEntity(HttpStatus.NOT_FOUND, e.getMessage()); + } + + /** + * Handles remaining exceptions by returning an {@link HttpStatus#INTERNAL_SERVER_ERROR} response. + * + * @param e The exception. + * @return A ResponseEntity containing the error response. + */ + @ExceptionHandler(Exception.class) + public ResponseEntity<ExceptionResponse> handleRemainingExceptions(Exception e) { + return ExceptionResponse.toResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR, e.getMessage()); + } + + /** + * Customizes the default exception handler. + * + * @param e The exception. + * @param body The body of the response. + * @param headers The headers for the response. + * @param statusCode The HTTP status code. + * @param request The current web request. + * @return A ResponseEntity containing the error response. + */ + @Override + protected ResponseEntity<Object> handleExceptionInternal(Exception e, Object body, HttpHeaders headers, HttpStatusCode statusCode, WebRequest request) { + ExceptionResponse response = new ExceptionResponse(statusCode.value(), e.getMessage()); + return super.handleExceptionInternal(e, response, headers, statusCode, request); + } +} diff --git a/src/main/java/no/ntnu/idi/stud/savingsapp/exception/auth/InvalidCredentialsException.java b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/auth/InvalidCredentialsException.java new file mode 100644 index 0000000..0950054 --- /dev/null +++ b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/auth/InvalidCredentialsException.java @@ -0,0 +1,14 @@ +package no.ntnu.idi.stud.savingsapp.exception.auth; + +/** + * Exception thrown when authentication fails due to invalid credentials. + */ +public class InvalidCredentialsException extends RuntimeException { + + /** + * Constructs an InvalidCredentialsException with the default message. + */ + public InvalidCredentialsException() { + super("Invalid credentials"); + } +} diff --git a/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/PermissionDeniedException.java b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/PermissionDeniedException.java new file mode 100644 index 0000000..9d7d389 --- /dev/null +++ b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/PermissionDeniedException.java @@ -0,0 +1,14 @@ +package no.ntnu.idi.stud.savingsapp.exception.user; + +/** + * Exception thrown when a user attempts an action they don't have permission to. + */ +public final class PermissionDeniedException extends RuntimeException { + + /** + * Constructs a PermissionDeniedException with the default message. + */ + public PermissionDeniedException() { + super("Permission denied"); + } +} diff --git a/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UserAlreadyExistsException.java b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UserAlreadyExistsException.java new file mode 100644 index 0000000..c5cc150 --- /dev/null +++ b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UserAlreadyExistsException.java @@ -0,0 +1,14 @@ +package no.ntnu.idi.stud.savingsapp.exception.user; + +/** + * Exception thrown when attempting to create a user that already exists in the system. + */ +public final class UserAlreadyExistsException extends RuntimeException { + + /** + * Constructs a UserAlreadyExistsException with the default message. + */ + public UserAlreadyExistsException() { + super("User already exists"); + } +} diff --git a/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UserNotFoundException.java b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UserNotFoundException.java new file mode 100644 index 0000000..b6e2a7e --- /dev/null +++ b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UserNotFoundException.java @@ -0,0 +1,21 @@ +package no.ntnu.idi.stud.savingsapp.exception.user; + +/** + * Exception thrown when attempting to retrieve a user that does not exist in the system. + */ +public final class UserNotFoundException extends RuntimeException { + + /** + * Constructs a UserNotFoundException with the default message. + */ + public UserNotFoundException() { + super("User not found"); + } + + /** + * Constructs a UserNotFoundException with the default message. + */ + public UserNotFoundException(String string) { + super(string); + } +} diff --git a/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UsernameTakenException.java b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UsernameTakenException.java new file mode 100644 index 0000000..8c3bdfc --- /dev/null +++ b/src/main/java/no/ntnu/idi/stud/savingsapp/exception/user/UsernameTakenException.java @@ -0,0 +1,14 @@ +package no.ntnu.idi.stud.savingsapp.exception.user; + +/** + * Exception thrown when attempting to create a user that already exists in the system. + */ +public final class UsernameTakenException extends RuntimeException { + + /** + * Constructs a UserAlreadyExistsException with the default message. + */ + public UsernameTakenException() { + super("Username is taken"); + } +} -- GitLab