Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data validation #2

Open
y9vad9 opened this issue Dec 2, 2021 · 3 comments
Open

Data validation #2

y9vad9 opened this issue Dec 2, 2021 · 3 comments
Assignees
Labels
enhancement New feature or request wontfix This will not be worked on

Comments

@y9vad9
Copy link
Owner

y9vad9 commented Dec 2, 2021

The most complex thing here is how to design validators.
I see three ways:

  • (Not convenient for me, but still the way) design it with notations and annotations:
    // object here is notation, we assuming that user will
    // create object here, but not class. Anyway this will
    // be checked at compile time
    object NameValidator : Validator<String> {
        override fun validate(value: String): Boolean = ...
    }
    
    @Validate(NameValidator::class)
    val name: String = ""
  • (Not convenient for me, but still the way) design it with function name notations:
    var name: String = ...
    fun _checkName_(): Boolean = ...
    Transformed to →
    private var _name: String
    var name: String get() = _name set() = if(/* code from _checkName_() */) ... else ...
  • (Now I'm looking forward this) Wrap it with a type:
    val name: Validate<String> = Validate(default = ...) { name -> require(name.length in 1..30) { "Provide a correct name" } }
    Transformed to →
    var _name: String = default
    var name get() = _name set() = if(/* code from name.validate() */) ... else ...

Originally posted by @y9san9 in #1 (comment)

@y9vad9 y9vad9 added the enhancement New feature or request label Dec 2, 2021
@y9vad9
Copy link
Owner Author

y9vad9 commented Dec 2, 2021

I don't see any variants for wrapping concretely value (with saving interface implementation for mutable & immutable realizations) via ksp so I suggest next variant:

@ImmutableImpl
interface Foo {
   val value: String
   
   @Validator(propertyName = "value")
   val validator: Validator<String> get() = StringLengthValidator(1..99) 
}

Minuses I see:

  • validator is exposing to public (we can use internal modifier, but it isn't a case for every situation)
    Possible solutions: It can be fixed with abstract class and protected modifier (requires Abstract class support #3)

Also, with same idea we can make companion object with next view:

@ImmutableImpl
interface Foo {
   val value: String
   
   @ValidatorsStorage
   private companion object Validation {
         val value: Validatable<String> = Validatable(property = Foo::value, validator = StringLengthValidator(0..99))
   }
}

@y9vad9 y9vad9 modified the milestone: 1.1.0 Dec 3, 2021
@y9vad9 y9vad9 added the wontfix This will not be worked on label Dec 8, 2021
@y9vad9
Copy link
Owner Author

y9vad9 commented Aug 28, 2022

finally, I propose next variant:

/**
 * Validator contract:
 * - Validator always should be object.
 */
interface Validator<T> {
    /**
     * Checks [value] for correctness.
     * If return value is `false` — implier will throw an exception.
     */
    fun validate(value: T): Boolean
}

annotation class Validates<T>(val validator: KClass<Validator<T>>)

object DigitStringValidator : Validator<String> {
    override fun validate(value: String): Boolean {
       return value.all { it.isDigit() }
    }
}

@ImmutableImpl
interface Entity {
   @Validates(DigitStringValidator::class)
   val value: String
}

Minuse I see is impossibility to provide some additional parameters to validator. But I think it can be avoided in next way:

abstract class IntValueValidator(val min: Int, val max: Int) : Validator<Int> {
   override fun validate(value: Int): Boolean {
        return value >= min && value =< max
   }

    companion object Month : IntValueValidator(1, 12)
    companion object Hour24 : IntValueValidator(0, 23)
     // etc
  }

However it still not a case for some situations, but I don't see any other possible solutions.
Anyway, we can use init {} block in abstract classes to validate the information.

@y9vad9
Copy link
Owner Author

y9vad9 commented Aug 28, 2022

Also, we can provide annotation that will provide safe way to institiate an object:

@Immutable
@SafeFactoryImpl
interface Entity {
     @Validates(EmailValidator::class)
     val email: String
     @Validates(StringLengthValidator.FirstName::class)
     val firstName: String
}

// generates
sealed interface EntityCreationResult {
     object EmailIsInvalid : EntityCreationResult
     object FirstNameIsInvalid : EntityCreationResult
     class Success(val value: Entity) : EntityCreationResult
}

fun Entity(email: String, firstName: String): EntityCreationResult {
      if(!EmailValidator.validate(email))
         return EntityCreationResult.EmailIsInvalid
      if(!StringLengthValidator.FirstName.validate(firstName))
          return EntityLengthValidator.FirstNameIsInvalid
      return EmailCreationResult.Success(ImmutableEntity(email, firstName))
}

@y9vad9 y9vad9 self-assigned this Aug 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

1 participant