Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Validation Data Class Parameters Kotlin

Tags:

If I am modeling my value objects using Kotlin data classes what is the best way to handle validation. Seems like the init block is the only logical place since it executes after the primary constructor.

data class EmailAddress(val address: String) {      init {         if (address.isEmpty() || !address.matches(Regex("^[a-zA-Z0-9]+@[a-zA-Z0-9]+(.[a-zA-Z]{2,})$"))) {             throw IllegalArgumentException("${address} is not a valid email address")         }     } } 

Using JSR-303 Example

The downside to this is it requires load time weaving

@Configurable data class EmailAddress(@Email val address: String) {      @Autowired     lateinit var validator: Validator      init {         validator.validate(this)     } } 
like image 630
greyfox Avatar asked Aug 16 '17 18:08

greyfox


People also ask

What are requirements for a data class in Kotlin?

Android Dependency Injection using Dagger with KotlinThe primary constructor needs to have at least one parameter. All primary constructor parameters need to be marked as val or var. Data classes cannot be abstract, open, sealed, or inner. The class may extend other classes or implement interfaces.

How do I validate in Kotlin?

Creating a custom validation constraint The extension method is implemented by calling validate(..) with our Constraint and passing a validation function. In the validation function we implement the actual validation. In this example we simply convert the Iterable to a List and then the List to a Set.

Can a data class in Kotlin have methods?

Data classes specialize in holding data. The Kotlin compiler automatically generates the following functionality for them: A correct, complete, and readable toString() method. Value equality-based equals() and hashCode() methods.

Can data class inherit another data class Kotlin?

Inheriting a data class from another data class is not allowed because there is no way to make compiler-generated data class methods work consistently and intuitively in case of inheritance.


2 Answers

I did make a comment, but I thought I would share my approach to validation instead.

First, I think it is a mistake to perform validation on instantiation. This will make the boundary between deserialization and handing over to your controllers messy. Also, to me, if you are sticking to a clean architecture, validation is part of your core logic, and you should ensure with tests on your core logic that it is happening.

So, to let me tackle this how I wish, I first define my own core validation api. Pure kotlin. No frameworks or libraries. Keep it clean.

interface Validatable {      /**      * @throws [ValidationErrorException]      */     fun validate() }   class ValidationErrorException(         val errors: List<ValidationError> ) : Exception() {      /***      * Convenience method for getting a data object from the Exception.      */     fun toValidationErrors() = ValidationErrors(errors) }  /**  * Data object to represent the data of an Exception. Convenient for serialization.  */ data class ValidationErrors(         val errors : List<ValidationError> )  data class ValidationError(         val path: String,         val message: String ) 

Then I have a framework specific implementations. For example a javax.validation.Validation implementation:

open class ValidatableJavax : Validatable {      companion object {         val validator = Validation.buildDefaultValidatorFactory().validator!!     }      override fun validate() {         val violations = validator.validate(this)         val errors = violations.map {             ValidationError(it.propertyPath.toString(), it.message)         }.toMutableList()         if (errors.isNotEmpty()) {             throw ValidationErrorException(errors = errors)         }     } } 

The only problem with this, is that the javax annotations don't play so well with kotlin data objects - but here is an example of a class with validation:

import javax.validation.constraints.Positive  class MyObject(         myNumber: BigDecimal ) : ValidatableJavax() {      @get:Positive(message = "Must be positive")     val myNumber: BigDecimal = myNumber  } 
like image 188
Laurence Avatar answered Sep 18 '22 11:09

Laurence


It seems unreasonable to me to have object creation validation anywhere else but in the class constructor. This is the place responsible for the creation, so that is the place where the rules which define what is and isn't a valid instance should be. From a maintenance perspective it also makes sense to me as it would be the place where I would look for such rules if I had to guess.

like image 45
Diego Marin Santos Avatar answered Sep 19 '22 11:09

Diego Marin Santos