A validator for kotlin

validate your inputs or objects with power of the kotlin typesafe builders

Usage

quick introduction

val result=email.validate("[email protected]")
if(result.isValid){
    println("provided email is valid ,user:${result.user},host:${result.host}")
}else{
    replayToUser(result.reason!!.translate())
}

or

val result=compositeRule<User>{
    User::email mustBe email
    User::password mustBe inRange(8..64)
}.validate(user)
if(!result.isValid){
    println(result[User::email]?.translate())
    println(result[User::password]?.translate())
}

Setup

Dependency

put this into your gradle script

repositories {
    //...
    maven { url "https://jitpack.io" }
}
dependencies {
    //...
    //android usage
    implementation 'com.github.amir1376.kotlin-validator:android:$version'
    
    //core (jvm)
    implementation 'com.github.amir1376.kotlin-validator:core:$version'
}

Android

in your app entry point initialize validator translation

ValidatedTranslation.initDefaultAndroidAdapter(context)
    //include default translations
    .applyDefaultTranslations()
    

JVM

before any use of translation provide a TranslationAdapter
here is the default adapter

ValidatedTranslation.initWithDefault()

Features

Combining rules together

you can validate your input by multiple rules

Here is an example

val result=(email or empty).validate(input)

Validate nested objects

sometimes you want to validate a model
the library has support this too
for example you have the following models

    data class User(
        val login:String,
        val name:Name,
        val password:String,
        val confirmPassword:String,
        val gender:String
    )
    data class Name(
        val first:String,
        val last:String,
    )
    enum class Gender{
        Male,Female
    }

you can validate this with this rule

//inside a suspend function
val userValidation=compositeRule<User>{
    User::login mustBe (startWithEnglishCharacter and inRange(6..64)) 
    User::name mustBe compositeRule<Name>{
        Name::first mustBe notEmpty
        Name::last mustBe notEmpty
    }
    User::password mustBe (
            containsAtLeastLowerCase(1) and
            containsAtLeastUpperCase(1) and 
            (containsAtLeastNumber(1)) and 
            inRange(8..64)
            )
    User::confirmPassword mustBe sameAs(User::password)
    User::gender mustBe oneOf<Gender>()
}
//user input
val user:User/*retrieve user object*/
val result=userValidation.validate(user)
if(!result.isValid){
//    you can get reason for each property
    result[User::login] 
}

if you can see in the above code, user password has a complex rule ,
but you can extract it to a variable
and because these rules are stateless (they don’t store any reference of input)
you can safely use this combination multiple times

val strongPassword = containsAtLeastLowerCase(1) and
        containsAtLeastUpperCase(1) and
        (containsAtLeastNumber(1)) and
        inRange(8..64)
            //.... then replace
        User::password mustBe strongPassword

Customization

Creating your own rules

Of course, you can create your own rules with ease
here is an example

val phone get() = rule<String>{ input->
    if(phonePattern.matchEntire(input)){
        thenValid()
    }else{
        because("your provided phone number is not valid")
    }
}

Localization

if your app has support of multiple language
when building your rules ,you have to provide Reason
instead of raw string

object PhoneInvalidReason:SingleReason
val phone get() = rule<String>{ input->
    if(phonePattern.matchEntire(input)){
        thenValid()
    }else{
        because(PhoneInvalidReason)
    }
}

then you have to provide PhoneInvalidReason translation to the adapter

ValidatedTranslation.adapter.apply{
    //declare translation here
    //this is up to you that how you want to translate that message
    PhoneInvalidReason::class providedBy {
        "your provided phone number is not valid"
    }
}

otherwise, if you are not interested on default translation approach,
then you can create your own translation by implementing ValidatedTranslationAdapter

Android support

at the moment ,we have separated android module
that contains an Android translation adapter,
it has some useful extensions to provide translation
from string resources

Validator.android().apply{
    //declare translation here
    //this is up to you that how you want to translate that message
    PhoneInvalidReason::class providedByResource (R.string.my_validation_phone_invalid)
}

if you use this library for android, you can use default translation provided
by the android module.

currently supported languages are

  • English (default)
  • Persian

Coroutines support

the validate method is a suspend function
accordingly, rules are all suspend functions too,
so you can have suspended calls on them
and because of that, you have to call validate only in a coroutine scope
but ,if your validation rule hasn’t any suspend calls, it is
totally safe (there is no switching context in the core artifact) to
wrap validate into runBlocking block (if you have to)

Attention

this library is still under beta
so that may have bugs

Contribution

you can consider a pull request,
if you see unexpected behaviors in the library
or write more common plugins

otherwise, if you have suggestions or have seen something weird out there (?),
please submit an issue

TODOS

  • write tests
  • write more plugins

GitHub

View Github