6

I'm just starting to use kotlin along side with Spring Boot to develop a simple web application.

Let´s take a simple data class object

@Entity  
data class User (name: String) {  
  @Id @GeneratedValue(strategy = GenerationType.AUTO)  
  var id: Long = -1  
}  

and a Controller

@RequestMapping(method = arrayOf(RequestMethod.POST))
fun createUser (@RequestBody user: User): User {
    return userService.createUser(user)
}

Well, making a request with any request body would just throw an http 400 error no suitable constructor found, can not deserialize from Object value (missing default constructor or creator, or perhaps need to add/enable type information?); nested exception is com.fasterxml.jackson.databind.JsonMappingException

To eliminate this error I found out that we need to give some default values to the constructor arguments so:

name: String = ""

or maybe:

name: String? = null

Now, there is absolutely no validation of the input that goes on the response body, meaning that, if the JSON present in there does not respect the syntax of the User class, the default values will be used and stored in the database.

Is there any way to validate the request body JSON input to throw a bad request if it does not comply with the User class arguments without having to do it manually?

In this example there is only one argument but with larger classes manually does not seems like a good way to go

Thanks in advance

Miguel Anciães
  • 106
  • 1
  • 5
  • 1
    Yes, you can do that in the constructor of the User class, or in validations method that userService calls. You should check out the [kotlin-spring gradle plugin](https://plugins.gradle.org/plugin/org.jetbrains.kotlin.plugin.spring) and the [All-open compiler plugin](https://kotlinlang.org/docs/reference/compiler-plugins.html) (quick intro at http://www.baeldung.com/kotlin-allopen-spring). They make Spring + Kotlin data classes much more bearable. – Paul Hicks Mar 01 '18 at 00:03
  • As to the _how_ of validation, you could go with JsonSchema if you don't mind the heavy schema docs. There's a basic example at http://www.baeldung.com/introduction-to-json-schema-in-java – Paul Hicks Mar 01 '18 at 00:13
  • 2
    Do you have jackson kotlin module (https://github.com/FasterXML/jackson-module-kotlin) registered? Once you do, you can use normal javax.validation annotations (just remember about annotation use-site target). – Rafal G. Mar 01 '18 at 07:27
  • Actually, @RafalG. 's answer worked like a charm. This has to do with the no-arg constructor mentioned by Paul Hicks so, thanks you both for taking the time to help me. JSON schema would also work if more validation it's needed – Miguel Anciães Mar 02 '18 at 12:32
  • @MiguelAnciães have a look into this https://stackoverflow.com/questions/35847763/kotlin-data-class-bean-validation-jsr-303 – Serhii Povísenko Feb 20 '20 at 10:54

2 Answers2

1

I think the following solution that consist of two parts could take place for Spring Boot 2.x:


API facade configuration

@Configuration
class ValidationRouter(
        val validationService: ValidationService
) {

    @Bean
    fun router() = router {
        accept(APPLICATION_JSON).nest {
            "/api".nest {
                POST("/validation") { req ->
                    req.bodyToMono(ValidatingBody::class.java)
                            .doOnNext(validationService::validate)
                            .flatMap { ServerResponse.ok().build() }
                            .onErrorResume(BodyValidationException::class.java, validationService::handle)
                }
            }

        }

    }

    data class ValidatingBody(
            @get:NotBlank(message = "should not be blank")
            var mandatoryText: String?,
            @get:Size(min = 2, max = 10, message = "should contain more than 2 and less than 10 characters")
            @get:NotBlank(message = "should not be blank")
            var sizedText: String?,
            @get:Min(value = 1, message = "should be no less than 1")
            @get:Max(value = 10, message = "should be no greater than 10")
            @get:NotNull(message = "should be specified")
            var limitedNumber: Int?
    )
}

Validating mechanism

@Service
class ValidationService(val validator: Validator) {

    private val log = LoggerFactory.getLogger(ValidationService::class.java)

    @Throws(BodyValidationException::class)
    fun <T> validate(o: T) {
        val violations = validator.validate(o)
        if (violations.isEmpty()) return

        throw BodyValidationException.of(violations.stream()
                .map { v: ConstraintViolation<T> -> "${v.propertyPath} ${v.message}" }
                .collect(Collectors.toSet()).toMutableSet())
    }

    fun handle(e: BodyValidationException): Mono<ServerResponse> =
            handleInternal(ErrorResponse(e.errors))

    private fun handleInternal(response: ErrorResponse): Mono<ServerResponse> =
            Mono.just(response)
                    .doOnNext { res -> log.warn(res.errors.joinToString(",")) }
                    .flatMap { res ->
                        badRequest().contentType(APPLICATION_JSON)
                                .bodyValue(res)
                    }

    data class ErrorResponse(val errors: Set<String>)

    class BodyValidationException private constructor(var errors: Set<String>) : IllegalArgumentException() {
        companion object {
            fun of(errors: Set<String>): BodyValidationException {
                return BodyValidationException(errors)
            }
        }

    }
}
Serhii Povísenko
  • 3,352
  • 1
  • 30
  • 48
0

Yes, there are ways to validate the request body JSON "automatically".

JSON Schema is one of them, and there are several implementations. Spring Data Rest includes it, so that's probably your first choice, since Spring Boot wraps it so nicely.

Paul Hicks
  • 13,289
  • 5
  • 51
  • 78