Rule Cascade
LearnOperators

Logic operators

and, or, not and if combine conditions and choose between values.

Logic operators combine booleans. and is true when every argument is true, or when one is. not turns true into false. if picks its second or third argument, depending on the first.

Syntax

and, or, not, if
{ op: and, args: [<boolean>, <boolean>, ...] }
{ op: or,  args: [<boolean>, <boolean>, ...] }
{ op: not, args: [<boolean>] }
{ op: if,  args: [<condition>, <value if true>, <value if false>] }
OperatorArgumentsResult
and, orany number of booleansBoolean. Stops at the first argument that decides. and [] is true, or [] is false
notbooleanBoolean
ifboolean, any, anyThe second or the third argument. Only that one is evaluated

Example

The request has a gift message without gift wrap, and asks for express delivery to France.

logic.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.logic, version: 1.0.0, title: Logic operators }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: order.gift-message.needs-wrap
    kind: validation
    target: { entity: Order, field: /giftMessage }
    operations: [create]
    assert:
      { op: or, args: [
        { op: eq, args: [{ var: data.giftWrap }, true] },
        { op: eq, args: [{ var: data.giftMessage }, null] }
      ] }
    severity: error
    finding: { code: LOG-001, message: gift.needsWrap }
  - id: order.express.germany-only
    kind: validation
    target: { entity: Order, field: /express }
    operations: [create]
    assert:
      op: not
      args:
        - { op: and, args: [
            { op: eq, args: [{ var: data.express }, true] },
            { op: ne, args: [{ var: data.country }, "DE"] }
          ] }
    severity: error
    finding: { code: LOG-002, message: express.germanyOnly }
  - id: order.shipping.price
    kind: validation
    target: { entity: Order, field: /shipping }
    operations: [create]
    assert:
      op: eq
      args:
        - { var: data.shipping }
        - { op: if, args: [{ op: eq, args: [{ var: data.express }, true] }, 9.5, 4.5] }
    severity: error
    finding: { code: LOG-003, message: shipping.price }
messages:
  en:
    gift.needsWrap: "A gift message needs gift wrap."
    express.germanyOnly: "Express delivery is only available in Germany."
    shipping.price: "Shipping costs 4.5, or 9.5 for express delivery."
tests:
  - name: a gift message without wrap, express to France
    entity: Order
    operation: create
    given:
      data: { giftMessage: "Happy birthday", giftWrap: false, express: true, country: "FR", shipping: 9.5 }
    expect:
      decision: deny
      findings:
        - { rule: order.gift-message.needs-wrap }
        - { rule: order.express.germany-only }
  - name: a wrapped gift, standard shipping
    entity: Order
    operation: create
    given:
      data: { giftMessage: "Happy birthday", giftWrap: true, express: false, country: "FR", shipping: 4.5 }
    expect: { decision: allow, findings: [] }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "giftMessage": "Happy birthday",
    "giftWrap": false,
    "express": true,
    "country": "FR",
    "shipping": 9.5
  }
}

Result, from the engine

Decisiondeny2 findings, server channel

  • LOG-001errorblockingA gift message needs gift wrap./giftMessage
  • LOG-002errorblockingExpress delivery is only available in Germany./express
Try it YourselfOpens this ruleset and request in the playground. Nothing to install.

Common mistakes

  • The arguments must be booleans. { op: and, args: [1, true] } is an evaluation error.
  • if needs exactly three arguments. There is no else if: nest another if in the third.
  • or stops at the first true. Put the cheap or safe test first.

Exercise

Coupons do not apply to express orders. Write a rule that denies an order with a coupon when express is true. An express order without a coupon is allowed.

Hint

The rule passes when there is no coupon, or when the order is not express. Use or and not.

Show answer
logic.ruleset.yaml
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.logic, version: 1.0.0, title: Logic operators }
scope:
  - { level: organization, id: learn }
entities:
  Order:
    schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
  - id: order.coupon.not-express
    kind: validation
    target: { entity: Order, field: /coupon }
    operations: [create]
    assert:
      op: or
      args:
        - { op: eq, args: [{ var: data.coupon }, null] }
        - { op: not, args: [{ op: eq, args: [{ var: data.express }, true] }] }
    severity: error
    finding: { code: LOG-001, message: coupon.notExpress }
messages:
  en:
    coupon.notExpress: "Coupons do not apply to express orders."
tests:
  - name: a coupon on an express order is denied
    entity: Order
    operation: create
    given:
      data: { coupon: "SAVE10", express: true }
    expect:
      decision: deny
      findings:
        - { rule: order.coupon.not-express, fields: [/coupon] }
  - name: a coupon on a standard order is allowed
    entity: Order
    operation: create
    given:
      data: { coupon: "SAVE10", express: false }
    expect: { decision: allow, findings: [] }
  - name: express without a coupon is allowed
    entity: Order
    operation: create
    given:
      data: { express: true }
    expect: { decision: allow, findings: [] }
request.json
{
  "entity": "Order",
  "operation": "create",
  "data": {
    "coupon": "SAVE10",
    "express": true
  }
}

Result, from the engine

Decisiondeny1 finding, server channel

  • LOG-001errorblockingCoupons do not apply to express orders./coupon
Course overview

On this page