LearnRule basics
Rules for every item of a list
forEach runs a validation rule once per element of an array, with the element bound to item.
forEach names an array in the entity. The rule runs once for every element, in order, with the
element bound to item. The target field is relative to the element, and each finding points at
the element that failed: /items/1/qty is the quantity of the second line.
Syntax
- id: order.item.qty
kind: validation
target: { entity: Order, field: /qty } # relative to each element
forEach: /items # the array
assert: { op: gte, args: [{ var: item.qty }, 1] }Example
The second line of the order has a quantity of zero.
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.for-each, version: 1.0.0, title: "For each item" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.item.qty
kind: validation
target: { entity: Order, field: /qty }
operations: [create]
forEach: /items
assert: { op: gte, args: [{ var: item.qty }, 1] }
severity: error
finding: { code: LRN-EACH-001, message: item.qtyTooLow, args: { sku: { var: item.sku } } }
messages:
en:
item.qtyTooLow: "Order at least one of {sku}."
tests:
- name: the second line has no quantity
entity: Order
operation: create
given:
data:
items:
- { sku: A-1, qty: 2, price: 5 }
- { sku: B-2, qty: 0, price: 7 }
expect:
decision: deny
findings:
- { rule: order.item.qty, fields: [/items/1/qty], message: Order at least one of B-2. }
- name: every line has a quantity
entity: Order
operation: create
given:
data:
items:
- { sku: A-1, qty: 2, price: 5 }
expect: { decision: allow, findings: [] }{
"entity": "Order",
"operation": "create",
"data": {
"items": [
{
"sku": "A-1",
"qty": 2,
"price": 5
},
{
"sku": "B-2",
"qty": 0,
"price": 7
}
]
}
}Result, from the engine
Decisiondeny1 finding, server channel
LRN-EACH-001errorblockingOrder at least one of B-2./items/1/qty
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.
Common mistakes
- Writing the full path in the target (
/items/qty). The target is relative to the element:/qty. - Using
data.items.qtyin the assert. Inside the rule, the element isitem. - An absent or
nullarray counts as empty: the rule runs zero times.
Exercise
Add a rule: every line needs a price above zero. Use the code LRN-EACH-002 and the message "Give
{sku} a price above zero."
Hint
Copy the rule. Change the target field to /price and the assert to item.price greater than 0.
Show answer
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.for-each, version: 1.0.0, title: "For each item" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.item.qty
kind: validation
target: { entity: Order, field: /qty }
operations: [create]
forEach: /items
assert: { op: gte, args: [{ var: item.qty }, 1] }
severity: error
finding: { code: LRN-EACH-001, message: item.qtyTooLow, args: { sku: { var: item.sku } } }
- id: order.item.price
kind: validation
target: { entity: Order, field: /price }
operations: [create]
forEach: /items
assert: { op: gt, args: [{ var: item.price }, 0] }
severity: error
finding: { code: LRN-EACH-002, message: item.priceMissing, args: { sku: { var: item.sku } } }
messages:
en:
item.qtyTooLow: "Order at least one of {sku}."
item.priceMissing: "Give {sku} a price above zero."
tests:
- name: the first line is free
entity: Order
operation: create
given:
data:
items:
- { sku: A-1, qty: 2, price: 0 }
- { sku: B-2, qty: 1, price: 7 }
expect:
decision: deny
findings:
- { rule: order.item.price, fields: [/items/0/price], message: Give A-1 a price above zero. }
- name: every line has a quantity and a price
entity: Order
operation: create
given:
data:
items:
- { sku: A-1, qty: 2, price: 5 }
expect: { decision: allow, findings: [] }{
"entity": "Order",
"operation": "create",
"data": {
"items": [
{
"sku": "A-1",
"qty": 2,
"price": 0
},
{
"sku": "B-2",
"qty": 1,
"price": 7
}
]
}
}Result, from the engine
Decisiondeny1 finding, server channel
LRN-EACH-002errorblockingGive A-1 a price above zero./items/0/price