1.5.5 • Published 8 months ago

rule-filter-validator v1.5.5

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

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

By default tests are case and type insensitive, meaning:

RuleFnScopeResult
1_eq'1'true
'ABC'_eq'abc'true
'zxc3'_contains3true
'zxc3'_contains'ZXC'true

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

RuleFnScopeResult
1_eq'1'false
'ABC'_eq'abc'false
'zxc3'_contains3true
'zxc3'_contains'ZXC'false

_contains always compares as strings and is therefore not type sensitive

Advance Usage

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
})
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

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

</details>
<br/>
<hr/>
<br/>

# 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](#Field-Functions) that can be applied to a date.

<br/>

# 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


### _$

<details>
<summary>Examples of using `_$`</summary>

Given the following data record:
```ts
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',
            },
        },
    },
};
1.5.5

8 months ago

1.5.4

11 months ago

1.5.3

11 months ago

1.5.2

11 months ago

1.5.1

11 months ago

1.5.0

11 months ago

1.4.3

12 months ago

1.4.2

12 months ago

1.4.1

1 year ago

1.2.3

1 year ago

1.4.0

1 year ago

1.3.1

1 year ago

1.2.2

1 year ago

1.3.0

1 year ago

1.2.0

2 years ago

1.2.1

1 year ago

1.1.3

2 years ago

1.1.2

2 years ago

1.1.1

2 years ago

1.1.0

2 years ago

1.0.8

2 years ago

1.0.7

2 years ago

1.0.6

2 years ago

1.0.5

2 years ago

1.0.4

2 years ago

1.0.3

2 years ago

1.0.2

2 years ago

1.0.1

2 years ago

1.0.0

2 years ago