Skip to content

Instantly share code, notes, and snippets.

@Benjiko99
Last active November 22, 2020 14:16
Show Gist options
  • Save Benjiko99/22c2574512035bb005b979afb1bd11f5 to your computer and use it in GitHub Desktop.
Save Benjiko99/22c2574512035bb005b979afb1bd11f5 to your computer and use it in GitHub Desktop.
Credentials Validator
/**
* Validates login credentials.
*
* To use it, call [validate] with your [Credentials] and check the returned [ValidationResult].
*
* To add new validations, define the rules which need to be satisfied
* and the errors that can be thrown in response to dissatisfied rules.
*
* For each field in your login form define a mapping of [Rule]s to [Error]s
* which apply to the given field.
*/
object CredentialsValidator {
/** Declares which rules apply to the email field and what error to return
* if the rule is not satisfied. */
private val emailFieldRules = mapOf<Rule, Error>(
FieldIsNotBlankRule to EmailIsBlank
)
private val passwordFieldRules = mapOf<Rule, Error>(
FieldIsNotBlankRule to PasswordIsBlank,
)
fun validate(credentials: Credentials): ValidationResult {
val errors = mutableListOf<Error>()
validateInputAgainstRules(credentials.email, emailFieldRules).also(errors::addAll)
validateInputAgainstRules(credentials.password, passwordFieldRules).also(errors::addAll)
return if (errors.isEmpty()) Valid else Invalid(errors)
}
private fun validateInputAgainstRules(input: String, rules: Map<Rule, Error>): List<Error> {
val errors = mutableListOf<Error>()
rules.forEach { (rule, error) ->
if (rule.isDissatisfiedBy(input))
errors.add(error)
}
return errors
}
}
sealed class ValidationResult {
object Valid : ValidationResult()
data class Invalid(
val errors: List<Error>
) : ValidationResult()
}
data class Credentials(
val email: String,
val password: String
)
sealed class Error(val message: FormattableString) {
constructor(@StringRes stringRes: Int) : this(FormattableString(stringRes))
object EmailIsBlank : EmailField,
Error(R.string.enter_email)
object PasswordIsBlank : PasswordField,
Error(R.string.enter_pass)
}
interface EmailField
interface PasswordField
abstract class Rule {
abstract fun isSatisfiedBy(input: String): Boolean
fun isDissatisfiedBy(input: String): Boolean =
isSatisfiedBy(input).not()
}
object FieldIsNotBlankRule : Rule() {
override fun isSatisfiedBy(input: String) =
input.isNotBlank()
}
private fun areCredentialsValid(credentials: Credentials): Boolean {
val result = CredentialsValidator.validate(credentials)
return result is ValidationResult.Valid
}
private updateUI() {
when (viewState.validationResult) {
is ValidationResult.Valid -> {
clearErrors()
}
is ValidationResult.Invalid -> {
val errors = viewState.validationResult.errors
val emailErrors = errors.filter { it is EmailField }
val passwordErrors = errors.filter { it is PasswordField }
emailField.setError(emailErrors[0].message)
passwordField.setError(passwordErrors[0].message)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment