package eu.bitfield.recipes.api; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.MessageSourceResolvable; import org.springframework.core.MethodParameter; import org.springframework.http.ProblemDetail; import org.springframework.util.MultiValueMap; import org.springframework.validation.FieldError; import org.springframework.validation.ObjectError; import org.springframework.validation.method.ParameterValidationResult; import org.springframework.web.ErrorResponse; import org.springframework.web.ErrorResponseException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.support.WebExchangeBindException; import org.springframework.web.method.annotation.HandlerMethodValidationException; import org.springframework.web.server.ServerWebInputException; import java.lang.reflect.Executable; import java.lang.reflect.Parameter; import java.util.List; import static eu.bitfield.recipes.util.CollectionUtils.*; import static org.springframework.http.HttpStatus.*; public interface ErrorResponseHandling { Logger log = LoggerFactory.getLogger(ErrorResponseHandling.class); private static void addMethodParameter(ProblemDetail problemDetail, MethodParameter methodParam) { Executable executable = methodParam.getExecutable(); Parameter param = methodParam.getParameter(); String method = executable.toGenericString(); String argument = param.getType().getCanonicalName() + " " + param.getName(); problemDetail.setProperty("method", method); problemDetail.setProperty("argument", argument); } @ExceptionHandler default ErrorResponse handleError(HandlerMethodValidationException e) { log.debug(""" Handling {}: method: {} errors: {}""", e.getClass().getName(), e.getMethod(), e.getAllErrors(), e); ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(BAD_REQUEST, e.getReason()); String method = e.getMethod().toGenericString(); problemDetail.setProperty("method", method); MultiValueMap errors = multiValueMap(); for (ParameterValidationResult result : e.getParameterValidationResults()) { Parameter param = result.getMethodParameter().getParameter(); String errorKey = param.getName(); List errorValues = result.getResolvableErrors() .stream() .map(MessageSourceResolvable::getDefaultMessage) .toList(); errors.addAll(errorKey, errorValues); } problemDetail.setProperty("errors", errors); return ErrorResponse.builder(e, problemDetail).build(); } @ExceptionHandler default ErrorResponse handleError(WebExchangeBindException e) { List objectErrors = e.getAllErrors(); log.debug(""" Handling {}: methodParameter: {} errors: {}""", e.getClass().getName(), e.getMethodParameter(), e.getAllErrors(), e); ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(BAD_REQUEST, e.getReason()); MethodParameter methodParam = e.getMethodParameter(); if (methodParam != null) addMethodParameter(problemDetail, methodParam); MultiValueMap errors = multiValueMap(); for (ObjectError objectError : objectErrors) { String errorKey = objectError.getObjectName(); if (objectError instanceof FieldError fieldError) { errorKey += "." + fieldError.getField(); } errors.add(errorKey, objectError.getDefaultMessage()); } problemDetail.setProperty("errors", errors); return ErrorResponse.builder(e, problemDetail).build(); } @ExceptionHandler default ErrorResponse handleError(ServerWebInputException e) { log.debug(""" Handling {}: methodParameter: {}""", e.getClass().getName(), e.getMethodParameter(), e); ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(BAD_REQUEST, e.getReason()); MethodParameter methodParam = e.getMethodParameter(); if (methodParam != null) addMethodParameter(problemDetail, methodParam); return ErrorResponse.builder(e, problemDetail).build(); } @ExceptionHandler default ErrorResponse handleError(ErrorResponseException e) { log.debug("Handling {}", e.getClass().getName(), e); return e; } }