Spring响应式WebFlux – 如何自定义BadRequest错误消息

huangapple go评论76阅读模式
英文:

Spring Reactive WebFlux - how to customize the BadRequest error message

问题

在我的请求处理程序中,如果传入的 accountId 无法转换为有效的 ObjectId,我想捕获错误并发送有意义的消息;然而,这样做会导致返回类型不兼容,我无法弄清如何实现这个相当简单的用例。

我的代码:

@GetMapping("/{accountId}")
public Mono<ResponseEntity<Account>> get(@PathVariable String accountId) {
    log.debug(GETTING_DATA_FOR_ACCOUNT, accountId);

    try {
        ObjectId id = new ObjectId(accountId);
        return repository.findById(id)
            .map(ResponseEntity::ok)
            .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
    } catch (IllegalArgumentException ex) {
        log.error(MALFORMED_OBJECT_ID, accountId);
        // TODO(marco): 找到一种返回自定义错误消息的方法。目前使用响应式 API 似乎不可能,因为使用 body(message) 会改变返回类型为不兼容的类型(而 Mono<ResponseEntity<?>> 似乎不起作用)。
        return Mono.just(ResponseEntity.badRequest().build());
    }
}

body(T body) 方法更改了返回的 Mono 类型,假设只发送一个 String,那么它是 Mono<ResponseEntity<String>>;然而,将方法的返回类型更改为 Mono<ResponseEntity<?>> 也不起作用:

...
return Mono.just(ResponseEntity.badRequest().body(
    MALFORMED_OBJECT_ID.replace("{}", accountId)));

因为它会在另一个 return 语句上产生一个“不兼容类型”的错误:

error: 不兼容的类型: Mono<ResponseEntity<Account>> 无法转换为 Mono<ResponseEntity<?>>
            .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));

显然,将方法的返回类型更改为 Mono<?> 将起作用,但此时响应是 ResponseEntity 的序列化 JSON,这不是我想要的。

我还尝试使用 onErrorXxxx() 方法,但在这里它们也不起作用,因为转换错误发生在计算 Flux 之前,我只会得到一个带有空消息的“常规” 400 错误。

我唯一想到的解决方法是向我的 Account 对象添加一个 message 字段,并返回该对象,但这实际上是一个可怕的破解方法。

英文:

In my request handler, if the passed-in accountId cannot be converted to a valid ObjectId I want to catch the error and send back a meaningful message; however, doing so causes the return type to be incompatible, and I cannot figure out how to achieve this pretty trivial use case.

My code:

  @GetMapping(&quot;/{accountId}&quot;)
  public Mono&lt;ResponseEntity&lt;Account&gt;&gt; get(@PathVariable String accountId) {
      log.debug(GETTING_DATA_FOR_ACCOUNT, accountId);

      try {
        ObjectId id = new ObjectId(accountId);
        return repository.findById(id)
            .map(ResponseEntity::ok)
            .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
      } catch (IllegalArgumentException ex) {
        log.error(MALFORMED_OBJECT_ID, accountId);
        // TODO(marco): find a way to return the custom error message. This seems to be currently
        //  impossible with the Reactive API, as using body(message) changes the return type to
        //  be incompatible (and Mono&lt;ResponseEntity&lt;?&gt;&gt; does not seem to cut it).
        return Mono.just(ResponseEntity.badRequest().build());
      }
  }

The body(T body) method changes the type of the returned Mono so that it is (assuming one just sends a String) a Mono&lt;ResponseEntity&lt;String&gt;&gt;; however, changing the method's return type to Mono&lt;ResponseEntity&lt;?&gt;&gt; does not work either:

        ...
        return Mono.just(ResponseEntity.badRequest().body(
            MALFORMED_OBJECT_ID.replace(&quot;{}&quot;, accountId)));

as it gives an "incompatible type" error on the other return statement:

error: incompatible types: Mono&lt;ResponseEntity&lt;Account&gt;&gt; cannot be converted to Mono&lt;ResponseEntity&lt;?&gt;&gt;
            .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));

Obviously, changing the return type of the method to Mono&lt;?&gt; would work, but the response then is the serialized JSON of the ResponseEntity which is NOT what I want.

I have also tried using the onErrorXxxx() methods, but they do not work here either, as the conversion error happens even before the Flux is computed, and I just get a "vanilla" 400 error with an empty message.

The only way I can think of working around this would be to add a message field to my Account object and return that one, but it's genuinely a horrible hack.

答案1

得分: 2

@thomas-andolf的回答帮助我找到了实际的解决方案。

对于将来遇到类似情况的其他人,以下是我实际解决这个谜题的方法(是的,你仍然需要try/catch来拦截ObjectId构造函数抛出的错误):

  @GetMapping("/{accountId}")
  public Mono<ResponseEntity<Account>> get(@PathVariable String accountId) {
    return Mono.just(accountId)
        .map(acctId -> {
          try {
            return new ObjectId(accountId);
          } catch (IllegalArgumentException ex) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
                MALFORMED_OBJECT_ID));
          }
        })
        .flatMap(repository::findById)
        .map(ResponseEntity::ok)
        .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
  }

要实际在返回的主体中看到message,您需要在application.properties中添加server.error.include-message=always(参见这里)。

在这里使用onError()不起作用(我尝试过所有变体),因为它需要一个Mono<ResponseEntity<Account>>,并且没有办法从错误状态生成这样一个对象(当添加消息正文时)。

英文:

@thomas-andolf's answer helped me figure out the actual solution.

For anyone stumbling upon this in future, here is how I actually solved the puzzle (and, yes, you still need the try/catch to intercept the error thrown by the ObjectId constructor):

  @GetMapping(&quot;/{accountId}&quot;)
  public Mono&lt;ResponseEntity&lt;Account&gt;&gt; get(@PathVariable String accountId) {
    return Mono.just(accountId)
        .map(acctId -&gt; {
          try {
            return new ObjectId(accountId);
          } catch (IllegalArgumentException ex) {
            throw new ResponseStatusException(HttpStatus.BAD_REQUEST,
                MALFORMED_OBJECT_ID));
          }
        })
        .flatMap(repository::findById)
        .map(ResponseEntity::ok)
        .switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
  }

To actually see the message in the returned body, you will need to add server.error.include-message=always in application.properties (see here).

Using onError() won't work here (I did try that, in all its variants) as it requires a Mono&lt;ResponseEntity&lt;Account&gt;&gt; and there is no way to generate one from the error status (when adding the message body).

huangapple
  • 本文由 发表于 2020年9月1日 15:18:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/63683064.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定