diff --git a/src/main/java/com/litoralregas/vpnprovisioner/common/exception/ApiErrorResponse.java b/src/main/java/com/litoralregas/vpnprovisioner/common/exception/ApiErrorResponse.java new file mode 100644 index 0000000..13d1fa0 --- /dev/null +++ b/src/main/java/com/litoralregas/vpnprovisioner/common/exception/ApiErrorResponse.java @@ -0,0 +1,12 @@ +package com.litoralregas.vpnprovisioner.common.exception; + +import java.time.Instant; + +public record ApiErrorResponse( + Instant timestamp, + int status, + String error, + String message, + String path +) { +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/common/exception/GlobalExceptionHandler.java b/src/main/java/com/litoralregas/vpnprovisioner/common/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..fff5f96 --- /dev/null +++ b/src/main/java/com/litoralregas/vpnprovisioner/common/exception/GlobalExceptionHandler.java @@ -0,0 +1,64 @@ +package com.litoralregas.vpnprovisioner.common.exception; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.*; + +import java.time.Instant; +import java.util.stream.Collectors; + +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(ResourceNotFoundException.class) + @ResponseStatus(HttpStatus.NOT_FOUND) + public ApiErrorResponse handleNotFound( + ResourceNotFoundException exception, + HttpServletRequest request + ) { + return new ApiErrorResponse( + Instant.now(), + HttpStatus.NOT_FOUND.value(), + "Not Found", + exception.getMessage(), + request.getRequestURI() + ); + } + + @ExceptionHandler(MethodArgumentNotValidException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiErrorResponse handleValidation( + MethodArgumentNotValidException exception, + HttpServletRequest request + ) { + String message = exception.getBindingResult() + .getFieldErrors() + .stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining(", ")); + + return new ApiErrorResponse( + Instant.now(), + HttpStatus.BAD_REQUEST.value(), + "Bad Request", + message, + request.getRequestURI() + ); + } + + @ExceptionHandler(IllegalArgumentException.class) + @ResponseStatus(HttpStatus.BAD_REQUEST) + public ApiErrorResponse handleIllegalArgument( + IllegalArgumentException exception, + HttpServletRequest request + ) { + return new ApiErrorResponse( + Instant.now(), + HttpStatus.BAD_REQUEST.value(), + "Bad Request", + exception.getMessage(), + request.getRequestURI() + ); + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/common/exception/ResourceNotFoundException.java b/src/main/java/com/litoralregas/vpnprovisioner/common/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..8f4dffb --- /dev/null +++ b/src/main/java/com/litoralregas/vpnprovisioner/common/exception/ResourceNotFoundException.java @@ -0,0 +1,8 @@ +package com.litoralregas.vpnprovisioner.common.exception; + +public class ResourceNotFoundException extends RuntimeException { + + public ResourceNotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterService.java b/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterService.java index 952832e..ad4e719 100644 --- a/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterService.java +++ b/src/main/java/com/litoralregas/vpnprovisioner/router/service/RouterService.java @@ -5,6 +5,7 @@ import com.litoralregas.vpnprovisioner.router.dto.RouterResponse; import com.litoralregas.vpnprovisioner.router.dto.UpdateRouterRequest; import com.litoralregas.vpnprovisioner.router.entity.Router; import com.litoralregas.vpnprovisioner.router.repository.RouterRepository; +import com.litoralregas.vpnprovisioner.common.exception.ResourceNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -43,7 +44,7 @@ public class RouterService { @Transactional(readOnly = true) public RouterResponse findById(UUID id) { Router router = routerRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("Router not found: " + id)); + .orElseThrow(() -> new ResourceNotFoundException("Router not found: " + id)); return toResponse(router); } @@ -51,7 +52,7 @@ public class RouterService { @Transactional public RouterResponse update(UUID id, UpdateRouterRequest request) { Router router = routerRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("Router not found: " + id)); + .orElseThrow(() -> new ResourceNotFoundException("Router not found: " + id)); router.updateDetails( request.name(), @@ -65,7 +66,7 @@ public class RouterService { @Transactional public void delete(UUID id) { Router router = routerRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException("Router not found: " + id)); + .orElseThrow(() -> new ResourceNotFoundException("Router not found: " + id)); routerRepository.delete(router); }