Reading facts
A rule can name a fact directly, amount > 1000, and for a fact you know is present, that is the
right thing to write.
For everything else there is in, and the difference is not convenience.
Why the readers exist
Section titled “Why the readers exist”A bare fact reference aborts the execution when the fact is missing. Every rule still on the agenda is evaluated every cycle, and a failed evaluation ends the run, however much output other rules already wrote. One optional field turns into a lost execution.
The readers cannot do that:
when in.Eq("status", "submitted") // false when status is absentwhen status == "submitted" // aborts the run when status is absentin.Eq returns false for a fact that is not there. It has no failure mode, so it needs no guard.
That is the whole design: a reader that cannot raise does not need a rule author to remember
anything.
Dotted paths
Section titled “Dotted paths”A key is a path. "invariant.doctype" reads the doctype member of the invariant object, at
any depth:
rule ReadNested salience 10 { when in.Has("customer.address.city") then out.Set("city", in.Str("customer.address.city", "")); out.Set("exact_total", in.EqNum("totals.amount", 1200.0)); out.Set("total", in.Double("totals.amount", 0.0));}{ "customer": { "address": { "city": "Chennai" } }, "totals": { "amount": 1200.0 }}{ "city": "Chennai", "exact_total": true, "total": 1200 }A dotted path is also the way to reach a nested member at all: dot access on a fact
(customer.address) and a second level of brackets (customer["address"]["city"]) both abort the
execution. See The rule language.
There is no separate GetPath surface, deliberately, ten functions where five will do is ten
things to choose wrongly between.
| Situation | Behaviour |
|---|---|
| A literal dot inside a key | Escape it with a backslash: "release\\.notes" reads the key release.notes |
| A key whose split would give an empty segment | Treated as literal, not as a path |
A trailing dot, like "Work Phone Ext." | Safe with no escaping, for the reason above |
rule LiteralDots salience 10 { when true then out.Set("ext", in.Str("Work Phone Ext.", "")); out.Set("release", in.Str("release\\.notes", ""));}{ "Work Phone Ext.": "42", "release.notes": "beta" }{ "ext": "42", "release": "beta" }Presence and equality
Section titled “Presence and equality”| Call | Returns | Notes |
|---|---|---|
in.Has(path) | bool | Whether anything resolves at that path |
in.Eq(path, value) | bool | String equality: the value is read as a string first, so in.Eq("amount", "12") is true for 12. False when absent |
in.EqNum(path, value) | bool | Numeric equality against a float: write 1200.0. False when absent |
in.EqInt(path, value) | bool | Numeric equality against an integer: write 3. False when absent |
These are the ones to reach for in a when. They are total: every input produces a true or a
false, never a failure.
rule RouteSubmitted "submitted claims go to review" salience 100 { when in.Eq("status", "submitted") && in.EqNum("amount", 0.0) == false then out.Set("queue", "review");}{ "status": "submitted", "amount": 1200.0 }{ "queue": "review" }Typed readers
Section titled “Typed readers”Each takes a default, which is what it returns when nothing is at the path. No guard, no abort.
| Call | Returns |
|---|---|
in.Str(path, default) | String |
in.Int(path, default) | Integer |
in.Double(path, default) | Float. The default must be a float literal: 0.0 |
in.Bool(path, default) | Boolean |
in.Date(path) | A date; the zero date (IsZero()) when absent or unparseable |
in.Map(path) | A nested object; nothing when absent |
in.Rows(path) | A list of records; nothing when absent |
in.Value(path) | The typed value, or null (IsNull()) |
rule ReadInvoice salience 10 { when in.Has("invoice") then out.Set("currency", in.Str("invoice.currency", "UNKNOWN")); out.Set("total", in.Double("invoice.total", 0.0)); out.Set("lines", in.Rows("invoice.lines"));}{ "invoice": { "total": 1500.0, "lines": [ { "sku": "A-1", "qty": 2.0 }, { "sku": "B-7", "qty": 1.0 } ] }}{ "currency": "UNKNOWN", "total": 1500, "lines": [ { "sku": "A-1", "qty": 2 }, { "sku": "B-7", "qty": 1 } ]}Passing the default explicitly is the point, the rule states what absence means instead of leaving
it to whatever the zero value happens to be. A nothing result (in.Map, in.Rows on a missing path)
written with out.Set leaves the key out of the output.
A value that is present is converted, not replaced by the default. A number reads as text through
in.Str, numeric text reads as a number through in.Int and in.Double, and a value that does not
convert reads as the zero value:
rule Conversions salience 10 { when true then out.Set("missing", in.Int("retries", 3)); out.Set("numeric_text", in.Int("count", 3)); out.Set("not_a_number", in.Int("status", 3));}{ "count": "7", "status": "submitted" }{ "missing": 3, "numeric_text": 7, "not_a_number": 0 }Deprecated forms
Section titled “Deprecated forms”in.GetStr, in.GetInt, in.GetFloat, in.GetBool, in.GetMap and in.Get still work: each is
the reader above with the zero value as its default, in.GetStr(k) is in.Str(k, ""). They load
with a warning. Write in.Str, in.Int, in.Double, in.Bool, in.Map and in.Value.
The scratch memory calls, in.MemSet, in.MemGet, in.MemGetStr, in.MemGetRows, in.MemDelete,
in.MemClear and in.GetMemAsMap, are deprecated too: stage a value with out.Set and read it
back with out.*. in.NewMapWithValues is deprecated for map.Of.
Choosing
Section titled “Choosing”| You want to | Write |
|---|---|
| Compare a fact you know is present | The bare name: amount > 1000 |
| Compare a fact that might be absent | in.Eq / in.EqNum / in.EqInt |
| Read a value with a meaningful fallback | in.Str(path, "UNKNOWN") |
| Read a nested member | A dotted path: in.Double("invoice.total", 0.0) |
| Ask whether something arrived at all | in.Has(path) |
See also
Section titled “See also”- Writing rules: structure, salience and the output envelope
- Writing output:
out, dotted keys and attributes - Functions: the complete callable surface