Nerd For Tech
Published in

Nerd For Tech

Object validation in Ktor/Kotlin

Introduction

public class BusinessObject {   @NotNull
@NotEmpty
@Length(max=32)
private String name;
@NotNull
private String deliveryAddress;
@NotNull
private String invoiceAddress;
@Assert(expr = "_value ==_this.deliveryAddress || _value == _
this.invoiceAddress", lang = "groovy")
public String mailingAddress;
}
username: Joi.string()
.alphanum()
.min(3)
.max(30)
.required(),
email: Joi.string()
.email({ minDomainSegments: 2, tlds: { allow: ['com', 'net']

The Bad

EasyValidation

Kamedon

Kvalidation

The Good

exception<IllegalArgumentException> { e ->
logger.error("Exception occurred: ${e.message}")
val response = TextContent(e.message ?: "Bad Request",
ContentType.Text.Plain.withCharset(Charsets.UTF_8),
HttpStatusCode.BadRequest
)
call.respond(response)
}

Valiktor

data class Employee(val id: Int, val name: String, val email: String) {
init {
validate(this) {
validate(Employee::id).isPositive()
validate(Employee::name).hasSize(min = 3, max = 80)
validate(Employee::email).isNotBlank().isEmail()
}
}
}
init {
runCatching {
validate(this) {
validate(Account::accountUUID).matches(uuidRegex)
validate(Account::createdAt).isNotNull()
validate(Account::modifiedAt).isNotNull()
validate(Account::status).isNotNull()
}
}
.throwOnFailure()
}

Konform

val validateUser = Validation<UserProfile> {
UserProfile::fullName {
minLength(2)
maxLength(100)
}

UserProfile::age ifPresent {
minimum(0)
maximum(150)
}
}
init {
Validation<Account> {
Account::accountUUID {
pattern(uuidRegex)
}
Account::createdAt required { }
Account::modifiedAt required { }
Account::status required { }
}
.validateAndThrowOnFailure(this)
}

Verdict

Addendum 1

import com.github.michaelbull.result.Result
import com.github.michaelbull.result.onFailure
fun <V> Result<V, Throwable>.throwOnFailure() {
onFailure {
val error = (component2() as ConstraintViolationException)
throw IllegalArgumentException(error.getMessage())
}
}
import com.github.michaelbull.result.*runCatching {
validate(this) {
validate(Account::accountUUID).matches(uuidRegex)
validate(Account::status).isNotNull()
}
}
.throwOnFailure()

Addendum 2

data class Account(
var accountUUID: String,
var createdAt: Instant,
var modified: Instant,
var status: AccountStatus
) {
fun validate(): Account {
// do validation
val account = call.receive<Account>()
val account = call.receive<Account>().validate()

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store