Input validation with Folktale


In this post I’ll show a way to do input validation in JavaScript using Folktale which is a library for functional programming. The beauty of this approach is having validation rules (is required, is email, is confirmed, etc) specified as a callbacks so they can be reused across different forms and also the absence of control flow structures associated when doing input validation traditional way (using if statements).

Before going straight to the code, I’d also like to mention that if input has multiple validation rules, they are evaluated sequentially and its validating process is interrupted after the first encountered unsatisfied rule and its error returned. As opposite to evaluating all rules for a field and displaying all unsatisfied.

Folktale provides constructs and utilities to make functional programming easier. For input validation, we can use Result or Validation. The main difference between them is that Result stops at first encountered error, while Validation evaluates all rules. You might want to use Result in cases like validating a required email address, because showing that input is required and that this field doesn’t look like email address at the same time is stupid. On the other side, Validation is useful if you want a field to satisfy multiple rules (has specified length, has one uppercase letter, has one symbol etc) and show them all at once.

I’ll be doing input validation using Result since I prefer showing one input error at a time.

Validation rules are specified as functions which takes form object and a key (field name). Why not just field value? Because there are rules that evaluates one field against another field, like password confirmation. If rule’s predicate is satisfied, it returns Result.Ok, otherwise Result.Error.

Examples of validation rules:

import Result from 'folktale/result'

const isRequired = (form, field) => !!form[field].trim()
    ? Result.Ok(form[field])
    : Result.Error("This field is required")

const isEmail = (form, field) => /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(form[field])
    ? Result.Ok(form[field])
    : Result.Error(`${form[field]} doesn't seem to be an email`)

const isConfirmed = (fieldToConfirm, what) => (form, confirmField) => form[fieldToConfirm] === form[confirmField]
    ? Result.Ok(form[confirmField])
    : Result.Error(`${what} don't match`)

They are used like this (in case of registration form):

const rules = {
    username:              [isRequired],
    email:                 [isRequired, isEmail],
    password:              [isRequired],
    password_confirmation: [isRequired, isConfirmed('password', 'Passwords')]
}

Now we need to write a function that iterates over rules’ entires and for each field constructs a predicate based on its rules. Field’s rules are chained like this (in case of password confirmation): isRequired.chain(isEmail) and its output is an instance of Result.Error or Result.Ok. The function I come up with is quite criptic for inexperienced programmers, but all it does is iterating over rules’ entries using Folktale’s mapEntries and for each field construct a similar chain using reduce.

import {assoc} from 'ramda'
import mapEntries from 'folktale/core/object/map-entries'

const validate = (form, rules) => mapEntries(
    rules,
    ([field, fieldRules]) => [
        field,
        fieldRules.slice(1).reduce(
            (p, r) => p.chain(_ => r(form, field)),
            fieldRules[0](form, field)
        )
    ],
    (result, key, value) => assoc(key, value, result)
)

mapEntries takes three arguments – object to be iterated over, a function which takes an array of key, value and which returns array of key, value and reduce function. The last argument allow us to skip the field or add additional field to the object so mapEntries isn’t necessary a 1:1 transformation.

Now all we have to do is use it. Field validation has two outcomes, Ok (valid) and Error (invalid). We want to show nothing if input is valid, but for invalid field a message is shown wrapped in html tags. Folktale provides pattern matching for all its constructs that evaluates a specified function based on type (in our case based on Result’s Ok/Error type). It looks like this:

const form = {
    username: 'Jernej',
    email: 'jernej.sila@gmail'
    password: 'pass',
    password_confirmation: 'password'
}

const rules = {
    username:              [isRequired],
    email:                 [isRequired, isEmail],
    password:              [isRequired],
    password_confirmation: [isRequired, isConfirmed('password', 'Passwords')]
}

const errors = validate(form, rules)

const usernameError = errors.username.matchWith({
    Ok: ({value}) => undefined,
    Error: ({value}) => `<p class="text-danger">${value}</p>`
})

const passwordError = errors.password.matchWith({
    Ok: ({value}) => undefined,
    Error: ({value}) => `<p class="text-danger">${value}</p>`
})
console.log(usernameError) // Output: undefined
console.log(passwordError) // Output: <p class="text-danger">This field is required</p>