0.1.0 • Published 4 years ago

@rewardops/filter-parser v0.1.0

Weekly downloads
1,035
License
MIT
Repository
github
Last release
4 years ago

Table of Contents

Working on the parser

This project uses the parser generator by Nearley.js . To work on the grammar and to modify the rules, you will need to have Nearley installed on your machine. To achieve that run:

npm install -g nearley

There are a few example grammar files provided in the folder lib/grammar/examples. Going through these might give the reader an understanding of the grammar syntax and the grammar examples also include instructions on how to test them.

Compiling the Grammar to generate the parser

The main file containing the grammar rules is stored in lib/grammar/main.ne. Whenever the file is updated, you need to compile it to generate the parser js file. To do this run:

nearleyc lib/grammar/main.ne -o lib/compiled-grammar/main.js

This will generate the parser file main.js which is used by this library.

Testing in the terminal

If you want to test the generated parser file against a particular output in the terminal without running the tests through jest, you can run:

nearley-test lib/compiled-grammar/main.js --input 'CATEGORY(true)=="abc"'

Library Specification

There are two main functions returned from the library with the following signatures:

parseFilterString(filterString) ⇒ object

Parses the filter string to return a JSON object

Kind: global function Returns: object - - a json representation of the string

ParamTypeDescription
filterStringstringthe filter string

Example

parseFilterString('CATEGORY(true)==["abc","cde"]&CATEGORY(false)==["xyz"]&CATEGORY(true)!=["123"]&SIV_ATTRIBUTE(id)==[12,23]&SIV_ATTRIBUTE(id)!=[65,34]')

-> returns: [
  {
    CATEGORY: {
      includedWithSubcategories: ['abc', 'cde'],
      includedWithoutSubcategories: ['xyz'],
      excludedWithSubcategories: ['123'],
    },
    SIV_ATTRIBUTE: {
      id: {
        included: [12, 23],
        excluded: [65, 34],
      },
    },
  },
];

If we break it down to smaller chunks, we get:

Category Filter With Subcategories

These are definitions where including a parent category automatically includes all its subcategories to the filter. In the filter string they are represented by the boolean in the parenthesis immediately after the keyword CATEGORY. For example in the category string CATEGORY(true)==['CAT_SYS_123'], the true indicates that all the subcategories are included.

These can be further divided into two types:

Adding categories

These filter strings are used to show the category codes which are added to the filter. These are filter strings of the form:

const filterString = 'CATEGORY(true)==["abc", "cde"]';

The resulting JSON for this string should look like:

const filterArray = [
  {
    CATEGORY: {
      includedWithSubcategories: ['abc', 'cde'],
    },
  },
];

Removing categories

Very similar to the previous example but these strings denote category codes which are explicitly removed from the filter. Currently, these filter strings do not exist in the actual data but strings like these are possible. These are filter strings of the form:

const filterString = 'CATEGORY(true)!=["xyz", "pyf"]';

Note that the equality operator here is != and not ==.

The resulting JSON for this string should look like:

const filterArray = [
  {
    CATEGORY: {
      excludedWithSubcategories: ['xyz', 'pyf'],
    },
  },
];

The key here is called excluded as opposed to included in the previous example.

Category Filter Without Subcategories

A category filter definition where adding or excluding a category only applies to items marked with that particular category and items marked with categories which are subcategories to the category mentioned in the definition are unaffected by this filter rule. An example of a filter string of this type would be CATEGORY(false)==['CAT_123'] and here the boolean false next to the category indicates that the subcategories are not included in the definition. These have the same 2 types as the definitions where the subcategories are included:

  • Adding Categories
  • Removing Categories

They look exactly like their counterparts where the subcategories are included except for the boolean flag. For example, a filter string of this type where you are adding the categories would look like:

const filterString = 'CATEGORY(false)==["abc", "cde"]';

The parsed JSON object for this string would be:

const filterArray = [
  {
    CATEGORY: {
      includedWithoutSubcategories: ['abc', 'cde'],
    },
  },
];

The only difference here being that the boolean value for the key subcategory is false.

Other Example Filter Strings

Items belonging to category codes or certain item IDs

const filterString = 'CATEGORY(false)==["abc", "cde"]|SIV_ATTRIBUTE(id)==[123, 432]';

The parsed JSON object for this string would be:

const filterArray = [
  {
    CATEGORY: {
      includedWithoutSubcategories: ['abc', 'cde'],
    },
  },
  {
    SIV_ATTRIBUTE: {
      id: {
        included: [123, 432],
      },
    },
  },
];

Items belonging to category codes but not including certain item IDs

const filterString = 'SIV_ATTRIBUTE(id)==[123, 432]&(CATEGORY(true)==["pikachu", "raichu"])';

The parsed JSON object for this string would be:

const filterArray = [
  {
    CATEGORY: {
      includedWithSubcategories: ['pikachu', 'raichu'],
    },
    SIV_ATTRIBUTE: {
      id: {
        excluded: [123, 432],
      },
    },
  },
];

Items belonging to category codes or item IDs but not including certain item IDs

const filterString = 'SIV_ATTRIBUTE(id)!=[123, 432]&(CATEGORY(true)==["pikachu", "raichu"]|SIV_ATTRIBUTE(id)==[98])';

The parsed JSON object for this string would be:

const filterArray = [
  {
    SIV_ATTRIBUTE: {
      id: {
        excluded: [123, 432],
      },
    },
    array: [
      {
        CATEGORY: {
          includedWithSubcategories: ['pikachu', 'raichu'],
        },
      },
      {
        SIV_ATTRIBUTE: {
          id: {
            included: [98],
          },
        },
      },
    ],
  },
];

setFilter(definition, { label,subtype,values }) ⇒ string

