如何在 Spring Boot Data Rest 控制器中以 RFC-7807 格式返回验证错误?

发布于 2025-01-17 02:44:35 字数 1799 浏览 1 评论 0原文

我有一个 Spring 存储库,

@RepositoryRestResource
public interface MongoIntegrationTokenRepository extends MongoRepository<IntegrationToken, String>, CrudRepository<IntegrationToken, String> {}

我添加了验证配置以添加验证支持,并且我的实体具有验证注释:

@Configuration
class MyRestMvcConfiguration implements RepositoryRestConfigurer {

    private final LocalValidatorFactoryBean localValidatorFactoryBean;

    @Autowired
    public MyRestMvcConfiguration(LocalValidatorFactoryBean localValidatorFactoryBean) {
        this.localValidatorFactoryBean = localValidatorFactoryBean;
    }

    @Override
    public void configureValidatingRepositoryEventListener(
            ValidatingRepositoryEventListener validatingRepositoryEventListener) {
        validatingRepositoryEventListener.addValidator("beforeCreate", localValidatorFactoryBean);
        validatingRepositoryEventListener.addValidator("beforeSave", localValidatorFactoryBean);
        validatingRepositoryEventListener.addValidator("beforeSave", localValidatorFactoryBean);
    }
}

运行创建操作时,如果存在任何验证错误,实体创建将失败,但 JSON 响应不包含任何内容错误详细信息。

使用无效数据发送到我的端点只会返回:

{
  "message": "Server Error",
  "details": [
    "Validation failed"
  ]
}

我想返回 中的验证错误RFC7807 格式。 应该可以通过 Spring HATEOAS 或 Zalando 的这个流行库实现 https://github.com/zalando/problem-spring-web 但是我不确定它们需要如何连接或应该使用哪种方法。

I have a Spring repository

@RepositoryRestResource
public interface MongoIntegrationTokenRepository extends MongoRepository<IntegrationToken, String>, CrudRepository<IntegrationToken, String> {}

I've added the validation configuration to add validation support and my entity has the validation annotations:

@Configuration
class MyRestMvcConfiguration implements RepositoryRestConfigurer {

    private final LocalValidatorFactoryBean localValidatorFactoryBean;

    @Autowired
    public MyRestMvcConfiguration(LocalValidatorFactoryBean localValidatorFactoryBean) {
        this.localValidatorFactoryBean = localValidatorFactoryBean;
    }

    @Override
    public void configureValidatingRepositoryEventListener(
            ValidatingRepositoryEventListener validatingRepositoryEventListener) {
        validatingRepositoryEventListener.addValidator("beforeCreate", localValidatorFactoryBean);
        validatingRepositoryEventListener.addValidator("beforeSave", localValidatorFactoryBean);
        validatingRepositoryEventListener.addValidator("beforeSave", localValidatorFactoryBean);
    }
}

When running the create operation, if there are any validation errors, the entity creation fails but the JSON response doesn't contain any errors details.

A POST to my endpoint with invalid data simply returns:

{
  "message": "Server Error",
  "details": [
    "Validation failed"
  ]
}

I'd like to return the validation errors in the RFC7807 format. This should be possible via Spring HATEOAS or by this popular library by Zalando https://github.com/zalando/problem-spring-web but I'm unsure how they need to be wired in or which approach should be used.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清风疏影 2025-01-24 02:44:35

在 Spring 6 和 Spring Boot 3 中,现在无需使用额外的库就可以实现这一点。

您可以返回 ProblemDetail

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(BookmarkNotFoundException.class)
    ProblemDetail handleBookmarkNotFoundException(BookmarkNotFoundException e) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
        problemDetail.setTitle("Bookmark Not Found");
        problemDetail.setType(URI.create("https://api.bookmarks.com/errors/not-found"));
        problemDetail.setProperty("errorCategory", "Generic");
        problemDetail.setProperty("timestamp", Instant.now());
        return problemDetail;
    }
}

ErrorResponse

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(BookmarkNotFoundException.class)
    ErrorResponse handleBookmarkNotFoundException(BookmarkNotFoundException e) {
        return ErrorResponse.builder(e, HttpStatus.NOT_FOUND, e.getMessage())
                .title("Bookmark not found")
                .type(URI.create("https://api.bookmarks.com/errors/not-found"))
                .property("errorCategory", "Generic")
                .property("timestamp", Instant.now())
                .build();
    }
}

如果您不需要自定义错误处理,似乎您也可以在 application.properties 中激活 ProblemDetails:

