Operating the Rules Engine

docproc ships with a small, easy-to-use rules engine that allows many of its builtin processors to behave in certain ways, according to your message contents. The rules are, if not stated otherwise executed against the message’s content section.

Since docproc uses JSON heavily, rules are also expressed in a JSON notation and are organised as a simple JSON array:

[
    {
        ... # rule 1
    },
    {
        ... # rule 2
    },
    {
        ... # rule 3
    }
]

Rule Configuration

A rule typically consists of the following fields.

{
    "name":     "<optional name of the rule>",
    "path":     "<message content path to use for comparing or checking>",
    "op":       "<operator to use>",
    "value":    "<value to use for comparison>",
    "subrules": [ "<more nested rules>" ]
}
name
An optional name describing the rule. This is for maintenance purposes and does not have any effect on the rule, if provided or absent.
path
The message’s content element to check. Paths can be nested using a dotted notation.
op

The comparison operator to use. If not stated otherwise, the comparison will consider path being the left-hand and value the right-hand argument:

value-of-path <op> rule-value
value
The value to compare the path’s value against. value can be omitted, if the comparison operator is exists or not exists. If it is provided for those operators, it will be ignored.
subrules

A list of additional rules that have have to be tested. The rule as well as all its sub-rules have to match successfully to consider the rule as a whole as successful.

Please note that all subrules are evaluated before the rule itself is evaluated. Thus, the most inner subrule is the first being tested.

Setting Paths

Paths are always relative to the message’s content element and can use a dotted notation to access nested elements of a message. It is also possible to access array values using brackets and the required index number.

Let’s have a look at a few examples of configuring proper paths for rules. Given the following message

{
    "content": {
        "name": "John Doe",
        "age": 30,
        "address": {
            "street": "Example Lane 123",
            "zip": "10026",
            "city": "New York"
        },
        "netValues": [
            1000.00,
            453.00,
            -102.00,
            2
        ]
    }
}

you can access and check the age of John Doe being greater than 20 via

{
    "path": "age",
    "op": ">",
    "value": 20
}

Accessing nested elements is done by connecting the element and its sub-element with a dot. To check, if an address exists and if its city is New York, you can use address.city.

{
    "path": "address.city",
    "op": "eq",
    "value": "New York",
    "subrules": [
        {
            "path": "address",
            "op": "exists"
        }
    ]

}

Note

Subrules are evaluated before the rule itself is evaluated. Thus, if you think of multiple conditions that have to apply, you have to build them in a reverse order:

1st condition:      if an address exists
2nd condition:      and if its city name is "New York"

thus becomes:

2nd (outer) rule:   and if its city name is "New York"
1st (inner) rule:   if an address exists

Make use of name to explain more complex rules to keep your maintenance efforts at a minimum.

You can access array values using brackets [] and the value’s index. Indexing starts at zero, so that the first element can be accessed by [0], the second by [1] and so on.

{
    "path": "netValues[2]",
    "op": ">=",
    "value": 500,
}

Operators

Existence

To check, if a given path of a message exists (it may contain nil values or empty strings) or not, use the exists and not exists operators:

{
    "path": "address",
    "op": "exists"
}

{
    "path": "alternativeName",
    "op": "not exists"
}

Any value provided on the rule, will be ignored.

Equality

The following operators check, if the provided values are equal:

=, ==, eq, equals

{
    "path": "name",
    "op": "=",
    "value": "John Doe",
}

Their counterparts, to check for inequality, are:

<>, !=, neq, not equals

{
    "path": "name",
    "op": "neq",
    "value": "Jane Janeson",
}
Size Comparators

Values can also be compared by size. This is straightforward for numeric values. If you use size comparators on strings, please note that the strings are compared lexicographically.

To check, if the left-hand value is greater than the right-hand value:

>, gt, greater than

{
    "path": "age",
    "op": ">",
    "value": 21,
}

To check, if the left-hand value is greater than or equal to the right-hand value:

>=, gte, greater than or equals

{
    "path": "netValues[0]",
    "op": "gte",
    "value": 500,
}

Their counterparts for checking the other way around:

<, lt, less than

{
    "path": "age",
    "op": "<",
    "value": 50,
}

and

<=, lte, less than or equals

{
    "path": "netValues[3]",
    "op": "less than or equals",
    "value": -1.0,
}
String Matching

To check, if a string contains another string or not, use the following operators:

contains, not contains

{
    "path": "name",
    "op": "contains",
    "value": "Doe",
}
{
    "path": "name",
    "op": "not contains",
    "value": "Jane",
}

As for the size comparators, this checks, if the left-hand value contains the right-hand value. To check the other way around, use

in, not in

instead.

{
    "path": "name",
    "op": "in",
    "value": "John Doe, Jane Doe, or their kids",
}

{
    "path": "address.city",
    "op": "not in",
    "value": "London, Vancouver, Washington, Halifax",
}