logic-engine
2.3.1
See the version list below for details.
dotnet add package logic-engine --version 2.3.1
NuGet\Install-Package logic-engine -Version 2.3.1
<PackageReference Include="logic-engine" Version="2.3.1" />
paket add logic-engine --version 2.3.1
#r "nuget: logic-engine, 2.3.1"
// Install logic-engine as a Cake Addin #addin nuget:?package=logic-engine&version=2.3.1 // Install logic-engine as a Cake Tool #tool nuget:?package=logic-engine&version=2.3.1
This documentation is in line with the active development, hence should be considered work in progress. To check the latest stable version please visit https://fabiolune.github.io/logic-engine/
Logic Engine
The logic-engine is a simple dotnet library to help introducing flexible logic systems.
It supports a generic set of rules that get compiled into executable code, allowing the possibility to dynamically change your business logic and adapt it to different needs without changing the core of your system.
The library deeply uses a functional programming approach implemented using Franco Melandri's amazing Tiny FP library.
The core functionalities are encapsulated in different components, both logical and functional.
The Rule
The rule object represents the building block for the system. A rule is an abstraction for a function acting on the value of a type and returning a boolean response.
Given a type to be applied to, a rule is defined by a set of fields
Property
: identifies the property against which to execute the evaluationOperator
: defines the operation to execute on the propertyValue
: identifies the value against which compare the result of the operator on the propertyCode
: the error code to be generated when the rules applied on an object fails (returnsfalse
)
The Operator
The Operator
can assume different possible values depending on the Property
it is applied to and on the value the result should be compared to.
Based on this considerations there are different types of operators. The rules categorization is also influenced by some implementation details.
Direct operators
These operators directly compare the Property
to the Value
considered as a constant:
Equal
: equality on value types (strings, numbers, ...)NotEqual
: inequality on value types (strings, numbers, ...)GreaterThan
: only applies to numbersGreaterThanOrEqual
: only applies to numbersLessThan
: only applies to numbersLessThanOrEqual
: only applies to numbers
public class MyClass
{
public string StringProperty {get; set;}
public int IntegerProperty {get; set;}
}
var stringRule = new Rule("StringProperty", OperatorType.Equal, "Some Value");
var integerRule = new Rule("IntegerProperty", OperatorType.Equal, "10");
sample rules with direct operators
Internal direct operators
Internal direct rules are similar to direct rules, but they are meant to be applied on values that are other fields of the same type; in this case Value
should correspond to the name of another field in the analyzed type:
InnerEqual
: equality between two value typed fieldsInnerNotEqual
: equality between two value typed fieldsInnerGreaterThan
: only applies whenProperty
andValue
are numbersInnerGreaterThanOrEqual
: only applies whenProperty
andValue
are numbersInnerLessThan
: only applies whenProperty
andValue
are numbersInnerLessThanOrEqual
: only applies whenProperty
andValue
are numbers
public class MyClass
{
public string StringProperty1 {get; set;}
public string StringProperty2 {get; set;}
public int IntegerProperty1 {get; set;}
public int IntegerProperty2 {get; set;}
}
var stringRule = new Rule("StringProperty1", OperatorType.InnerEqual, "StringProperty2");
var integerRule = new Rule("IntegerProperty1", OperatorType.InnerGreaterThan, "IntegerProperty2");
sample rules with internal direct operators
Enumerable operators
These rules apply to operand ot generic enumerable type:
Contains
: checks thatProperty
containsValue
NotContains
: checks thatProperty
does notValue
Overlaps
: checks thatProperty
has a non empty intersection withValue
NotOverlaps
: checks thatProperty
has an empty intersection withValue
public class MyClass
{
public IEnumerable<string> StringEnumerableProperty {get; set;}
}
var rule1 = new Rule("StringEnumerableProperty", OperatorType.Contains, "value");
var rule2 = new Rule("StringEnumerableProperty", OperatorType.Overlaps, "value1,value2");
sample rules with enumerable operators
Internal enumerable operators
These operators act on enumerable fields by comparing them against fields of the same type:
InnerContains
:InnerNotContains
:InnerOverlaps
:InnerNotOverlaps
:
public class MyClass
{
public IEnumerable<int> EnumerableProperty1 {get; set;}
public IEnumerable<int> EnumerableProperty2 {get; set;}
public int IntegerField {get; set;}
}
var rule1 = new Rule("EnumerableProperty1", OperatorType.InnerContains, "IntegerField");
var rule2 = new Rule("EnumerableProperty1", OperatorType.InnerOverlaps, "EnumerableProperty2");
sample rules for internal enumerable operators
Key-value operators
These operators act on dictionary-like objects:
ContainsKey
: checks that theProperty
contains the specfic key defined by theValue
NotContainsKey
: checks that theProperty
doesn't contain the specfic key defined by theValue
ContainsValue
: checks that the dictionaryProperty
contains a value defined by theValue
NotContainsValue
: checks that the dictionaryProperty
doesn't contain a value defined by theValue
KeyContainsValue
: checks that the dictionaryProperty
has a key with a specific valueNotKeyContainsValue
: checks that the dictionaryProperty
doesn't have a key with a specific value
public class MyClass
{
public IDictionary<string, int> DictProperty {get; set;}
}
var rule1 = new Rule("DictProperty", OperatorType.ContainsKey, "mykey");
var rule2 = new Rule("DictProperty", OperatorType.KeyContainsValue, "mykey[myvalue]");
sample rules for key-value enumerable operators
Inverse enumerable operators
These rules apply to scalars against enumerable fields:
IsContained
: checks thatProperty
is contained in a specific setIsNotContained
: checks thatProperty
is not contained in a specific set
public class MyClass
{
public int IntProperty {get; set;}
}
var rule1 = new Rule("IntProperty", OperatorType.IsContained, "1,2,3");
sample rules for inverse enumerable operators
RulesSet and RulesCatalog
A RulesSet
is basically a set of rules. From a functional point of view it represents a boolean typed function composed by a set of functions on a given type.
DEFINITION
A Rule
is satisfied by an item t
of type T
if the associated function f: T ──► bool
returns true if f(t)
is true
.
DEFINITION
A RulesSet
is satisfied by an item t
of type T
if and only if all the functions of the set are satisfied by t
.
A RulesCatalog
represents a set of RulesSet
, and functionally corresponds to a boolean typed function composed by a set of sets of functions on a given type.
DEFINITION
A RulesCatalog
is satisfied by an item t
of type T
if at least one of its RulesSet
is satisfied by t
.
From these considerations, it is clear that a RulesSet
corresponds to the logical AND
operator on its functions, and a RulesCatalog
corresponds to the logical OR
operator on its RulesSet
.
The Algebraic model
As discussed above, composite types RulesSet
and RulesCatalog
represent logical operations on the field of functions f: T ──► bool
; it seems than possible to define an algebraic model where catalogs can be added or multiplied together to generate more complex catalogs.
The sum of two RulesCatalog
objects is a RulesCatalog
with a set of RulesSet
obtained by simply concatenating the two sets of RulesSet
:
c1 = {rs1, rs2, rs3}
c2 = {rs4, rs5}
──► c1 + c2 = {rs1, rs2, rs3, rs4, rs5}
sum of two
RulesCatalog
The product of two catalogs is a catalog with a set of all the RulesSet
obtained concatenating a set of the first catalog with one of the second.
c1 = {rs1, rs2, rs3}
c2 = {rs4, rs5}
──► c1 * c2 = {(rs1*rs4), (rs1*rs5), (rs2*rs4), (rs2*rs5), (rs3*rs4), (rs3*rs5)}
product of two
RulesCatalog
The definition relies on the definition of product between two RulesSet
: its definition is simply a new RulesSet
whose rules are the concatenation of the rules in the two factors:
rs1 = {r1, r2, r3}
rs2 = {r4, r5}
──► rs1 * rs2 = {r1, r2, r3, r4, r5}
product of two
RulesSet
The compilers
The SingleRuleCompiler
is the component that parses and compiles a Rule
into executable code.
Every rule becomes an Option
of CompiledRule<T>
, an object capturing a Func<T, Either<string, Unit>>
: the None
status of the option corresponds to a Rule
that is not formally correct and hence cannot be compiled.
The monadic function notation captures the possible outputs of the function:
- the left type of the
Either
(string
) represents a non matching result represented by the code of the executed rule - the right type (
Unit
) represents insted a matching result for which no additional details are needed.
The RulesSetCompiler
transforms a RulesSet into a CompiledRulesSet<T>
, essentially a wrapper around an array of Func<T, Either<string, Unit>>
(all the rules of the RulesSet
that are not correct get filtered out).
The RulesCatalogCompiler
, finally, trasforms a full RulesCatalog
into a CompiledCatalog<T>
, a container for Func<T, Either<string, Unit>>[][]
(a bidimensional array of functions to represents the logical superposition of OR
operations on rules joined by a logical AND
)
The RulesManager
The RulesManager<T>
is responsible for the application of the rules functions to an item of type T
.
Given a RulesCatalog
, the manager exposes two main functionalities:
ItemSatisfiesRules
: it just returns a true or false boolean value that represents if the catalog is satisfied by the item under consideration or notItemSatisfiesRulesWithMessage
: this method is similar to the previous and returns a Unit value if the item satisfies the catalog; if not a set of all the codes associated to rules not matched is returned[^1]
The main difference between the two method is the circuit breaking approach: given the logical structure, as soon as a Rule
in a RuleSet
fails, ItemSatisfiesRules
will jump to the next one, while ItemSatisfiesRulesWithMessage
will still check all the rules in the set to collect all the failure codes[^2].
Additional methods of the manager allow to operate on an IEnumerable<T>
, in particular it is possible to:
- apply a filter based on the catalog
- extract the first item satisfying the catalog
The RulesSetManager
The RulesSetManager
instead, given a RulesSet
, evaulates, in order, all its rules on a specific item and returns the code of the first satisfied rule.
This can be useful in a scenario where, given a condition on an item, some specific operation should be executed (like a routing matching in a web api).
To ensure meaningful results, the RulesSetManager
requires its RulesSet
not to have rules with the same Code
.
How to install
If you are using nuget.org
you can add the dependency in your project using
dotnet add package logic-engine --version 2.1.0
To install the logic-engine library from GitHub's packages system please refer to the packages page.
[^1]: null or empty codes are removed because they don't carry reusable info [^2]: from a technical perspective this is obtained with a concrete implementation of the railway pattern
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- tiny-fp (>= 1.2.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
4.0.0 | 113 | 7/26/2024 |
3.1.0 | 362 | 8/24/2023 |
3.1.0-rc0004 | 140 | 8/14/2023 |
3.1.0-rc0003 | 131 | 8/12/2023 |
3.1.0-rc0002 | 136 | 8/11/2023 |
3.1.0-rc0001 | 128 | 8/10/2023 |
3.0.1 | 161 | 6/21/2023 |
3.0.0 | 178 | 6/20/2023 |
3.0.0-rc0001 | 129 | 6/20/2023 |
2.4.0 | 2,875 | 12/12/2022 |
2.3.1 | 448 | 3/17/2022 |
2.3.0 | 439 | 3/14/2022 |
2.2.2 | 440 | 3/9/2022 |
2.2.1 | 428 | 3/9/2022 |
2.2.0 | 418 | 3/8/2022 |
2.1.0 | 437 | 2/24/2022 |
2.0.0 | 438 | 2/20/2022 |
1.1.0 | 438 | 2/15/2022 |
1.0.0 | 495 | 2/14/2022 |