4.0.1 • Published 12 months ago

promise-validation v4.0.1

Weekly downloads
-
License
ISC
Repository
github
Last release
12 months ago

Promise Validation

Promise Validation in JavaScript library which uses promises in a functional-like manner. It is light weight, composable, and really simple to use.

Installation

npm install promise-validation

Or just copy the file and paste it into your project.

Top-level View

A composable way to validate and create objects. This is how you would create a validator which will validate and transform your data to what you would like.

const nameValidator = {
    firstName: createString25('First Name'),
    lastName: maybe(createString25('Last Name'))
}

const personValidator = {
    ...name,
    birthdate: createDate('Birthdate')
}

You can see how you can compose the validators to build new validators. You can combine validators like maybe with createString25 — this will say that the string doesn't necessarily need to exist (it can be null or undefined) but if it is something will validate that it is a string of maximum length of 25. Then the name validator is composed into the person validator which adds the birthdate validation.

Then when you pass it to the function to do the actual validation:

const rawData = {
    firstName: "George",
    lastName: "Jungle",
    birthdate: "1967-09-09"
}

// No need to check anything about the person as you know it is a person and
// that person is there and the data just like you said it would be in the
// validator.
const person = await validateObject(rawData, personValidator)

console.log(person)
/*
{
    firstName: "George",
    lastName: "Jungle",
    birthdate: Date("1967-09-09")
}
*/

But what happens when the validation fails? Let us make one that fails for all properties:

const rawData = {
    lastName: "Jungle".repeat(5), // String length of 30
    birthdate: "1967-99-99"
}

// Returns x value in `catch` method. Normally you would catch this error higher
// up in the stack.
const failed = await validateObject(rawData, personValidator).catch(x => x)

console.log(failed)
/*
ValidationResult:
    reasons:
        [
            ValidationError:
                key: "firstName"
                reason: "'First Name' is undefined but is required."
            ValidationMessage:
                key: "lastName"
                reason: "'Last Name' is longer than 25."
            ValidationMessage:
                key: "birthdate"
                reason: "'Birthdate' is an invalid date."
        ]
*/

So, how would you normally do this?

const person = validatePerson(data)
if (person == null) {
    return null
}
const firstName = person?.firstName ?? "No name"

Rather messy. But with the validation above you can know that if the async function doesn't return an error that all your values are what they say they are.

More Details

So, how do we create the validation components?

function fail(message: string) {
    return Promise.reject(message)
}

function required<T>(name: string, value: T) : Promise<T> {
    return !value ? fail(`"${name}" is required but is falsey.`) : value
}

async function createString(name: string, value: string, length: number) {
    const v = await required(name, value?.trim())
    return v.length <= length ? v : fail(`${name} is longer than ${length} characters.`)
}

const createString25 =
    (name: string) =>
    (value: string | undefined) =>
        createString(name, value, 25)

const maybe =
    <T>(f: (val: T | undefined) => Promise<T>) =>
    (val: T | undefined) =>
        !val ? Promise.resolve(val) : f(val)

const createDate =
    (name: string) =>
    async (value: string | Date) => {
        let v = await required(name, value)
        if (v instanceof Date) {
            if (isNaN(+v)) {
                return fail(`"${name}" is not a valid date.`)
            }
            return v
        }
        let d = new Date(v)
        if (isNaN(+d)) {
            return fail(name, `is not a valid date.`)
        }
        return d
    }

As you can see, you can build up all the different validations just like you want to make them. They can be composable with high reuse of code in a functional and declarative way. No need for if statements. Just a simple async-await pattern gets you where you need to be!

How to handle an array of data?

Glad you asked. You could do it like so:

var results = Promise.all(data)
// OR if you want an aggregation
var results = validate(data)
// OR if you want partial results
var results = Promise.allSettled(data)

Change Log

4.0.0

  • Removed reason field from ValidationResult class.