regola is a rule evaluator written in Java.
Disclaimer: This library is in development mode and there could be breaking changes as new versions are released.
- be fast
- be reusable and extensible
- be well documented
- have high test coverage
- allow for efficient evaluation against data retrieved from external data sources
- have Json rules conversion builtin in the library
- run on Java 11+
- Add to your maven dependencies:
<dependency>
<groupId>com.adobe.abp</groupId>
<artifactId>regola</artifactId>
<version>0.0.24</version>
</dependency>- Write a rule
var rule = new StringRule();
rule.setKey("MARKET_SEGMENT");
rule.setOperator(Operator.EQUALS);
rule.setValue("COM");
rule.setDescription("The market segment should be COM");You could also use fluent setters:
var rule = new StringRule()
.setValue("COM")
.setOperator(Operator.EQUALS)
.setKey("MARKET_SEGMENT")
.setDescription("The market segment should be COM");For some rules, you could also pass some parameters directly via the constructor for conciseness:
var rule = new StringRule("MARKET_SEGMENT", Operator.EQUALS, "COM");
rule.setDescription("The market segment should be COM");- Define how data for the "MARKET_SEGMENT"
keymust be retrieved
var factsResolver = new SimpleFactsResolver<>();
factsResolver.addFact(new Fact<>("MARKET_SEGMENT", data -> "COM"));- Evaluate
var evaluationResult = new Evaluator().evaluate(rule, factsResolver);
// The evaluation is an asynchronous process, so the associated CompletableFuture must be executed to get a result.
// The following line returns the result value when complete, or throws an (unchecked) exception if completed exceptionally.
evaluationResult.status().join();
var result = evaluationResult.snapshot();The result object will contain information on whether the evaluation was valid or not, plus any relevant information about the rule run.
- If we were to print the
resultas json
{
"result" : "VALID",
"type" : "STRING",
"operator" : "EQUALS",
"key" : "MARKET_SEGMENT",
"description": "The market segment should be COM",
"expectedValue" : "COM",
"actualValue": "COM"
}Boolean rules are used to combine rules together.
The "And Rule" is used to combine multiple rules together, where all the rules must evaluate to VALID for it to evaluate to VALID.
{
"type" : "AND",
"rules" : [
// list of other rules
]
}| A | B | A && B |
|---|---|---|
| VALID | VALID | VALID |
| VALID | INVALID | INVALID |
| VALID | MAYBE | MAYBE |
| VALID | FAILED | FAILED |
| VALID | OPERATION_NOT_SUPPORTED | OPERATION_NOT_SUPPORTED |
The AND rule is commutative: A && B = B && A.
An empty AND evaluates to VALID. This follows the identity of conjunction: with no subrules, there is nothing to invalidate the expression.
The "Or Rule" is used to combine multiple rules together, where at least one rule must evaluate to VALID for it to evaluate to VALID.
{
"type" : "OR",
"rules" : [
// list of other rules
]
}| A | B | A || B |
|---|---|---|
| VALID | any | VALID |
| INVALID | INVALID | INVALID |
The order of precedence for non-VALID results is: FAILED, OPERATION_NOT_SUPPORTED, INVALID, MAYBE.
So, for example: FAILED || INVALID == FAILED, while MAYBE || INVALID == INVALID and so on.
The OR rule is commutative: A || B = B || A.
An empty OR evaluates to INVALID. This follows the identity of disjunction: with no subrules, there is nothing that can make the expression valid.
The "Not Rule" is used to negate the result of another rule.
{
"type" : "NOT",
"rule" : {
// rule to negate
}
}| A | !A |
|---|---|
| VALID | INVALID |
| INVALID | VALID |
| MAYBE | MAYBE |
| FAILED | FAILED |
| OPERATION_NOT_SUPPORTED | OPERATION_NOT_SUPPORTED |
A NOT rule must contain a non-null rule. If the operand is missing, the evaluation is treated as malformed configuration and fails.
The "Exists Rule" is used to check whether a fact exists or not.
{
"type": "EXISTS",
"key": "foo"
}| Key | Fact | Result |
|---|---|---|
| "foo" | { "foo": "bar" } | VALID |
| "foo" | { "foo": null } | INVALID |
| "foo" | { "not_foo": "bar" } | INVALID |
The "Constant Rule" is used to always return the same result, regardless of the fact.
{
"type": "CONSTANT",
"result": "VALID" // INVALID, MAYBE, FAILED, OPERATION_NOT_SUPPORTED
}These rules evaluate facts against a value set in the rule. When creating a value-based rule, you must also set an operator (e.g., EQUALS, GREATER_THAN, IN, etc...).
The relationship between facts, values and operators is: fact OPERATOR value.
So, for example a rule having value Cat, operator EQUALS, and evaluated against the fact Dog reads as: Dog EQUALS Cat (false).
A rule having value Cat, operator CONTAINS, and evaluated against the fact [Dog, Bird, Cat] reads as: [Dog, Bird, Cat] CONTAINS Cat (true).
The "Number Rule" is used to evaluate facts against a number. The number can be an integer or a double.
{
"type": "NUMBER",
"operator": "GREATER_THAN",
"key": "foo",
"value": 7 // you can also have 7.0
}Supported operators: EQUALS, GREATER_THAN, GREATER_THAN_EQUAL, LESS_THAN, LESS_THAN_EQUAL, CONTAINS, DIVISIBLE_BY
Integer-Double comparisons between the rule's value and the data provided by the fact work for all operators except CONTAINS.
The DIVISIBLE_BY operator is valid only for integer values since divisibility is not well-defined for floating-point numbers.
| Rule Value | Operator | Fact | Result |
|---|---|---|---|
| 7 | EQUALS | 7 | VALID |
| 7 | GREATER_THAN | 7 | INVALID |
| 7 | GREATER_THAN | 8 | VALID |
| 7 | GREATER_THAN_EQUAL | 7 | VALID |
| 7 | GREATER_THAN_EQUAL | 7.5 | VALID |
| 7.4 | GREATER_THAN | 7.5 | VALID |
| 7.5 | GREATER_THAN | 7.5 | INVALID |
| 7 | CONTAINS | [ 6, 7, 8] | VALID |
| 7 | CONTAINS | [ 6, 8] | INVALID |
| 7 | CONTAINS | [ 6, 7.0, 8] | INVALID |
| 7.0 | CONTAINS | [ 6, 7.0, 8] | VALID |
| 7 | DIVISIBLE_BY | 7 | VALID |
| 7 | DIVISIBLE_BY | 8 | INVALID |
| 0 | DIVISIBLE_BY | 8 | FAILED |
| 7 | DIVISIBLE_BY | 0 | VALID |
| any number | supported operator | null | INVALID |
| null | supported operator | any number | INVALID |
When using the CONTAINS operator, the Fact must be a Set of numbers.
{
"type": "STRING",
"operator": "EQUALS",
"key": "foo",
"value": "bar"
}Supported operators: EQUALS, GREATER_THAN, GREATER_THAN_EQUAL, LESS_THAN, LESS_THAN_EQUAL, CONTAINS
Comparisons are case-sensitive.
| Rule Value | Operator | Fact | Result |
|---|---|---|---|
| "bar" | EQUALS | "bar" | VALID |
| "bar" | EQUALS | "BAR" | INVALID |
| "bar" | EQUALS | "baz" | INVALID |
| "bar" | GREATER_THAN | "car" | VALID |
| "bar" | GREATER_THAN_EQUAL | "bar" | VALID |
| "bar" | GREATER_THAN | "are" | INVALID |
| "bar" | STARTS_WITH | "bar" | VALID |
| "bar" | STARTS_WITH | "barfoo" | VALID |
| "bar" | STARTS_WITH | "foobar" | INVALID |
| "bar" | ENDS_WITH | "bar" | VALID |
| "bar" | ENDS_WITH | "foobar" | VALID |
| "bar" | ENDS_WITH | "barfoo" | INVALID |
| "bar" | CONTAINS | ["are", "bar", "baz"] | VALID |
| "bar" | CONTAINS | ["are", "baz"] | INVALID |
| any string | supported operator | null | INVALID |
| null | supported operator | any string | INVALID |
A "Set rule" is a rule that evaluates a fact against a set of values. The set rule has two operators: IN and INTERSECTS.
IN: evaluates to VALID if the fact is a subset of the rule's value. INTERSECTS: evaluates to VALID if the fact and the rule's value have at least one item in common.
{
"type": "SET",
"operator": "IN",
"key": "foo",
"values": ["bar", "baz"]
}Supported operators: IN, INTERSECTS
String comparisons are case-sensitive.
| Rule Value | Operator | Fact | Result |
|---|---|---|---|
| ["bar", "baz"] | IN | "bar" | VALID |
| ["bar", "baz"] | IN | "waz" | INVALID |
| [1, 2, 3] | IN | 2 | VALID |
| [1, 2, 3] | IN | 4 | INVALID |
| ["bar", "baz"] | IN | [] | INVALID |
| [] | IN | [] | VALID |
| [] | IN | ["bar"] | INVALID |
| ["bar", "baz"] | IN | ["bar"] | VALID |
| ["bar", "baz"] | IN | ["waz"] | INVALID |
| ["bar", "baz"] | IN | ["bar", "waz"] | INVALID |
| ["bar", "baz"] | INTERSECTS | "bar" | VALID |
| ["bar", "baz"] | INTERSECTS | "waz" | INVALID |
| ["bar", "baz"] | INTERSECTS | ["bar", "waz"] | VALID |
| ["bar", "baz"] | INTERSECTS | ["wiz", "waz"] | INVALID |
| ["bar", "baz"] | INTERSECTS | [] | INVALID |
| [] | INTERSECTS | [] | INVALID |
| any set | any operator | null | INVALID |
The Set rule can be used on more complex objects too.
SetRule<Patient> setRule = new SetRule<>();
setRule.setKey("PATIENT");
setRule.setOperator(Operator.IN);
setRule.setValues(bob, alice);Where bob and alice are instances of Patient.
You must also make sure that the Patient class overrides the equals and hashCode methods.
Regola expects the equals method to perform a commutative comparison between objects.
Also note that regola does not support JSON serialization/deserialization for SET rules with complex objects.
The "Date Rule" is a rule that evaluates a fact against a date value.
{
"type" : "DATE",
"operator" : "GREATER_THAN",
"key" : "foo",
"value" : "2021-07-07T12:30:00Z"
}Supported operators: EQUALS, GREATER_THAN, GREATER_THAN_EQUAL, LESS_THAN, LESS_THAN_EQUAL, CONTAINS
Dates must be parsable to an OffsetDateTime:
A date-time with an offset from UTC/Greenwich in the ISO-8601 calendar system
| Rule Value | Operator | Fact | Result |
|---|---|---|---|
| "2021-07-07T12:30:00Z" | EQUALS | "2021-07-07T12:30:00Z" | VALID |
| "2021-07-07T12:30:00Z" | GREATER_THAN | "2022-07-07T12:30:00Z" | VALID |
| "2021-07-07T12:30:00Z" | GREATER_THAN | "2020-07-07T12:30:00Z" | INVALID |
| "2021-07-07T12:30:00Z" | LESS_THAN | "2020-07-07T12:30:00Z" | VALID |
| any date | supported operator | null | INVALID |
| null | supported operator | any date | INVALID |
A "Null Rule" is a rule that evaluates a fact against a null value.
{
"type": "NULL",
"key": "foo"
}| Key | Fact | Result |
|---|---|---|
| "foo" | null | VALID |
| "foo" | "foo" | INVALID |
Rules can be combined using the boolean rules: AND, OR, NOT.
{
"type" : "AND",
"rules" : [ {
"type" : "STRING",
"operator" : "EQUALS",
"key" : "foo",
"value" : "bar"
}, {
"type" : "OR",
"rules" : [ {
"type" : "EXISTS",
"key" : "waz"
}, {
"type" : "NUMBER",
"operator" : "EQUALS",
"key" : "foobar",
"value" : 21
}]
}]
}Rules can be set to be ignored, so that the evaluation of AND/OR/NOT rules does not take them into account.
This matters for empty or effectively-empty boolean rules too:
ANDwith no subrules evaluates toVALID.ORwith no subrules evaluates toINVALID.ORwhere every subrule is present but marked asignoredevaluates toVALID, because all configured subrules are excluded from the final decision.NOTwith an ignored subrule evaluates toVALID.NOTwith no subrule fails.
Example of a rule marked as ignored:
{
"type" : "STRING",
"operator" :