Extends
Inherit a parent ruleset's rules, params and messages.
extends names one parent ruleset and the versions you accept. The child starts from the parent's
rules, params, entities, types, functions and messages, then adds its own. The parent's rules run
first, and each finding's source says which ruleset the rule came from.
Syntax
extends:
- ruleset: learn.base
version: "^1.0.0" # 1.0.0 or later, same major; "1.2.0" means exactly 1.2.0
checksum: sha256:... # optional: pin the exact parent contentExample
The base ruleset (version 1.2.0) requires at least one item. The shop extends it and requires a country. One request gets a finding from each.
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.shop, version: 1.0.0, title: "Shop rules" }
scope:
- { level: organization, id: learn }
- { level: project, id: shop }
extends:
- { ruleset: learn.base, version: "^1.0.0" }
rules:
- id: shop.country.required
kind: validation
target: { entity: Order, field: /country }
operations: [create]
assert: { op: exists, args: [{ var: data.country }] }
severity: error
finding: { code: LRN-EXT-001, message: shop.countryRequired }
messages:
en:
shop.countryRequired: "Choose the country to deliver to."
tests:
- name: inherited and own rules run together
entity: Order
operation: create
given:
data: { quantity: 0 }
expect:
decision: deny
findings:
- { rule: base.quantity.min }
- { rule: shop.country.required }
- name: a complete order is allowed
entity: Order
operation: create
given:
data: { quantity: 2, country: "PT" }
expect: { decision: allow, findings: [] }ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.base, version: 1.2.0, title: "Base rules" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: base.quantity.min
kind: validation
target: { entity: Order, field: /quantity }
operations: [create]
assert: { op: gte, args: [{ var: data.quantity }, 1] }
severity: error
finding: { code: LRN-BAS-001, message: base.quantityMin }
messages:
en:
base.quantityMin: "Order at least one item."{
"entity": "Order",
"operation": "create",
"data": {
"quantity": 0
}
}Result, from the engine
Decisiondeny2 findings, server channel
LRN-BAS-001errorblockingOrder at least one item./quantityLRN-EXT-001errorblockingChoose the country to deliver to./country
Common mistakes
Loading checks the parent before anything else, in this order:
- The parent is not in the registry:
EXTENDS_NOT_FOUND. - The parent's version is outside the range:
EXTENDS_VERSION_MISMATCH. Below, the shop asks for^2.0.0, but the base is 1.2.0. - The parent extends the child, directly or not:
EXTENDS_CYCLE. - A
checksumpin differs from the parent's checksum:EXTENDS_CHECKSUM_MISMATCH. - The child declares a rule id the parent already has:
RULE_DUPLICATE. Change an inherited rule with an override instead (see Override a rule).
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.shop, version: 1.0.0, title: "Shop rules" }
scope:
- { level: organization, id: learn }
- { level: project, id: shop }
extends:
- { ruleset: learn.base, version: "^2.0.0" }
rules:
- id: shop.country.required
kind: validation
target: { entity: Order, field: /country }
operations: [create]
assert: { op: exists, args: [{ var: data.country }] }
severity: error
finding: { code: LRN-EXT-001, message: shop.countryRequired }
messages:
en:
shop.countryRequired: "Choose the country to deliver to."
tests:
- name: inherited and own rules run together
entity: Order
operation: create
given:
data: { quantity: 0 }
expect:
decision: deny
findings:
- { rule: base.quantity.min }
- { rule: shop.country.required }
- name: a complete order is allowed
entity: Order
operation: create
given:
data: { quantity: 2, country: "PT" }
expect: { decision: allow, findings: [] }ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.base, version: 1.2.0, title: "Base rules" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: base.quantity.min
kind: validation
target: { entity: Order, field: /quantity }
operations: [create]
assert: { op: gte, args: [{ var: data.quantity }, 1] }
severity: error
finding: { code: LRN-BAS-001, message: base.quantityMin }
messages:
en:
base.quantityMin: "Order at least one item."{
"entity": "Order",
"operation": "create",
"data": {
"quantity": 0
}
}Result, from the engine
does not loadThe engine refuses the ruleset before it evaluates anything.
EXTENDS_VERSION_MISMATCHlearn.shop: parent learn.base is 1.2.0, need ^2.0.0
Exercise
Pin the parent to exactly version 1.2.0. Then add a child rule that requires an e-mail address, with a test that shows its finding.
Hint
Write the version 1.2.0 without the caret, and add a validation rule with its own code and message.
Show answer
ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.shop, version: 1.0.0, title: "Shop rules" }
scope:
- { level: organization, id: learn }
- { level: project, id: shop }
extends:
- { ruleset: learn.base, version: "1.2.0" }
rules:
- id: shop.country.required
kind: validation
target: { entity: Order, field: /country }
operations: [create]
assert: { op: exists, args: [{ var: data.country }] }
severity: error
finding: { code: LRN-EXT-001, message: shop.countryRequired }
- id: shop.email.required
kind: validation
target: { entity: Order, field: /email }
operations: [create]
assert: { op: exists, args: [{ var: data.email }] }
severity: error
finding: { code: LRN-EXT-002, message: shop.emailRequired }
messages:
en:
shop.countryRequired: "Choose the country to deliver to."
shop.emailRequired: "Give an e-mail address."
tests:
- name: inherited and own rules run together
entity: Order
operation: create
given:
data: { quantity: 0, email: "ana@example.com" }
expect:
decision: deny
findings:
- { rule: base.quantity.min }
- { rule: shop.country.required }
- name: a complete order is allowed
entity: Order
operation: create
given:
data: { quantity: 2, country: "PT", email: "ana@example.com" }
expect: { decision: allow, findings: [] }
- name: the child's own rule
entity: Order
operation: create
given:
data: { quantity: 2, country: "PT" }
expect:
decision: deny
findings:
- { rule: shop.email.required }ruleCascade: 1.0.0
kind: RuleSet
metadata: { id: learn.base, version: 1.2.0, title: "Base rules" }
scope:
- { level: organization, id: learn }
entities:
Order:
schema: { $ref: "./learn.openapi.yaml#/components/schemas/Order" }
rules:
- id: base.quantity.min
kind: validation
target: { entity: Order, field: /quantity }
operations: [create]
assert: { op: gte, args: [{ var: data.quantity }, 1] }
severity: error
finding: { code: LRN-BAS-001, message: base.quantityMin }
messages:
en:
base.quantityMin: "Order at least one item."{
"entity": "Order",
"operation": "create",
"data": {
"quantity": 0,
"email": "ana@example.com"
}
}Result, from the engine
Decisiondeny2 findings, server channel
LRN-BAS-001errorblockingOrder at least one item./quantityLRN-EXT-001errorblockingChoose the country to deliver to./country