The four rule kinds
validation, state, compute and action rules — what each one produces.
Every rule has an id, a kind, a target and the operations it applies to. The kind decides
what the rule produces. A validation rule produces a finding, a state rule produces field
states, a compute rule produces values and an action rule produces commands. title and
description are for people; the engine does not read them.
Syntax
- { id: <id>, kind: compute, ..., assign: [ { field, value } ] }
- { id: <id>, kind: state, ..., when: <expr>, effects: [ { field, set } ] }
- { id: <id>, kind: validation, ..., assert: <expr>, severity, finding }
- { id: <id>, kind: action, ..., enforcement: server, commands: [ { name, type, idempotencyKey } ] }
- { id: <id>, kind: x-<name>, ... } # an extension: conforming engines ignore it| Kind | Body | Produces |
|---|---|---|
compute | assign | Computed values, written before validation runs |
state | when, effects | Field states (visible, enabled, required, read-only) while when is true |
validation | assert, severity, finding | A finding when assert is false |
action | commands, enforcement: server | Commands, only after an allowed server evaluation |
x-* | anything | Nothing. Reserved for extensions |
Example
One rule of each kind. The order costs 400 three times, so the computed total is too high.
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.rule-kinds, version: 1.0.0, title: The four rule kinds }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.total.compute
kind: compute
title: Work out the total
description: The total is the price times the quantity.
target: { entity: Order, field: /total }
operations: [create]
assign:
- { field: /total, value: { op: mul, args: [{ var: data.price }, { var: data.quantity }] }, mode: always }
- id: order.gift-message.show
kind: state
title: Show the gift message for gift-wrapped orders
target: { entity: Order, field: /giftMessage }
operations: [create]
when: { op: eq, args: [{ var: data.giftWrap }, true] }
effects:
- { field: /giftMessage, set: { visible: true } }
- id: order.total.max
kind: validation
title: At most 1000 per order
target: { entity: Order, field: /total }
operations: [create]
assert: { op: lte, args: [{ var: data.total }, 1000] }
severity: error
finding: { code: LRN-KND-001, message: order.totalTooHigh }
- id: order.placed.announce
kind: action
title: Announce the new order
target: { entity: Order }
operations: [create]
enforcement: server
commands:
- name: order.placed
type: event
idempotencyKey: [{ var: data.id }]
- id: order.audit.note
kind: x-audit
target: { entity: Order }
operations: [create]
x-note: An extension kind. Engines ignore it.
messages:
en:
order.totalTooHigh: "An order may cost at most 1000."
tests:
- name: a gift order of 1200 is denied and announces nothing
entity: Order
operation: create
given:
data: { id: o-1, price: 400, quantity: 3, giftWrap: true }
expect:
decision: deny
findings:
- { rule: order.total.max, fields: [/total] }
effects:
- { type: value, field: /total, value: 1200 }
- { type: state, field: /giftMessage, set: { visible: true } }
commands: []
- name: an order of 800 is allowed and announced
entity: Order
operation: create
given:
data: { id: o-2, price: 400, quantity: 2 }
expect:
decision: allow
findings: []
effects:
- { type: value, field: /total, value: 800 }
commands: [order.placed]{
"entity": "Order",
"operation": "create",
"data": {
"id": "o-1",
"price": 400,
"quantity": 3,
"giftWrap": true
}
}Result, from the engine
Decisiondeny1 finding, server channel
LRN-KND-001errorblockingAn order may cost at most 1000./total
- computed value
/total=1200 - field state
/giftMessage={"visible":true}
Common mistakes
- Expecting commands from a denied request. Action rules run only when the decision is
allow. - Leaving out
enforcement: serveron an action rule. The compiler refuses it: commands never run in a browser. - Reusing a rule
id. Every id is unique in a ruleset and everything it inherits.
Exercise
Gift-wrapped orders show the gift message field. Make the field required as well, and update the golden test to expect it.
Hint
A state effect's set can hold more than one property: visible, enabled, required, readOnly.
Show answer
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.rule-kinds, version: 1.0.0, title: The four rule kinds }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.total.compute
kind: compute
title: Work out the total
description: The total is the price times the quantity.
target: { entity: Order, field: /total }
operations: [create]
assign:
- { field: /total, value: { op: mul, args: [{ var: data.price }, { var: data.quantity }] }, mode: always }
- id: order.gift-message.show
kind: state
title: Show the gift message for gift-wrapped orders
target: { entity: Order, field: /giftMessage }
operations: [create]
when: { op: eq, args: [{ var: data.giftWrap }, true] }
effects:
- { field: /giftMessage, set: { visible: true, required: true } }
- id: order.total.max
kind: validation
title: At most 1000 per order
target: { entity: Order, field: /total }
operations: [create]
assert: { op: lte, args: [{ var: data.total }, 1000] }
severity: error
finding: { code: LRN-KND-001, message: order.totalTooHigh }
- id: order.placed.announce
kind: action
title: Announce the new order
target: { entity: Order }
operations: [create]
enforcement: server
commands:
- name: order.placed
type: event
idempotencyKey: [{ var: data.id }]
- id: order.audit.note
kind: x-audit
target: { entity: Order }
operations: [create]
x-note: An extension kind. Engines ignore it.
messages:
en:
order.totalTooHigh: "An order may cost at most 1000."
tests:
- name: a gift order of 1200 is denied and announces nothing
entity: Order
operation: create
given:
data: { id: o-1, price: 400, quantity: 3, giftWrap: true }
expect:
decision: deny
findings:
- { rule: order.total.max, fields: [/total] }
effects:
- { type: value, field: /total, value: 1200 }
- { type: state, field: /giftMessage, set: { visible: true, required: true } }
commands: []
- name: an order of 800 is allowed and announced
entity: Order
operation: create
given:
data: { id: o-2, price: 400, quantity: 2 }
expect:
decision: allow
findings: []
effects:
- { type: value, field: /total, value: 800 }
commands: [order.placed]{
"entity": "Order",
"operation": "create",
"data": {
"id": "o-1",
"price": 400,
"quantity": 3,
"giftWrap": true
}
}Result, from the engine
Decisiondeny1 finding, server channel
LRN-KND-001errorblockingAn order may cost at most 1000./total
- computed value
/total=1200 - field state
/giftMessage={"visible":true,"required":true}