spring.mvc.problemdetails.enabled=true

获取信息和示例来自这篇文章: https://www.sivalabs.in/spring-boot-3-error-reporting-using-problem-details/

With Spring 6 and Spring Boot 3 this is now possible without using an additional library.

You can either return a ProblemDetail

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(BookmarkNotFoundException.class)
    ProblemDetail handleBookmarkNotFoundException(BookmarkNotFoundException e) {
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, e.getMessage());
        problemDetail.setTitle("Bookmark Not Found");
        problemDetail.setType(URI.create("https://api.bookmarks.com/errors/not-found"));
        problemDetail.setProperty("errorCategory", "Generic");
        problemDetail.setProperty("timestamp", Instant.now());
        return problemDetail;
    }
}

or an ErrorResponse

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(BookmarkNotFoundException.class)
    ErrorResponse handleBookmarkNotFoundException(BookmarkNotFoundException e) {
        return ErrorResponse.builder(e, HttpStatus.NOT_FOUND, e.getMessage())
                .title("Bookmark not found")
                .type(URI.create("https://api.bookmarks.com/errors/not-found"))
                .property("errorCategory", "Generic")
                .property("timestamp", Instant.now())
                .build();
    }
}

It seems you can also activate ProblemDetails in the application.properties, if you do not need custom error handling:

spring.mvc.problemdetails.enabled=true

Information and examples are taken from this article: https://www.sivalabs.in/spring-boot-3-error-reporting-using-problem-details/

白昼 2025-01-24 02:44:35

我在 Github 上找到了这个孤独的例子。 https://github.com/marciovmartins/problem-spring-web-starter-expanded/blob/aed5825c958fad93f4aaad022689958926cf3b4a/src/main/kotlin/com/github/marciovmartins/problem/spring/web/expanded/ProblemExceptionHandler.kt 并将其重写为爪哇。这似乎可以做到。

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.data.rest.core.RepositoryConstraintViolationException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
import org.zalando.problem.ThrowableProblem;
import org.zalando.problem.spring.web.advice.ProblemHandling;
import org.zalando.problem.violations.Violation;

@ControllerAdvice
public class ProblemControllerAdvice implements ProblemHandling {

    @ExceptionHandler
    public ResponseEntity<Problem> handleRepositoryConstraintViolationException(RepositoryConstraintViolationException exception, NativeWebRequest request) {
        List<Violation> violationList = exception.getErrors().getAllErrors()
                .stream()
                .map(objectError -> {
                    if (objectError instanceof FieldError) {
                        return new Violation(((FieldError) objectError).getField(), objectError.getDefaultMessage());
                    }
                    return new Violation(null, objectError.getDefaultMessage());
                })
                .collect(Collectors.toList());

        ThrowableProblem problem = Problem.builder()
                .withTitle("Constraint Violation")
                .withStatus(defaultConstraintViolationStatus())
                .with("violations", violationList)
                .build();
        return create(problem, request);
    }

}

I found this lone example on Github. https://github.com/marciovmartins/problem-spring-web-starter-expanded/blob/aed5825c958fad93f4aaad022689958926cf3b4a/src/main/kotlin/com/github/marciovmartins/problem/spring/web/expanded/ProblemExceptionHandler.kt and rewrote it in Java. This seems to do it.

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.data.rest.core.RepositoryConstraintViolationException;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.NativeWebRequest;
import org.zalando.problem.Problem;
import org.zalando.problem.ThrowableProblem;
import org.zalando.problem.spring.web.advice.ProblemHandling;
import org.zalando.problem.violations.Violation;

@ControllerAdvice
public class ProblemControllerAdvice implements ProblemHandling {

    @ExceptionHandler
    public ResponseEntity<Problem> handleRepositoryConstraintViolationException(RepositoryConstraintViolationException exception, NativeWebRequest request) {
        List<Violation> violationList = exception.getErrors().getAllErrors()
                .stream()
                .map(objectError -> {
                    if (objectError instanceof FieldError) {
                        return new Violation(((FieldError) objectError).getField(), objectError.getDefaultMessage());
                    }
                    return new Violation(null, objectError.getDefaultMessage());
                })
                .collect(Collectors.toList());

        ThrowableProblem problem = Problem.builder()
                .withTitle("Constraint Violation")
                .withStatus(defaultConstraintViolationStatus())
                .with("violations", violationList)
                .build();
        return create(problem, request);
    }

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文