Spring webflux bean validation not working
Asked Answered
H

3

12

I am trying to use bean validation in Webflux. This is what I have so far:

@PostMapping("contact")
fun create(@RequestBody @Valid contact: Mono<Contact>) : Mono<Contact> {
    return contact.flatMap { contactRepository.save(it) }
            .doOnError{ Error("test") }
}

The The validation doesn't work... I would expect that the Error("test") would be shown...

Does someone has a working example(Java or Kotlin)?

UPDATE

Here is a repository so it can be reproducted: https://github.com/jwz104/webflux-validation-test

Request:

curl --request POST \
  --url http://localhost:8080/tickets \
  --header 'content-type: application/json' \
  --data '{
    "email": "",
    "name": "",
    "message": ""
}'

Renamed contact to ticket, but everything is still the same.

Haber answered 25/1, 2018 at 9:51 Comment(3)
have you tried binding an Errors or BindingResult argument in your handler method signature?Lumber
@BrianClozel I tried both, but I get the same result... github.com/spring-projects/spring-boot/issues/10950 has the same issue with a fix. But that fix is the onErrorXX and doesn't work for meHaber
Can you provide a bit more information so we can reproduce it? The Contact class and the actual request sent (a curl command) would be nice.Lumber
U
11

The annotations you have placed in the example project are actually annotations on the constructor parameters of the Ticket class. For Spring validation, you need to annotate the fields instead. You can do this in Kotlin by using annotation use-site targets.

In this specific case, your Ticket class should look like this:

data class Ticket(
        @field:Id
        @field:JsonSerialize(using = ToStringSerializer::class)
        val id: ObjectId = ObjectId.get(),

        @field:Email
        @field:Max(200)
        @field:NotEmpty
        val email: String,

        @field:NotEmpty
        @field:Size(min = 2, max = 200)
        val name: String,

        @field:NotEmpty
        @field:Size(min = 10, max = 2000)
        val message: String
)

This, along with the following controller function will work and return errors as expected:

@PostMapping("tickets")
fun create(@RequestBody @Valid contact: Mono<Ticket>) : Mono<Ticket> {
    return contact.flatMap { ticketRepository.save(it) }
            .doOnError{ Error("test") }
}
Upanddown answered 29/1, 2018 at 19:37 Comment(0)
P
2

I had to combine few of the answers from SO to get the validation correct. What I did was:

  • Made my data class as suggested here.

    • Then i used javax.validation.Validator class to validate the data class.

import javax.validation.Validator import javax.validation.ConstraintViolationException

@Service
open class MyService {
    @Autowired
    lateinit var repository: UserRepository

    @Autowired
    lateinit var validator: Validator

     open fun createUser(user: User): Mono<User> {
         val violations = validator.validate(user)
          //if it violates our constraints, it will throw an exception, which we 
          //handle using global exception handler
          if(violations.isNotEmpty()){
               throw ConstraintViolationException(violations)
           }
           return repo.save(user)
         }
. . .
}
  • Add these lines in application.properties.
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false

And to catch the exceptions.

@RestControllerAdvice    
open class ExceptionHandlers {

  @ExceptionHandler(ConstraintViolationException::class)
  @ResponseStatus(HttpStatus.BAD_REQUEST)
  fun throwConstraintViolationExcetion(ex: ConstraintViolationException): ApiResponse {
    return ApiResponse (message= ex.message.toString(), data= null);
  }
}

p.s ApiResponse is just a data class that takes server message & data as parameters. No need to handle errors in @Controller as it will be thrown by the @Service class

Passion answered 21/9, 2018 at 16:17 Comment(0)
E
0

You need to also add bindingResult: BindingResult to as an extra parameter. When the method begins you can do something like bindingResult.isValid(). We use an aspect over all controller methods to return an error message with the validation errors to the user.

Exon answered 25/1, 2018 at 13:24 Comment(3)
I get Failed to resolve argument 0 of type 'org.springframework.validation.BindingResult' Haber
There is no method isValid().Barbarbarbara
The correct method in BindingResult is hasErrors().Marja

© 2022 - 2024 — McMap. All rights reserved.