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

3

10

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?

Badillo answered 30/12, 2017 at 6:38 Comment(2)
My handler classes have injected @Service classes. After switching from SpringMVC to Spring WebFlux I added @Validated to the @Service classes, and also @Valid to the according method arguments.Housekeeping
@JuergenZimmermann It is helpful.Badillo
U
8

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))));
}
Upbeat answered 9/9, 2018 at 11:49 Comment(1)
Welcome to StackOverflow Toshiaki. While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Can you add more context and usage of the library to your answer? Answers that are little more than a link may be deleted.Figural
L
3

One of the ways I've managed to do it in my application is the following (code is in Kotlin but the idea is the same). I've declared RequestHandler class which performs validation:

@Component
class RequestHandler(private val validator: Validator) {

    fun <BODY> withValidBody(
            block: (Mono<BODY>) -> Mono<ServerResponse>,
            request: ServerRequest, bodyClass: Class<BODY>): Mono<ServerResponse> {

        return request
                .bodyToMono(bodyClass)
                .flatMap { body ->
                    val violations = validator.validate(body)
                    if (violations.isEmpty())
                        block.invoke(Mono.just(body))
                    else
                        throw ConstraintViolationException(violations)
                }
    }
}

Request objects can contain java validation annotations in this way:

data class TokenRequest constructor(@get:NotBlank val accessToken: String) {
    constructor() : this("")
}

And handler classes use RequestHandler to perform validation:

fun process(request: ServerRequest): Mono<ServerResponse> {
    return requestHandler.withValidBody({
        tokenRequest -> tokenRequest
                .flatMap { token -> tokenService.process(token.accessToken) }
                .map { result -> TokenResponse(result) }
                .flatMap { ServerResponse.ok()
                        .contentType(MediaType.APPLICATION_JSON_UTF8)
                        .body(Mono.just(it), TokenResponse::class.java)
                }
    }, request, TokenRequest::class.java)
}

Got the idea from this blog post.

Litch answered 21/4, 2018 at 16:32 Comment(0)
F
0

Looks like we now have a better way to do this (as per the official docs):

A functional endpoint can use Spring’s validation facilities to apply validation to the request body. For example, given a custom Spring Validator implementation for a Person:

public class PersonHandler {

  private final Validator validator = new PersonValidator(); 

  // ...

  public Mono<ServerResponse> createPerson(ServerRequest request) {
      Mono<Person> person = request.bodyToMono(Person.class).doOnNext(this::validate); 
      return ok().build(repository.savePerson(person));
  }

  private void validate(Person person) {
      Errors errors = new BeanPropertyBindingResult(person, "person");
      validator.validate(person, errors);
      if (errors.hasErrors()) {
          throw new ServerWebInputException(errors.toString()); 
      }
  }
}

① Create Validator instance.

② Apply validation.

③ Raise exception for a 400 response.

Handlers can also use the standard bean validation API (JSR-303) by creating and injecting a global Validator instance based on LocalValidatorFactoryBean. See Spring Validation.

Fellows answered 14/6, 2023 at 16:38 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.