What is the best way to validate request in a Spring Webflux functional application

Hantsy picture Hantsy · Dec 30, 2017 · Viewed 7.5k times · Source

In a traditional web application it is easy to validate the request body in the controller method, eg.

ResponseEntity create(@Valid @ResponseBody Post post) {
} 

If it is a MVC application, we can gather the errors by injecting a BindingResult, and decide if there is some validation errors from the input form.

In the pages, there are some helpers existed for Freemarker and Thymeleaf to display the messages.

But when I come to Webflux and try to use RouterFunction to define the routing in the applications. For example,

Mono<ServerResponse> create(ServerRequest req) {
    return req.bodyToMono(Post.class)
    .flatMap { this.posts.save(it) }
    .flatMap { ServerResponse.created(URI.create("/posts/".concat(it.getId()))).build() }
}

@Bean
RouterFunction<ServerResponse> routes(PostHandler postController) {
    return route(GET("/posts"), postController.&all)
    .andRoute(POST("/posts"), postController.&create)
    .andRoute(GET("/posts/{id}"), postController.&get)
    .andRoute(PUT("/posts/{id}"), postController.&update)
    .andRoute(DELETE("/posts/{id}"), postController.&delete)
}

A possible approach is converting the request data(Mono or Flux) to blocking and injecting a Validator and validate them manually.

But I think the codes will look a little ugly.

How to process the validation of request body or form data gracefully?

Is there a better to validate the request body or form data and do not lost the functional and reactive features for both WEB(rendering a view) and REST applications?

Answer

Toshiaki Maki picture Toshiaki Maki · Sep 9, 2018

I've developed "Yet Another Validator" for this porpose.

https://github.com/making/yavi

It would be great if YAVI could meet your expectation.

Validation code will look like following:

static RouterFunction<ServerResponse> routes() {
    return route(POST("/"), req -> req.bodyToMono(User.class) //
            .flatMap(body -> validator.validateToEither(body) //
                    .leftMap(violations -> {
                        Map<String, Object> error = new LinkedHashMap<>();
                        error.put("message", "Invalid request body");
                        error.put("details", violations.details());
                        return error;
                    })
                    .fold(error -> badRequest().syncBody(error), //
                          user -> ok().syncBody(user))));
}