ErrorResponse.java
package com.wilzwert.myjobs.infrastructure.api.rest.dto;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.wilzwert.myjobs.core.domain.model.user.exception.LoginException;
import com.wilzwert.myjobs.core.domain.shared.exception.DomainException;
import com.wilzwert.myjobs.core.domain.shared.exception.EntityAlreadyExistsException;
import com.wilzwert.myjobs.core.domain.shared.exception.EntityNotFoundException;
import com.wilzwert.myjobs.core.domain.shared.exception.ValidationException;
import com.wilzwert.myjobs.core.domain.shared.validation.ErrorCode;
import com.wilzwert.myjobs.core.domain.shared.validation.ValidationError;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.apache.coyote.BadRequestException;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.HttpMediaTypeException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.server.ResponseStatusException;
import java.time.Instant;
import java.util.*;
@Data
@AllArgsConstructor
public class ErrorResponse {
@JsonIgnore
private HttpStatusCode httpStatusCode;
private String status;
private String message;
private Map<String, List<ValidationErrorResponse>> errors;
private long timestamp;
public static <E extends EntityAlreadyExistsException> ErrorResponse fromException(E ex) {
return build(HttpStatus.CONFLICT, ex.getErrorCode().name());
}
public static <E extends EntityNotFoundException> ErrorResponse fromException(E ex) {
return build(HttpStatus.NOT_FOUND, ex.getErrorCode().name());
}
public static ErrorResponse fromException(ValidationException ex) {
Map<String, List<ValidationErrorResponse>> errors = new HashMap<>();
for (ValidationError error : ex.getFlatErrors()) {
errors.computeIfAbsent(error.field(), k -> new ArrayList<>()).add(new ValidationErrorResponse(error.code().name(), error.details()));
}
return build(HttpStatus.UNPROCESSABLE_ENTITY, ErrorCode.VALIDATION_FAILED.name(), errors);
}
public static ErrorResponse fromException(DomainException ex) {
return build(HttpStatus.BAD_REQUEST, ex.getErrorCode().name());
}
public static ErrorResponse fromException(ResponseStatusException ex) {
return build(ex.getStatusCode(), ex.getReason());
}
public static ErrorResponse fromException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
List<FieldError> fieldErrors = bindingResult.getFieldErrors();
Map<String, List<ValidationErrorResponse>> errors = new HashMap<>();
for(FieldError fieldError : fieldErrors){
errors.computeIfAbsent(fieldError.getField(), k -> new ArrayList<>()).add(new ValidationErrorResponse(fieldError.getDefaultMessage() != null ? fieldError.getDefaultMessage() : ErrorCode.UNEXPECTED_ERROR.name()));
}
return build(ex.getStatusCode(), ErrorCode.VALIDATION_FAILED.name(), errors);
}
public static ErrorResponse fromException(ConstraintViolationException ex) {
Map<String, List<ValidationErrorResponse>> errors = new HashMap<>();
for(ConstraintViolation<?> violation : ex.getConstraintViolations()){
errors.computeIfAbsent(violation.getPropertyPath().toString(), k -> new ArrayList<>()).add(new ValidationErrorResponse(violation.getMessage() != null ? violation.getMessage() : ErrorCode.UNEXPECTED_ERROR.name()));
}
return build(HttpStatus.UNPROCESSABLE_ENTITY, ErrorCode.VALIDATION_FAILED.name(), errors);
}
public static ErrorResponse fromException(LoginException ex) {
return build(HttpStatus.UNAUTHORIZED, "Access denied");
}
public static ErrorResponse fromException(AccessDeniedException ex) {
return build(HttpStatus.UNAUTHORIZED, "Access denied");
}
public static ErrorResponse fromException(HttpRequestMethodNotSupportedException ex) {
return build(ex.getStatusCode(), "Unsupported method");
}
public static ErrorResponse fromException(NumberFormatException ex) {
return build(HttpStatus.BAD_REQUEST, ex.getMessage());
}
public static ErrorResponse fromException(BadRequestException ex) {
return build(HttpStatus.BAD_REQUEST, ex.getMessage());
}
public static ErrorResponse fromException(HttpMessageNotReadableException ex) {
Throwable cause = ex.getCause();
if (cause instanceof JsonMappingException formatEx) {
String fieldName = "";
if (!formatEx.getPath().isEmpty()) {
fieldName = formatEx.getPath().getFirst().getFieldName();
}
if(!fieldName.isEmpty()) {
Map<String, List<ValidationErrorResponse>> errors = new HashMap<>();
errors.put(fieldName, List.of(new ValidationErrorResponse(ErrorCode.INVALID_VALUE.name())));
return build(HttpStatus.BAD_REQUEST, ErrorCode.VALIDATION_FAILED.name(), errors);
}
return build(HttpStatus.BAD_REQUEST, ErrorCode.VALIDATION_FAILED.name());
}
return build(HttpStatus.BAD_REQUEST, ex.getMessage());
}
public static ErrorResponse fromException(MissingServletRequestParameterException ex) {
return build(HttpStatus.BAD_REQUEST, ex.getMessage());
}
public static ErrorResponse fromException(IllegalArgumentException ex) {
return build(HttpStatus.BAD_REQUEST, ex.getMessage());
}
public static ErrorResponse fromException(HttpMediaTypeException ex) {
return build(HttpStatus.UNSUPPORTED_MEDIA_TYPE, ex.getMessage());
}
public static ErrorResponse fromException(HttpClientErrorException ex) {
return build(ex.getStatusCode(), ex.getMessage());
}
public static ErrorResponse fromException(Exception ex) {
return build(HttpStatus.INTERNAL_SERVER_ERROR, ErrorCode.UNEXPECTED_ERROR.name());
}
private static ErrorResponse build(HttpStatusCode status, String message) {
return new ErrorResponse(status, String.valueOf(status.value()), message, Collections.emptyMap(), Instant.now().toEpochMilli());
}
private static ErrorResponse build(HttpStatusCode status, String message, Map<String, List<ValidationErrorResponse>> errors) {
return new ErrorResponse(status, String.valueOf(status.value()), message, errors, Instant.now().toEpochMilli());
}
}