logic-engine 2.4.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package logic-engine --version 2.4.0                
NuGet\Install-Package logic-engine -Version 2.4.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="logic-engine" Version="2.4.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add logic-engine --version 2.4.0                
#r "nuget: logic-engine, 2.4.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install logic-engine as a Cake Addin
#addin nuget:?package=logic-engine&version=2.4.0

// Install logic-engine as a Cake Tool
#tool nuget:?package=logic-engine&version=2.4.0                

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

GitHub CI codecov

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 evaluation
  • Operator: defines the operation to execute on the property
  • Value: identifies the value against which compare the result of the operator on the property
  • Code: the error code to be generated when the rules applied on an object fails (returns false)

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 numbers
  • GreaterThanOrEqual: only applies to numbers
  • LessThan: only applies to numbers
  • LessThanOrEqual: 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 fields
  • InnerNotEqual: equality between two value typed fields
  • InnerGreaterThan: only applies when Property and Value are numbers
  • InnerGreaterThanOrEqual: only applies when Property and Value are numbers
  • InnerLessThan: only applies when Property and Value are numbers
  • InnerLessThanOrEqual: only applies when Property and Value 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 that Property contains Value
  • NotContains: checks that Property does not Value
  • Overlaps: checks that Property has a non empty intersection with Value
  • NotOverlaps: checks that Property has an empty intersection with Value
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 the Property contains the specfic key defined by the Value
  • NotContainsKey: checks that the Property doesn't contain the specfic key defined by the Value
  • ContainsValue: checks that the dictionary Property contains a value defined by the Value
  • NotContainsValue: checks that the dictionary Property doesn't contain a value defined by the Value
  • KeyContainsValue: checks that the dictionary Property has a key with a specific value
  • NotKeyContainsValue: checks that the dictionary Property 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 that Property is contained in a specific set
  • IsNotContained: checks that Property 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 RulesSets.

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 containing 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 ok 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 on sets)

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 not
  • ItemSatisfiesRulesWithMessage: 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 <version>

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 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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 111 7/26/2024
3.1.0 358 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,873 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