Table of Contents

Handle errors

Nearly everything that can be wrong with a rule is found when it loads, not on a random request. Evaluation itself never throws because of data: a null, a missing key or a division by zero each have a defined result.

Four documents, three kinds of failure

This loop loads four broken documents: text that isn't JSON, a document with a typo and a wrong value, a rule that names a member Checkout doesn't have, and one that calls a function nobody registered.

using RuleWright.Core;
using RuleWright.Execution;
using RuleWright.Json.SystemText;
using RuleWright.Serialization;

try
{
    LoadedRuleSet rules = engine.LoadRuleSet(json);
    RuleEvaluationResult result = engine.Evaluate(rules, new Checkout());
}
catch (RuleParseException ex)
{
    // Not well-formed JSON, or a number outside the representable range.
    Console.WriteLine($"Parse:       {ex.Message}");
}
catch (RuleValidationException ex)
{
    // Well-formed JSON that breaks the schema. Every problem, each with a JSON pointer.
    foreach (RuleValidationError error in ex.Errors)
    {
        Console.WriteLine($"Validation:  {error.Path}: {error.Message}");
    }
}
catch (RuleCompilationException ex)
{
    // A valid document that cannot bind: an unknown member, an unregistered function.
    Console.WriteLine($"Compilation: [{ex.RuleId}] {ex.Message}");
}

Output

Parse:       Invalid JSON: Expected start of a property name or value, but instead reached end of data. LineNumber: 0 | BytePositionInLine: 18.
Validation:  /actons: Unknown property 'actons'. Expected one of: id, description, priority, enabled, condition, actions, else, failureMessage, params, layout.
Validation:  /condition/value: 'value' must be a non-empty array for operator 'In'.
Compilation: [wrong-field] Rule 'wrong-field': field path 'Order.GrandTotal': member 'GrandTotal' was not found on type Order.
Compilation: [no-such-function] Rule 'no-such-function': custom function 'IsBusinessDay' is not registered. Register it with RuleWrightBuilder.RegisterFunction before loading the rule set.

The exceptions

All derive from RuleWright.Core.RuleWrightException.

Exception Namespace Thrown by Means
RuleParseException RuleWright.Serialization LoadRuleSet(string) The text isn't well-formed JSON, or a number is outside what .NET can represent (such as 1e400).
RuleValidationException RuleWright.Serialization LoadRuleSet(string) Well-formed JSON that breaks the schema, including a regular expression that doesn't parse. Errors lists every problem, each with a JSON pointer Path and a Message.
RuleCompilationException RuleWright.Execution LoadRuleSet, or the first Evaluate for a fact type A rule can't bind: an unregistered custom function (at load), or an unknown member or a constant of the wrong type on a typed fact (on the first evaluation). RuleId names the rule.
RegexMatchTimeoutException System.Text.RegularExpressions Evaluate A MatchesRegex pattern ran past the engine's timeout on this fact's text. See Accept rules you didn't write.
InvalidOperationException System LoadRuleSet(string), Validate The engine was built without UseJsonReader.
Tip

Validate first when a person is waiting. engine.Validate(json) returns the same errors as RuleValidationException without throwing, and reports malformed JSON as one error at the root. See Validate documents.

Why an unknown field fails late

LoadRuleSet doesn't know what you'll evaluate against. The same document can serve a Checkout class, a different class, or a dictionary. So a field path is checked when a rule is compiled for a type, which happens the first time Evaluate reaches that rule with that type. Do that at startup, with a representative fact, and a typo in a rule stops the deployment instead of failing the first real request:

using RuleWright.Core;
using RuleWright.Execution;
using RuleWright.Json.SystemText;
using RuleWright.Serialization;

// At startup: compile the rules for Checkout now, so a bad field path stops the deployment
// instead of failing the first real request.
engine.Evaluate(rules, new Checkout());

Compilation is per rule and lazy. A warm-up call compiles every enabled rule it evaluates, which is all of them unless the set stops at the first match. For a set that stops, warm up with a fact that matches none of its rules. Disabled rules are never compiled.

Evaluation is total

Once a rule set has loaded and compiled for a type, evaluating it can't fail because of the data in a fact:

  • A null anywhere along a path, or a missing dictionary key, follows the null rules.
  • A non-numeric operand to arithmetic, or a division or modulo by zero, gives null.
  • Built-in functions return false for a value of the wrong type.

The one exception is a regular expression that runs past its time limit. That is a problem with the pattern, surfaced on the data that exposed it.

Next

Work through a tutorial, or read how to write conditions.