LearnOperators
List operators
list, in, all, some, none, sum, map and filter build and inspect lists.
List operators build lists and look inside them. The collection operators (all, some, none,
sum, map, filter) evaluate their second argument once per element, with the element bound to
item. A missing list counts as empty.
Syntax
{ op: in, args: [<value>, { op: list, args: ["DE", "FR"] }] }
{ op: all, args: [{ var: data.items }, <predicate using item>] }
{ op: sum, args: [{ var: data.items }, { var: item.qty }] }| Operator | Arguments | Result |
|---|---|---|
list | any number | A list of the arguments |
in | any, list | Boolean: the value equals one element |
all, some, none | list, predicate | Boolean. Every element is evaluated: no short cut |
sum | list, expression | Sum of the expression over the elements; 0 for an empty list |
map | list, expression | A list of the expression's value for each element |
filter | list, predicate | The elements for which the predicate is true |
Example
The request ships to the US and contains a sale item.
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.list, version: 1.0.0, title: List operators }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.country.supported
kind: validation
target: { entity: Order, field: /country }
operations: [create]
assert: { op: in, args: [{ var: data.country }, { op: list, args: ["DE", "FR", "ES"] }] }
severity: error
finding: { code: LST-001, message: country.supported }
- id: order.items.quantity
kind: validation
target: { entity: Order, field: /items }
operations: [create]
assert: { op: all, args: [{ var: data.items }, { op: gt, args: [{ var: item.qty }, 0] }] }
severity: error
finding: { code: LST-002, message: items.quantity }
- id: order.gift-wrap.needs-gift-item
kind: validation
target: { entity: Order, field: /giftWrap }
operations: [create]
when: { op: eq, args: [{ var: data.giftWrap }, true] }
assert: { op: some, args: [{ var: data.items }, { op: eq, args: [{ var: item.sku }, "GIFT"] }] }
severity: error
finding: { code: LST-003, message: giftWrap.needsItem }
- id: order.tags.not-blocked
kind: validation
target: { entity: Order, field: /tags }
operations: [create]
assert: { op: none, args: [{ var: data.tags }, { op: eq, args: [{ var: item }, "blocked"] }] }
severity: error
finding: { code: LST-004, message: tags.blocked }
- id: order.quantity.sum
kind: validation
target: { entity: Order, field: /quantity }
operations: [create]
assert: { op: eq, args: [{ var: data.quantity }, { op: sum, args: [{ var: data.items }, { var: item.qty }] }] }
severity: error
finding: { code: LST-005, message: quantity.sum }
- id: order.items.no-sale
kind: validation
target: { entity: Order, field: /items }
operations: [create]
assert:
op: not
args:
- { op: in, args: ["SALE", { op: map, args: [{ var: data.items }, { var: item.sku }] }] }
severity: error
finding: { code: LST-006, message: items.noSale }
- id: order.items.expensive
kind: validation
target: { entity: Order, field: /items }
operations: [create]
assert:
op: lte
args:
- { op: len, args: [{ op: filter, args: [{ var: data.items }, { op: gt, args: [{ var: item.price }, 100] }] }] }
- 2
severity: error
finding: { code: LST-007, message: items.expensive }
messages:
en:
country.supported: "We deliver to DE, FR and ES."
items.quantity: "Every item needs a quantity above 0."
giftWrap.needsItem: "Gift wrap needs a GIFT item in the order."
tags.blocked: "This order is blocked."
quantity.sum: "The quantity must be the sum of the item quantities."
items.noSale: "Sale items cannot be ordered online."
items.expensive: "An order may have at most two items above 100."
tests:
- name: a delivery to the US with a sale item
entity: Order
operation: create
given:
data:
country: "US"
quantity: 3
giftWrap: false
tags: ["new"]
items:
- { sku: "A-1", qty: 2, price: 10 }
- { sku: "SALE", qty: 1, price: 5 }
expect:
decision: deny
findings:
- { rule: order.country.supported }
- { rule: order.items.no-sale }
- name: a wrapped gift to Germany
entity: Order
operation: create
given:
data:
country: "DE"
quantity: 2
giftWrap: true
tags: []
items:
- { sku: "A-1", qty: 1, price: 120 }
- { sku: "GIFT", qty: 1, price: 3 }
expect: { decision: allow, findings: [] }{
"entity": "Order",
"operation": "create",
"data": {
"country": "US",
"quantity": 3,
"giftWrap": false,
"tags": [
"new"
],
"items": [
{
"sku": "A-1",
"qty": 2,
"price": 10
},
{
"sku": "SALE",
"qty": 1,
"price": 5
}
]
}
}Result, from the engine
Decisiondeny2 findings, server channel
LST-001errorblockingWe deliver to DE, FR and ES./countryLST-006errorblockingSale items cannot be ordered online./items
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.
Common mistakes
- A YAML list is not an expression. Write
{ op: list, args: ["DE", "FR"] }, not["DE", "FR"]. - Inside a collection operator,
itemis the current element. For a list of strings,itemitself is the string:{ var: item }. allof an empty list istrue. Add alencheck if the list must not be empty.
Exercise
Every item must cost at least 1, and the items together (qty times price) may be worth at most 500. Write two rules.
Hint
all with gte checks each price. sum with mul of item.qty and item.price gives the value of the items.
Show answer
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.list, version: 1.0.0, title: List operators }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.items.price-min
kind: validation
target: { entity: Order, field: /items }
operations: [create]
assert: { op: all, args: [{ var: data.items }, { op: gte, args: [{ var: item.price }, 1] }] }
severity: error
finding: { code: LST-001, message: items.priceMin }
- id: order.items.value-max
kind: validation
target: { entity: Order, field: /items }
operations: [create]
assert:
op: lte
args:
- { op: sum, args: [{ var: data.items }, { op: mul, args: [{ var: item.qty }, { var: item.price }] }] }
- 500
severity: error
finding: { code: LST-002, message: items.valueMax }
messages:
en:
items.priceMin: "Every item costs at least 1."
items.valueMax: "The items may be worth at most 500 together."
tests:
- name: items worth 600 are denied
entity: Order
operation: create
given:
data:
items:
- { sku: "A-1", qty: 3, price: 200 }
expect:
decision: deny
findings:
- { rule: order.items.value-max }
- name: a free item is denied
entity: Order
operation: create
given:
data:
items:
- { sku: "A-1", qty: 1, price: 0 }
expect:
decision: deny
findings:
- { rule: order.items.price-min }
- name: items worth 500 are allowed
entity: Order
operation: create
given:
data:
items:
- { sku: "A-1", qty: 2, price: 200 }
- { sku: "B-2", qty: 1, price: 100 }
expect: { decision: allow, findings: [] }{
"entity": "Order",
"operation": "create",
"data": {
"items": [
{
"sku": "A-1",
"qty": 3,
"price": 200
}
]
}
}Result, from the engine
Decisiondeny1 finding, server channel
LST-002errorblockingThe items may be worth at most 500 together./items