Field states
State rules tell a form which fields to show, require, enable or lock.
A state rule sets field properties while its when is true: visible, enabled, required and
readOnly. Each comes back as a state effect, which a form uses to show or lock fields. When
when is false, the field keeps its default state.
Syntax
- id: <rule id>
kind: state
target: { entity: Order }
operations: [create, update]
when: <expression>
effects:
- { field: /giftMessage, set: { visible: true, required: true } }Example
Gift wrapping shows the gift message field and makes it required.
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.field-states, version: 1.0.0, title: "Field states" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.gift.message
kind: state
target: { entity: Order }
operations: [create, update]
when: { op: eq, args: [{ var: data.giftWrap }, true] }
effects:
- { field: /giftMessage, set: { visible: true, required: true } }
- id: order.shipped.lock
kind: state
target: { entity: Order }
operations: [update]
when: { op: eq, args: [{ var: data.status }, "shipped"] }
effects:
- { field: /country, set: { readOnly: true } }
- { field: /notes, set: { enabled: false } }
tests:
- name: gift wrapping shows the gift message field
entity: Order
operation: create
given:
data: { giftWrap: true }
expect:
decision: allow
findings: []
effects:
- { type: state, field: /giftMessage, set: { visible: true, required: true } }
- name: a shipped order locks the country and the notes
entity: Order
operation: update
given:
data: { status: "shipped" }
original: { status: "paid" }
expect:
decision: allow
findings: []
effects:
- { type: state, field: /country, set: { readOnly: true } }
- { type: state, field: /notes, set: { enabled: false } }{
"entity": "Order",
"operation": "create",
"data": {
"giftWrap": true
}
}Result, from the engine
Decisionallow0 findings, server channel
- field state
/giftMessage={"visible":true,"required":true}
Conflicts
Two rules may set the same field property to different values. With conflictPolicy: fail (the
default) the second rule is an evaluation error. With conflictPolicy: priority the rule with the
higher priority wins silently; equal priorities are still an error. Here the gift rule has
priority 10 and wins:
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.field-conflict, version: 1.0.0, title: "Two rules, one field" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
conflictPolicy: priority
rules:
- id: order.gift.show
kind: state
target: { entity: Order }
operations: [create]
priority: 10
when: { op: eq, args: [{ var: data.giftWrap }, true] }
effects:
- { field: /giftMessage, set: { visible: true } }
- id: order.express.hide
kind: state
target: { entity: Order }
operations: [create]
when: { op: eq, args: [{ var: data.express }, true] }
effects:
- { field: /giftMessage, set: { visible: false } }
tests:
- name: both rules set the same field
entity: Order
operation: create
given:
data: { giftWrap: true, express: true }
expect:
decision: allow
findings: []
effects:
- { type: state, field: /giftMessage, set: { visible: true }, rule: order.gift.show }{
"entity": "Order",
"operation": "create",
"data": {
"giftWrap": true,
"express": true
}
}Result, from the engine
Decisionallow0 findings, server channel
- field state
/giftMessage={"visible":true}
Common mistakes
- Treating
required: trueas validation. It is a hint for the form; add a validation rule to deny a request without the field.
Exercise
Make /deliveryDate required when express delivery is chosen. Add a golden test that expects
the state effect.
Hint
Add a state rule whose when checks that data.express equals true, and the effect { field: /deliveryDate, set: { required: true } }.
Show answer
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.field-states, version: 1.0.0, title: "Field states" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: order.gift.message
kind: state
target: { entity: Order }
operations: [create, update]
when: { op: eq, args: [{ var: data.giftWrap }, true] }
effects:
- { field: /giftMessage, set: { visible: true, required: true } }
- id: order.shipped.lock
kind: state
target: { entity: Order }
operations: [update]
when: { op: eq, args: [{ var: data.status }, "shipped"] }
effects:
- { field: /country, set: { readOnly: true } }
- { field: /notes, set: { enabled: false } }
- id: order.express.date
kind: state
target: { entity: Order }
operations: [create, update]
when: { op: eq, args: [{ var: data.express }, true] }
effects:
- { field: /deliveryDate, set: { required: true } }
tests:
- name: gift wrapping shows the gift message field
entity: Order
operation: create
given:
data: { giftWrap: true }
expect:
decision: allow
findings: []
effects:
- { type: state, field: /giftMessage, set: { visible: true, required: true } }
- name: a shipped order locks the country and the notes
entity: Order
operation: update
given:
data: { status: "shipped" }
original: { status: "paid" }
expect:
decision: allow
findings: []
effects:
- { type: state, field: /country, set: { readOnly: true } }
- { type: state, field: /notes, set: { enabled: false } }
- name: express orders need a delivery date
entity: Order
operation: create
given:
data: { express: true }
expect:
decision: allow
findings: []
effects:
- { type: state, field: /deliveryDate, set: { required: true } }{
"entity": "Order",
"operation": "create",
"data": {
"giftWrap": true
}
}Result, from the engine
Decisionallow0 findings, server channel
- field state
/giftMessage={"visible":true,"required":true}