Discover the vocabulary
A rule editor has to know which operators exist, which take a value, and which functions this engine has. Don't hard-code that list. RuleSchemaCatalog and engine.FunctionCatalog hand it to you at run time, derived from the same tables the parser and validator use, so they can't disagree with what the engine accepts.
The whole vocabulary
using RuleWright.Serialization;
Console.WriteLine("Condition operators");
foreach (ConditionOperatorInfo op in RuleSchemaCatalog.ConditionOperators)
{
Console.WriteLine($" {op.JsonName,-19} value: {op.ValueKind,-9} expression left: {op.AllowsExpressionLeft,-5} needs name: {op.RequiresFunctionName}");
}
Console.WriteLine("Logical operators");
foreach (LogicalOperatorInfo op in RuleSchemaCatalog.LogicalOperators)
{
Console.WriteLine($" {op.JsonName,-4} children: {op.MinChildren}..{op.MaxChildren?.ToString() ?? "n"}");
}
Console.WriteLine("Expression operators");
foreach (ExpressionOperatorInfo op in RuleSchemaCatalog.ExpressionOperators)
{
Console.WriteLine($" {op.JsonName,-9} {op.Category,-12} operands: {op.MinOperands}..{op.MaxOperands?.ToString() ?? "n"}");
}
Console.WriteLine("Action types");
foreach (ActionTypeInfo action in RuleSchemaCatalog.ActionTypes)
{
Console.WriteLine($" {action.Name,-15} {action.Effect,-8} requires value: {action.RequiresValue}");
}
Output
Condition operators
Equals value: Scalar expression left: True needs name: False
NotEquals value: Scalar expression left: True needs name: False
GreaterThan value: Scalar expression left: True needs name: False
GreaterThanOrEqual value: Scalar expression left: True needs name: False
LessThan value: Scalar expression left: True needs name: False
LessThanOrEqual value: Scalar expression left: True needs name: False
Contains value: Text expression left: True needs name: False
StartsWith value: Text expression left: True needs name: False
EndsWith value: Text expression left: True needs name: False
MatchesRegex value: Text expression left: True needs name: False
In value: Array expression left: True needs name: False
NotIn value: Array expression left: True needs name: False
IsNull value: None expression left: True needs name: False
IsNotNull value: None expression left: True needs name: False
custom value: Custom expression left: False needs name: True
Any value: Condition expression left: False needs name: False
All value: Condition expression left: False needs name: False
None value: Condition expression left: False needs name: False
Logical operators
AND children: 1..n
OR children: 1..n
NOT children: 1..1
Expression operators
add Arithmetic operands: 2..n
subtract Arithmetic operands: 2..2
multiply Arithmetic operands: 2..n
divide Arithmetic operands: 2..2
modulo Arithmetic operands: 2..2
negate Arithmetic operands: 1..1
concat Text operands: 2..n
coalesce NullHandling operands: 2..n
count Collection operands: 1..1
Action types
setOutput Replace requires value: True
addToOutput Add requires value: True
appendToOutput Append requires value: True
removeOutput Remove requires value: False
Any needs an element condition: True
What each entry tells an editor
| Catalog | Entry | Tells you |
|---|---|---|
ConditionOperators |
ConditionOperatorInfo |
JsonName; ValueKind (which value editor to show: None, Scalar, Text, Array, Custom or Condition); RequiresValue; RequiresFunctionName; RequiresElementCondition; AllowsExpressionLeft. |
LogicalOperators |
LogicalOperatorInfo |
JsonName, and MinChildren / MaxChildren (null means no limit). |
ExpressionOperators |
ExpressionOperatorInfo |
JsonName, Category, and MinOperands / MaxOperands. |
ActionTypes |
ActionTypeInfo |
Name, Effect (Replace, Add, Append, Remove), and RequiresValue. |
Look one up by name with TryGetConditionOperator, TryGetExpressionOperator or TryGetActionType:
using RuleWright.Serialization;
if (RuleSchemaCatalog.TryGetConditionOperator("Any", out ConditionOperatorInfo any))
{
Console.WriteLine($"Any needs an element condition: {any.RequiresElementCondition}");
}
Registrations are per engine
The operators are fixed. Three parts of the vocabulary depend on what each engine registered, so they live on the engine:
| Registration | Names | Catalog |
|---|---|---|
Custom functions (custom) |
engine.RegisteredFunctions |
engine.FunctionCatalog: Description and ValueKind per function |
Value functions (call) |
engine.RegisteredValueFunctions |
engine.ValueFunctionCatalog: Description and RequiredOperandCount per function |
Custom actions (action type) |
engine.RegisteredActions |
The names; validation accepts them alongside the built-in four |
using RuleWright.Core;
using RuleWright.Execution;
using RuleWright.Json.SystemText;
RuleWrightEngine engine = new RuleWrightBuilder()
.UseJsonReader(new SystemTextJsonReader())
.RegisterFunction("IsBusinessDay", (field, value) =>
field is DateTime d && d.DayOfWeek is not (DayOfWeek.Saturday or DayOfWeek.Sunday))
.RegisterValueFunction(new RoundToFunction())
.RegisterAction("setIfHigher", context => { /* see Custom actions */ })
.Build();
Console.WriteLine($"custom functions: {string.Join(", ", engine.RegisteredFunctions)}");
Console.WriteLine($"value functions: {string.Join(", ", engine.RegisteredValueFunctions)}");
Console.WriteLine($"custom actions: {string.Join(", ", engine.RegisteredActions)}");
foreach (RuleValueFunctionDescriptor function in engine.ValueFunctionCatalog)
{
Console.WriteLine($" {function.Name}: operands {function.RequiredOperandCount?.ToString() ?? "any"} — {function.Description}");
}
Output
custom functions: IsBusinessDay
value functions: RoundTo
custom actions: setIfHigher
RoundTo: operands 2 — Rounds a number to a digit count.
Building an editor
A rule editor usually needs three things from the server, and RuleWright provides all three:
- The vocabulary, from the two catalogs above, to build its palettes and value editors.
- Validation as you type, from
engine.Validate, with JSON pointers to underline. - A test run, from
EvaluatewithEnableTrace, to show which conditions a sample fact passes.
The web API tutorial serves the first two over HTTP. The RuleWright repository has a complete example: a Blazor WebAssembly rule builder with a drag-and-drop canvas, running the real engine in the browser. It stores node positions in each rule's layout key, which the engine ignores and the content hash excludes, so moving a node never recompiles a rule.