Skip to content

A validator for comparing a set of rules against a given payload

Notifications You must be signed in to change notification settings

u12206050/rule-filter-validator

Repository files navigation

The idea

Given a specific scope what is the easiest way to write various rules that be tested against the scope for various reasons

Useful for testing & validation Business Logic stored as json.

Getting started

npm install rule-filter-validator

Usage

// Initial state/scope/payload
const SCOPE = {
    person: {
        id: 1,
        dob: "1998-02-18",
        age: 23,
        active: true,
        gender: 'F'
    }
}
// Validate and return errors
const rule: Filter = {
    "person": {
        "age": {
            "_gt": 18,
            "_lt": 25
        }
    }
}

let errors = validatePayload(rule, SCOPE);
return ! errors.length // true
// Simply validate
// Using field alter functions
isValidPayload({ person: { 'year(dob)': { _eq: 1998 } } }, SCOPE) // true


// Using $NOW
isValidPayload({ person: { 'dob': { _gt: '$NOW(-6 years)' } } }, SCOPE) // false

View all methods and functions

NOTE: Strictness

By default tests are case and type insensitive, meaning:

Rule Fn Scope Result
1 _eq '1' true
'ABC' _eq 'abc' true
'zxc3' _contains 3 true
'zxc3' _contains 'ZXC' true

Calling validatePayload(filter, payload, [strict = false]) with strict = true will make the validator to be case and type sensitive.

Rule Fn Scope Result
1 _eq '1' false
'ABC' _eq 'abc' false
'zxc3' _contains 3 true
'zxc3' _contains 'ZXC' false

_contains always compares as strings and is therefore not type sensitive


Advance Usage

Find a matching price rule
const prices = [
    {
        label: 'Child price'
        price: 100,
        logic: {
            "person": {
                "age": {
                    "_lt": 18
                }
            }
        }
    },
    {
        label: 'Adult price'
        price: 200,
        logic: {
            "person": {
                "age": {
                    "_gte": 18
                }
            }
        }
    }
]

const scope = getUserScope(); // You implement this.

const priceToPay = prices.find(({ price, logic }) => {
    let e = validatePayload(logic, scope)
    return ! e.length
})
Matching items in a list
const filter: Filter = {
    _or: [{
        permissions: {
            _$: {
                action: {
                    _eq: 'update',
                },
                collection: {
                    _eq: 'membership',
                },
                fields: {
                    _contains: 'status',
                },
            },
        },
    },
    {
        role: {
            _eq: 'admin',
        },
    }],
};

const scope = {
    role: 'author',
    permissions: [
        {
            action: 'create',
            collection: 'membership',
            fields: ['person', 'status'],
        },
        {
            action: 'read',
            collection: 'membership',
            fields: ['id', 'person', 'status'],
        },
        {action: 'update', collection: 'membership', fields: ['status']},
    ],
};

const canAccess = validatePayload(filter, scope, strict?).length === 0
Using functions and date adjustments
// passes
validatePayload(
    { person: {'year(dob)': {_eq: 2009}} },
    { person: { dob: '2009-02-18' } }
);

// passes (IF the year is currently 2021)
validatePayload(
    { person: {'year(dob)': {_eq: '$NOW(-12 years).year'}} },
    { person: { dob: '2009-02-19' } }
);



Methods

  • isValidPayload(Filter, Payload, strict?)

    This is a simple function that returns true if the payload is valid against the filter, and false otherwise.

  • validatePayload(Filter, Payload, strict?)

    This is the main function that validates the payload against the filter. It returns an array of errors, if any.

  • invertFilter(Filter)

    Inverts the filter, so that the filter will return the opposite of what it would have returned before.

  • extractFieldFromFilter(Filter, Field, Path?)

    This extracts the given field from the passed Filter and returns a new Filter object that only contains only the given field and its children, if any.

  • adjustDate(date, adjustment)

    The function applies adjustments to the Date using built-in methods such as setUTCMonth, setUTCHours, etc., based on the type of adjustment requested. If no supported adjustment type is supplied, it returns undefined.

    eg. -1 month will return the date 1 month before the given date.

    Supports years, months, days, hours, minutes, seconds, milliseconds, and weeks.


Dynamic values

Currently only supporting one dynamic value, which is $NOW.

Examples:

  • $NOW(-1 month) will test the payload with the date 1 month before the current date.
  • $NOW(-12 years).year will test the payload with the year 12 years before the current date.

Supports all field functions that can be applied to a date.


Operands

Special Operands

Fn Description Accepted Types
_and All of the specified filters must be true for the expression to be true array of filters
_or At least one of the specified filters must be true for the expression to be true array of filters
_$ Used as an index for array of objects, whereby at least one item must pass the filter for the expression to be true. object

Field Functions

Used on fields to perform operations on them. eg. year(dob) will test with the year of the date of birth.

Fn Description Accepted Types
year() the year of the date date, isoString
month() the month of the date date, isoString
week() the week of the date date, isoString
day() the day of the date date, isoString
hour() the hour of the date date, isoString
minute() the minute of the date date, isoString
second() the second of the date date, isoString
count() the number of items in the array array

All Operands (Functions)

Fn Description Accepted Types
_eq equal to string, number, boolean
_neq not equal to string, number, boolean
_contains string/array contains string, number
_ncontains string/array does not contain string, number
_starts_with starts with string, number
_nstarts_with does not start with string, number
_ends_with ends with string, number
_nends_with does not end with string, number
_in in string, Array
_nin not in string, Array
_between between string, number, Date
_nbetween not between string, number, Date
_gt greater than string, number, Date
_gte greater than or equal to string, number, Date
_lt less than string, number, Date
_lte less than or equal to string, number, Date
_null null = string, number, boolean, Date, Array
_nnull not null = string, number, boolean, Date, Array
_empty empty = string, Array
_nempty not empty = string, Array
_submitted submitted = string, number, boolean, Date, Array
_regex matching regex string, number, boolean, Date

_$

Examples of using `_$`

Given the following data record:

const data = {
    colors: [{
        name: 'red',
        hex: '#ff0000',
    }, {
        name: 'green',
        hex: '#00ff00',
    }, {
        name: 'blue',
        hex: '#0000ff',
    }]
};

The following filter will pass:

{
    colors: {
        _$: {
            name: {
                _eq: 'red',
            },
        },
    },
}

And the following will fail:

{
    colors: {
        _$: {
            name: {
                _eq: 'yellow',
            },
        },
    },
}

You could also have multiple properties that have to match

// Will pass
{
    colors: {
        _$: {
            name: {
                _eq: 'red',
            },
            hex: {
                _eq: '#ff0000',
            },
        },
    },
}

// Will fail
{
    colors: {
        _$: {
            name: {
                _eq: 'red',
            },
            hex: {
                _eq: '#ff4444',
            },
        },
    },
};

About

A validator for comparing a set of rules against a given payload

Resources

Stars

Watchers

Forks

Packages

No packages published