Validation is an important part of each API. This feature provides a simple way for incoming request validation.
Let's imagine we have request object which must be validated:
data class Message(val id: Long, val message: String)
First of all, we need to implement validation logic implementing Validator
interface:
object MessageValidator : Validator<Message> {
override fun supports(clazz: KClass<*>): Boolean = Message::class == clazz
override fun validate(obj: Message): ValidationResult {
val errors = mutableMapOf<PropertyPath, List<ValidationError>>()
if (obj.message.isBlank()) errors["message"] = listOf("message must be not blank")
return ValidationResult.Invalid(errors.toMap())
}
}
Then we need to configure the feature. There are 2 options:
- If object validation fails (object is not valid), then we get
RequestValidationException
, which we need handle. One of the options is StatusPage feature.
install(ValidationFeature) {
validators = listOf(MessageValidator) //add MessageValidator to feature
}
//StatusPages feature is optional, otherwise you should care about exception handling by yourself
install(StatusPages) {
exception<RequestValidationException> { cause ->
call.respond(HttpStatusCode.BadRequest, cause.validationResult)
}
}
// Some logic is here
post("/messages") {
val message = call.receive<Message>()
call.respond(message)
}
- If you don't want to throw the exception, you should set
throwExceptionIfInvalid
tofalse
. In this case, you could get validation result by calling:call.receiveValidated<T>()
which returnsPair<T, ValidationResult>
.ValidationResult
is sealed class, can be:NotValidated
- in case validators weren't providedValid
- in case validation finished successfullyInvalid
- in case validation failed
install(ValidationFeature) {
validators = listOf(MessageValidator) //add MessageValidator to feature
throwExceptionIfInvalid = false //by default, it is true
}
post("/messages") {
val (msg, validatedResult) = call.receiveValidated<Message>()
when (validatedResult) {
is ValidationResult.NotValidated -> call.respond(msg.message)
is ValidationResult.Valid -> call.respond(msg)
is ValidationResult.Invalid -> call.respond(HttpStatusCode.BadRequest, validatedResult.errors)
}
}
Add validated function for Routers
post("/") = validated {
val (msg, validatedResult) = call.receiveValidated<Message>()
when (validatedResult) {
is ValidationResult.NotValidated -> call.respond(msg.message)
is ValidationResult.Valid -> call.respond(msg)
is ValidationResult.Invalid -> call.respond(HttpStatusCode.BadRequest, validatedResult.errors)
}
}