Computed values
Compute rules fill in or recompute fields before validation runs.
A compute rule assigns values to fields. Each assignment has a field, a value expression and
a mode: default writes only when the field is empty, always overwrites it. Compute rules run
first, so validation sees the computed values. Each assignment comes back as a value effect.
Syntax
- id: <rule id>
kind: compute
target: { entity: Order }
operations: [create]
assign:
- { field: /shipping, mode: default, value: 4 }
- { field: /total, mode: always, value: <expression> }expect:
decision: allow
findings: []
effects:
- { type: value, field: /total, value: 29 }Example
Express delivery sets shipping to 10, and the total is recomputed as quantity × price + shipping.
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.computed, version: 1.0.0, title: "Computed values" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.compute.totals
kind: compute
target: { entity: Order }
operations: [create]
assign:
- field: /shipping
mode: default
value: { op: if, args: [{ op: eq, args: [{ var: data.express }, true] }, 10, 4] }
- field: /total
mode: always
value: { op: add, args: [{ op: mul, args: [{ var: data.quantity }, { var: data.price }] }, { var: data.shipping }] }
- id: order.total.max
kind: validation
target: { entity: Order, field: /total }
operations: [create]
assert: { op: lte, args: [{ var: data.total }, 1000] }
severity: error
finding: { code: LRN-CMP-001, message: order.totalMax }
messages:
en:
order.totalMax: "An order may not cost more than 1000."
tests:
- name: express shipping is added to the total
entity: Order
operation: create
given:
data: { quantity: 2, price: 9.5, express: true }
expect:
decision: allow
findings: []
effects:
- { type: value, field: /shipping, value: 10 }
- { type: value, field: /total, value: 29 }
- name: a shipping price the caller gave is kept, the total is always recomputed
entity: Order
operation: create
given:
data: { quantity: 2, price: 9.5, shipping: 0, total: 1 }
expect:
decision: allow
findings: []
effects:
- { type: value, field: /total, value: 19 }
- name: validation sees the computed total
entity: Order
operation: create
given:
data: { quantity: 200, price: 9.5 }
expect:
decision: deny
findings:
- { rule: order.total.max }{
"entity": "Order",
"operation": "create",
"data": {
"quantity": 2,
"price": 9.5,
"express": true
}
}Result, from the engine
Decisionallow0 findings, server channel
- computed value
/shipping=10 - computed value
/total=29
Common mistakes
- Using
alwaysfor a field the user may set.defaultkeeps what the caller sent. - Two rules that assign different values to one field. That is a conflict, an evaluation error (see Field states).
Exercise
Add a discount that defaults to 0, and subtract it from the total. Test that a discount of 5
gives a total of 24.
Hint
Add an assignment for /discount with mode: default and value 0 before /total, then subtract data.discount in the total.
Show answer
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.computed, version: 1.0.0, title: "Computed values" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.compute.totals
kind: compute
target: { entity: Order }
operations: [create]
assign:
- field: /shipping
mode: default
value: { op: if, args: [{ op: eq, args: [{ var: data.express }, true] }, 10, 4] }
- field: /discount
mode: default
value: 0
- field: /total
mode: always
value: { op: sub, args: [{ op: add, args: [{ op: mul, args: [{ var: data.quantity }, { var: data.price }] }, { var: data.shipping }] }, { var: data.discount }] }
- id: order.total.max
kind: validation
target: { entity: Order, field: /total }
operations: [create]
assert: { op: lte, args: [{ var: data.total }, 1000] }
severity: error
finding: { code: LRN-CMP-001, message: order.totalMax }
messages:
en:
order.totalMax: "An order may not cost more than 1000."
tests:
- name: express shipping is added to the total
entity: Order
operation: create
given:
data: { quantity: 2, price: 9.5, express: true }
expect:
decision: allow
findings: []
effects:
- { type: value, field: /shipping, value: 10 }
- { type: value, field: /discount, value: 0 }
- { type: value, field: /total, value: 29 }
- name: a shipping price the caller gave is kept, the total is always recomputed
entity: Order
operation: create
given:
data: { quantity: 2, price: 9.5, shipping: 0, total: 1 }
expect:
decision: allow
findings: []
effects:
- { type: value, field: /total, value: 19 }
- name: validation sees the computed total
entity: Order
operation: create
given:
data: { quantity: 200, price: 9.5 }
expect:
decision: deny
findings:
- { rule: order.total.max }
- name: a discount lowers the total
entity: Order
operation: create
given:
data: { quantity: 2, price: 9.5, express: true, discount: 5 }
expect:
decision: allow
findings: []
effects:
- { type: value, field: /total, value: 24 }{
"entity": "Order",
"operation": "create",
"data": {
"quantity": 2,
"price": 9.5,
"express": true
}
}Result, from the engine
Decisionallow0 findings, server channel
- computed value
/shipping=10 - computed value
/discount=0 - computed value
/total=29