Takes a filter string and a json object and updates filter string with the JSON object. Existing values for the quantity being set are blown away.

Kind: global function Returns: string - The filter difinition with the values added to the initial definition.

ParamTypeDescription
definitionstringThe filter definition to be modified
{ label, subtype, values }objectAn object which specifies what to set on the filter and the values to set

Example

setFilter("SIV_ATTRIBUTE(id)=[123]", { label: 'CATEGORY', subtype: 'subcategory-included', values: ["cat1"]})

-> `CATEGORY(true)==["cat1"]|SIV_ATTRIBUTE(id)==[123]`

The currently supported labels and the valid subtypes are in this chart.

LabelValid Subtypes
CATEGORYsubcategory-included, subcategory-excluded
SIV_ATTRIBUTEid-included, id-excluded, supplier-included

The following section explains each of these label subtype combinations in further detail with examples.

CATEGORY

subcategory-included

Used to update or set category codes which are to be included in the filter string including their subcategories. Say the filter initially includes all items with the category code electronics and its subcategories. You want to change the filter to include all items which belong to category codes electricals and home-appliances and their subcategories.

const initialDefinition = 'CATEGORY(true)==["electronics"]';

setFilter(initialDefintion, { label: 'CATEGORY', subtype: 'subcategory-included', values: ["electricals", "home-appliances"]});

-> 'CATEGORY(true)==["electricals", "home-appliances"]'

The initial value can also be a filter string not containing category. Say the initial filter was all items belonging to the supplier Apple. And we wanted to add the same 2 category codes to this filter string.

const initialDefinition = `SIV_ATTRIBUTE(supplier)==["Apple"]`;

setFilter(initialDefintion, { label: 'CATEGORY', subtype: 'subcategory-included', values: ["electricals", "home-appliances"]});

-> `SIV_ATTRIBUTE(supplier)==["Apple"]&CATEGORY(true)==["electricals", "home-appliances"]`

subcategory-excluded

Used to update or set category codes which are to be included in the filter string excluding their subcategories. Say the filter initially includes all items with the category code electronics and its subcategories. You want to change the filter to include all items which belong to category codes electronics2 and home-appliances but not their subcategories.

const initialDefinition = 'CATEGORY(true)==["electronics"]';

setFilter(initialDefintion, { label: 'CATEGORY', subtype: 'subcategory-excluded', values: ["electronics2", "home-appliances"]});

-> 'CATEGORY(false)==["electronics2", "home-appliances"]'

SIV_ATTRIBUTE

id-included (special case)

We use this to create a filter which includes all the SIV IDs provided. This is a special case because the other filter strings are all inclusive. For example, your filter may try to get all items belonging to a particular category, say Electronics AND is supplied by a particular supplier, say Apple. But in the case of SIV IDs, it is the other conditions OR all items with these IDs. It is not an AND relationship.

const initialDef = 'CATEGORY(true)==["electronics"]&SIV_ATTRIBUTE(supplier)==["Apple"]'

setFilter(initialDef, { label: 'SIV', subtype: 'id-included', values: [123]})

->  '(CATEGORY(true)==["electronics"]&SIV_ATTRIBUTE(supplier)==["Apple"])|SIV_ATTRIBUTE(id)==[123]'

Note: The resulting filter is joined with an "|" which denotes that its an OR condition.

id-excluded

If you want to select all items belonging to a particular filter except for a few IDs, thats when you use this rule. This will specify a set of SIV ID values to exclude from the filter.

const initialDef = 'CATEGORY(true)==["electronics"]&SIV_ATTRIBUTE(supplier)==["Apple"]'

setFilter(initialDef, { label: 'SIV', subtype: 'id-excluded', values: [123]})

->  '(CATEGORY(true)==["electronics"]&SIV_ATTRIBUTE(supplier)==["Apple"]&SIV_ATTRIBUTE(id)!=[123])'

Note the difference from the previous filter string. The IDs are denoted with a "!=" sign and they are joined with an & to denote that this rule works with the existing rules.

NOTE: Another thing to note is that currently, you cannot have a filter definiton with only ID excluded and ID included. In that case, all the ID excluded entries are automatically removed.

supplier-included

If you have a particular filter but if you want to restrict the filter result only to a list of suppliers, you will use this subtype. For example, say the filter currently returns all items belonging to the category electronics, but you want to just look at the items under electronics provided by the amazing supplier rewardos and say the supplier ID of rewardos was 1.

const initialDef = 'CATEGORY(true)==["electronics"]'

setFilter(initialDef, { label: 'SIV', subtype: 'supplier-included', values: [1]})

->  '(CATEGORY(true)==["electronics"]&SIV_ATTRIBUTE(supplier)==[1])'

supplier-excluded

If you have a particular filter but if you want to restrict the filter result only to exclude a list of suppliers, you will use this subtype. For example, say the filter currently returns all items belonging to the category electronics, but you want to just look at all the items under electronics except items provided by the amazing supplier rewardos and say the supplier ID of rewardos was 1.

const initialDef = 'CATEGORY(true)==["electronics"]'

setFilter(initialDef, { label: 'SIV', subtype: 'supplier-excluded', values: [1]})

->  '(CATEGORY(true)==["electronics"]&SIV_ATTRIBUTE(supplier)!=[1])'

Testing

This project implements jest for testing. To run the tests, simply run

npm run test

Running tests in debug mode

You can add debugger in your code and run your tests in debug mode by running:

npm run test:debug

It will start the test in debug mode. And then you can go to chrome and go to the inspect tab: chrome://inspect, and you will see the debug session under Remote Targets. Click on it and it will open another chrome tab with the test running in a debugger. Once the test completes running, you need to close the chrome debug tab for the test to end on the terminal.

More details are here: Jest Troubleshooting