英文:
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("/{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): 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<ResponseEntity<?>> 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<ResponseEntity<String>>
; however, changing the method's return type to Mono<ResponseEntity<?>>
does not work either:
...
return Mono.just(ResponseEntity.badRequest().body(
MALFORMED_OBJECT_ID.replace("{}", accountId)));
as it gives an "incompatible type" error on the other return
statement:
error: incompatible types: Mono<ResponseEntity<Account>> cannot be converted to Mono<ResponseEntity<?>>
.switchIfEmpty(Mono.just(ResponseEntity.notFound().build()));
Obviously, changing the return type of the method to Mono<?>
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("/{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()));
}
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<ResponseEntity<Account>>
and there is no way to generate one from the error status (when adding the message body).
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论