Rule Cascade
LearnOperators

Comparison operators

eq, ne, lt, lte, gt, gte and between compare two values and return true or false.

An operator call is an object with op and args. Comparison operators take values and return true or false. eq and ne compare any two values. lt, lte, gt, gte and between compare numbers only.

Syntax

an operator call
{ op: <operator>, args: [<argument>, <argument>] }
OperatorArgumentsResult
eq, neany, anyEqual, or not equal. Deep: lists element by element, objects key by key
lt, lte, gt, gtenumber, numberLess than, at most, greater than, at least
betweennumber, low, highlow <= number <= high

Example

Seven rules check one order. The request orders zero items, in dollars, in a 40 kg parcel.

comparison.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.comparison, version: 1.0.0, title: Comparison operators }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: order.quantity.positive
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: gt, args: [{ var: data.quantity }, 0] }
    severity: error
    finding: { code: CMP-001, message: quantity.positive }
  - id: order.quantity.max
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: lte, args: [{ var: data.quantity }, 100] }
    severity: error
    finding: { code: CMP-002, message: quantity.max }
  - id: order.price.min
    kind: validation
    target: { entity: Order, field: /price }
    operations: [create]
    assert: { op: gte, args: [{ var: data.price }, 1] }
    severity: error
    finding: { code: CMP-003, message: price.min }
  - id: order.discount.below-price
    kind: validation
    target: { entity: Order, field: /discount }
    operations: [create]
    assert: { op: lt, args: [{ var: data.discount }, { var: data.price }] }
    severity: error
    finding: { code: CMP-004, message: discount.belowPrice }
  - id: order.currency.eur
    kind: validation
    target: { entity: Order, field: /currency }
    operations: [create]
    assert: { op: eq, args: [{ var: data.currency }, "EUR"] }
    severity: error
    finding: { code: CMP-005, message: currency.eur }
  - id: order.status.not-cancelled
    kind: validation
    target: { entity: Order, field: /status }
    operations: [create]
    assert: { op: ne, args: [{ var: data.status }, "cancelled"] }
    severity: error
    finding: { code: CMP-006, message: status.cancelled }
  - id: order.weight.range
    kind: validation
    target: { entity: Order, field: /weight }
    operations: [create]
    assert: { op: between, args: [{ var: data.weight }, 0.1, 30] }
    severity: error
    finding: { code: CMP-007, message: weight.range }
messages:
  en:
    quantity.positive: "Order at least one item."
    quantity.max: "You can order at most 100 items."
    price.min: "The price must be at least 1."
    discount.belowPrice: "The discount must be lower than the price."
    currency.eur: "We only accept orders in EUR."
    status.cancelled: "You cannot create a cancelled order."
    weight.range: "A parcel weighs between 0.1 and 30 kg."
tests:
  - name: zero items, dollars and a heavy parcel are denied
    entity: Order
    operation: create
    given:
      data: { quantity: 0, price: 10, discount: 2, currency: "USD", status: "new", weight: 40 }
    expect:
      decision: deny
      findings:
        - { rule: order.quantity.positive, fields: [/quantity] }
        - { rule: order.currency.eur, fields: [/currency] }
        - { rule: order.weight.range, fields: [/weight] }
  - name: a valid order is allowed
    entity: Order
    operation: create
    given:
      data: { quantity: 3, price: 10, discount: 2, currency: "EUR", status: "new", weight: 2 }
    expect: { decision: allow, findings: [] }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "quantity": 0,
    "price": 10,
    "discount": 2,
    "currency": "USD",
    "status": "new",
    "weight": 40
  }
}

Result, from the engine

Decisiondeny3 findings, server channel

  • CMP-001errorblockingOrder at least one item./quantity
  • CMP-005errorblockingWe only accept orders in EUR./currency
  • CMP-007errorblockingA parcel weighs between 0.1 and 30 kg./weight
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Common mistakes

  • lt, lte, gt, gte and between accept only numbers. { op: gt, args: ["5", 3] } is an evaluation error, not true.
  • eq never converts: 1 is not "1". Values of different types are never equal.
  • between includes both ends: 30 is between 0.1 and 30.

Exercise

Write one rule with between that allows 1 to 50 items per order. Check that 0 and 51 are denied and 50 is allowed.

Hint

between takes the value first, then the lowest and the highest allowed value.

Show answer
comparison.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.comparison, version: 1.0.0, title: Comparison operators }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: order.quantity.range
    kind: validation
    target: { entity: Order, field: /quantity }
    operations: [create]
    assert: { op: between, args: [{ var: data.quantity }, 1, 50] }
    severity: error
    finding: { code: CMP-001, message: quantity.range }
messages:
  en:
    quantity.range: "Order between 1 and 50 items."
tests:
  - name: fifty-one items are denied
    entity: Order
    operation: create
    given:
      data: { quantity: 51 }
    expect:
      decision: deny
      findings:
        - { rule: order.quantity.range, fields: [/quantity] }
  - name: zero items are denied
    entity: Order
    operation: create
    given:
      data: { quantity: 0 }
    expect:
      decision: deny
      findings:
        - { rule: order.quantity.range }
  - name: one and fifty items are allowed
    entity: Order
    operation: create
    given:
      data: { quantity: 50 }
    expect: { decision: allow, findings: [] }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "quantity": 51
  }
}

Result, from the engine

Decisiondeny1 finding, server channel

  • CMP-001errorblockingOrder between 1 and 50 items./quantity
Course overview

On this page