genericskotlingeneric-variancetype-projection

How can I handle this "which is not a subtype of overridden" error


I'm trying to write some validatable form interface in Kotlin. In the validation part I'm using https://github.com/kamedon/Validation.

Here is the very simple code I'm trying to run;

import com.kamedon.validation.Validation


abstract class Validatable {
    abstract val validation: Validation<Any>

    fun validate() = validation.validate(this)
}

class LoginForm : Validatable() {
    val name: String = "Onur"
    val age: Int = 23

    override val validation = Validation<LoginForm> {
        "name" {
            be { name.length >= 5 } not "5 characters or more"
            be { name.length <= 10 } not "10 characters or less"
        }
        "age" {
            be { age >= 20 } not "Over 20 years old"
        }
    }
}


fun main(args: Array<String>) {
    val user = LoginForm()
    val result = user.validate()
    println(result)
}

This code gives me;

Type of 'validation' is not a subtype of the overridden property 'public abstract val validation: Validation<Any> defined in Validatable'

If I use Validation<out Any> in Validatable it says;

Kotlin: Out-projected type 'Validation<out Any>' prohibits the use of 'public final fun validate(value: T): Map<String, List<String>> defined in com.kamedon.validation.Validation'

If I use Validation<in Any> in Validatable it says;

Kotlin: Type of 'validation' is not a subtype of the overridden property 'public abstract val validation: Validation<in Any> defined in Validatable'

If I use Validation<Any> instead of Validation<LoginForm> in LoginForm, the code runs but this time name and age inside of validation are used from the class inside itself. I don't want to change this in respect of the usage of the library.

Is there anyway to use in and out keywords together or may be there is another way to achieve my goal.


Solution

  • You could make the abstract class Validatable a generic class, and make the subclass provide an object which exposes both the Validation object, and also itself as the target to validate, e.g.

    abstract class Validatable<T> {
        protected class ValidationInfo<T>(val target: T, val validation: Validation<T>)
    
        protected abstract val validationInfo: ValidationInfo<T>
    
        fun validate() = validationInfo.let { it.validation.validate(it.target) }
    }
    
    class LoginForm : Validatable<LoginForm>() {
        val name: String = "Onur"
        val age: Int = 23
    
        override val validationInfo = ValidationInfo(this, Validation {
            "name" {
                be { name.length >= 5 } not "5 characters or more"
                be { name.length <= 10 } not "10 characters or less"
            }
            "age" {
                be { age >= 20 } not "Over 20 years old"
            }
        })
    }