Rule Cascade
LearnEffects

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

a state rule
- 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.

field-states.ruleset.yaml
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 } }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "giftWrap": true
  }
}

Result, from the engine

Decisionallow0 findings, server channel

  • field state /giftMessage = {"visible":true,"required":true}
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

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:

field-states-conflict.ruleset.yaml
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 }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "giftWrap": true,
    "express": true
  }
}

Result, from the engine

Decisionallow0 findings, server channel

  • field state /giftMessage = {"visible":true}
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Common mistakes

  • Treating required: true as 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
field-states.ruleset.yaml
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 } }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "giftWrap": true
  }
}

Result, from the engine

Decisionallow0 findings, server channel

  • field state /giftMessage = {"visible":true,"required":true}
Course overview

On this page