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:
Ina non-empty array,divideexactly two,removeOutputnovalue, a quantifier acondition. - 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.