Table of Contents

Validate documents

engine.Validate(json) checks a document against the rule schema and returns every problem it finds, each with a JSON pointer to the offending value. It never throws for a bad document, so you can bind it straight to an editor or a pull-request check.

A draft with eight mistakes

{
  "name": "Draft from the editor",
  "rules": [
    {
      "id": "big-order",
      "condition": { "field": "Order.Total", "operator": "GreaterThen", "value": 200 },
      "actions": [ { "type": "setOutput", "target": "Review", "value": true } ]
    },
    {
      "id": "restricted",
      "condition": { "field": "Order.Lines", "operator": "Any" },
      "actons": [
        { "type": "appendToOutput", "target": "Checks", "value": "age" }
      ]
    },
    {
      "id": "discount",
      "condition": { "field": "Customer.Tier", "operator": "In", "value": [] },
      "actions": [
        {
          "type": "setOutput",
          "target": "Discount",
          "value": {
            "op": "divide",
            "operands": [ { "field": "Order.Total" } ]
          }
        },
        { "type": "removeOutput", "target": "Review", "value": true }
      ]
    },
    {
      "id": "big-order",
      "condition": { "field": "$root.Total", "operator": "IsNotNull" }
    }
  ]
}
using System.Text.RegularExpressions;
using RuleWright.Core;
using RuleWright.Execution;
using RuleWright.Json.SystemText;
using RuleWright.Serialization;

string json = File.ReadAllText("invalid-rules.json");

RuleSetValidationResult validation = engine.Validate(json);
if (!validation.IsValid)
{
    foreach (RuleValidationError error in validation.Errors)
    {
        Console.WriteLine($"{error.Path}");
        Console.WriteLine($"    {error.Message}");
    }
}

Output

/rules/0/condition/operator
    Unknown operator 'GreaterThen'. Expected one of: Equals, NotEquals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, Contains, StartsWith, EndsWith, MatchesRegex, In, NotIn, IsNull, IsNotNull, custom, Any, All, None.
/rules/1/actons
    Unknown property 'actons'. Expected one of: id, description, priority, enabled, condition, actions, else, failureMessage, params, layout.
/rules/1/condition
    'condition' (applied to each element) is required for operator 'Any'.
/rules/2/condition/value
    'value' must be a non-empty array for operator 'In'.
/rules/2/actions/0/value/operands
    Operator 'divide' requires exactly 2 operands.
/rules/2/actions/1/value
    'value' is not allowed for action type "removeOutput".
/rules/3/condition/field
    '$root.Total' is not a valid field path: the '$' prefix is reserved, and "$" (the element a quantifier is testing) is the only form currently defined.
/rules/3/id
    Duplicate rule id 'big-order'.

Malformed JSON: IsValid=False, path ""

Every problem is reported, not just the first, and each Path is a JSON pointer (RFC 6901) into the document, so an editor can underline the exact value.

Malformed JSON

Text that isn't JSON at all comes back as a single error at the root (Path is empty), not an exception, so one code path handles both:

using System.Text.RegularExpressions;
using RuleWright.Core;
using RuleWright.Execution;
using RuleWright.Json.SystemText;
using RuleWright.Serialization;

RuleSetValidationResult broken = engine.Validate("{ \"id\": ");
Console.WriteLine();
Console.WriteLine($"Malformed JSON: IsValid={broken.IsValid}, path \"{broken.Errors[0].Path}\"");

What validation checks

  • Unknown keys are errors. The vocabulary is closed, so "actons" is reported instead of silently producing a rule that fires and writes nothing.
  • Operators, action types and expression operators are known, and each gets the operands it needs: In a non-empty array, divide exactly two, removeOutput no value, a quantifier a condition.
  • Rule ids are present and unique in the set.
  • Field paths are well formed, and the reserved $ prefix is used only as "$", inside a quantifier.
  • Regular expressions parse.
  • Decision tables have one cell per column in every row.

What it can't check without a fact type or an engine: whether a field path exists on your class (found on the first Evaluate), and whether a custom function is registered (found by LoadRuleSet). See Handle errors.

Validate or LoadRuleSet?

Validate(json) LoadRuleSet(json)
Bad document Returns IsValid = false and Errors Throws RuleValidationException with the same Errors
Malformed JSON One error at "" Throws RuleParseException
Unregistered function Not checked Throws RuleCompilationException
Returns RuleSetValidationResult a LoadedRuleSet ready to evaluate

LoadRuleSet validates first, so you never need to call both on the happy path. Call Validate where a person will read the errors.

The JSON Schema

The same contract is published as a JSON Schema (draft 2020-12) for editors and build tools: download rule-schema.json. In VS Code, map it to your rule files in settings.json:

"json.schemas": [
  { "fileMatch": [ "rules/*.json" ], "url": "./schemas/rule-schema.json" }
]

The schema catches structure and vocabulary as you type. Validate remains the authority: it also checks things a schema can't express, such as unique rule ids and whether a regular expression compiles.

Next

Trace why a rule fired.