Rule Cascade
LearnValues and types

Lists

Lists in data, list parameters (stringList and numberList), and checking membership with in.

A list is a JSON array. Data can hold lists, and so can parameters: a stringList parameter holds strings and a numberList parameter holds numbers. in checks whether a value is a member of a list. When a list is a message argument, its elements are joined with ", ".

Syntax

list parameters
params:
  shippingCountries: { type: stringList, default: [DE, FR, ES] }
  packSizes:         { type: numberList, default: [1, 6, 12] }
# in a rule:
assert: { op: in, args: [{ var: data.country }, { var: params.shippingCountries }] }

Example

The shop ships to three countries and sells packs of 1, 6 or 12. The request orders 5 bottles to Italy.

lists.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.lists, version: 1.0.0, title: "Lists" }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
params:
  shippingCountries:
    type: stringList
    default: [DE, FR, ES]
  packSizes:
    type: numberList
    default: [1, 6, 12]
rules:
  - id: order.country.shipped
    kind: validation
    target: { entity: Order, field: /country }
    operations: [create]
    assert: { op: in, args: [{ var: data.country }, { var: params.shippingCountries }] }
    severity: error
    finding: { code: LRN-LST-001, message: order.noShipping, args: { countries: { var: params.shippingCountries } } }
  - id: order.quantity.pack
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: in, args: [{ var: data.quantity }, { var: params.packSizes }] }
    severity: error
    finding: { code: LRN-LST-002, message: order.packSize }
messages:
  en:
    order.noShipping: "We ship only to {countries}."
    order.packSize: "Choose a pack of 1, 6 or 12."
tests:
  - name: no shipping to Italy, and 5 is not a pack size
    entity: Order
    operation: create
    given:
      data: { country: IT, quantity: 5 }
    expect:
      decision: deny
      findings:
        - { rule: order.country.shipped, fields: [/country], message: "We ship only to DE, FR, ES." }
        - { rule: order.quantity.pack, fields: [/quantity] }
  - name: six bottles to Spain
    entity: Order
    operation: create
    given:
      data: { country: ES, quantity: 6 }
    expect: { decision: allow, findings: [] }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "country": "IT",
    "quantity": 5
  }
}

Result, from the engine

Decisiondeny2 findings, server channel

  • LRN-LST-001errorblockingWe ship only to DE, FR, ES./country
  • LRN-LST-002errorblockingChoose a pack of 1, 6 or 12./quantity
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Common mistakes

  • Writing Norway as NO without quotes. YAML 1.1 reads it as false. Write "NO".
  • Mixing types in a list: in compares without coercion, so 6 is not in ["6"].
  • Writing a list literal in an expression ([1, 6, 12]). A bare list is not an expression; use a parameter, or the list operator.

Exercise

Start shipping to Italy (IT). Check that an order of 6 to Italy is allowed and one to Portugal is denied with the message "We ship only to DE, FR, ES, IT."

Hint

Only the default of shippingCountries changes. The message uses the list, so it changes by itself.

Show answer
lists.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.lists, version: 1.0.0, title: "Lists" }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
params:
  shippingCountries:
    type: stringList
    default: [DE, FR, ES, IT]
  packSizes:
    type: numberList
    default: [1, 6, 12]
rules:
  - id: order.country.shipped
    kind: validation
    target: { entity: Order, field: /country }
    operations: [create]
    assert: { op: in, args: [{ var: data.country }, { var: params.shippingCountries }] }
    severity: error
    finding: { code: LRN-LST-001, message: order.noShipping, args: { countries: { var: params.shippingCountries } } }
  - id: order.quantity.pack
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: in, args: [{ var: data.quantity }, { var: params.packSizes }] }
    severity: error
    finding: { code: LRN-LST-002, message: order.packSize }
messages:
  en:
    order.noShipping: "We ship only to {countries}."
    order.packSize: "Choose a pack of 1, 6 or 12."
tests:
  - name: Italy is in the list now
    entity: Order
    operation: create
    given:
      data: { country: IT, quantity: 6 }
    expect: { decision: allow, findings: [] }
  - name: Portugal is not
    entity: Order
    operation: create
    given:
      data: { country: PT, quantity: 6 }
    expect:
      decision: deny
      findings:
        - { rule: order.country.shipped, message: "We ship only to DE, FR, ES, IT." }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "country": "IT",
    "quantity": 6
  }
}

Result, from the engine

Decisionallow0 findings, server channel

Course overview

On this page