Rules, tests, and errors#
Business Language has grammar-level support for executable rules, validation rule blocks, tests, and message-centered error handling.
Rules#
Rules are reusable executable declarations. A rule can have a full function-like body or be declared by name for implementation elsewhere.
rule can_post_invoice(invoice: Invoice): bool {
return invoice.status == "ready";
}
rule configured_elsewhere;Executable rule declarations use the colon return form. Do not write rule name(...) -> Type; that is function syntax, not rule syntax.
Use rules for reusable logic. If a rule depends on configurable thresholds, routes, statuses, outcomes, or reason codes, resolve those values from configuration tables.
Validation rule blocks#
Standalone validation rule declarations support ensure, check, and require statements. In this form, ensure requires message, check requires on, and require requires else.
validation rule RequiredInvoiceAmount {
ensure amount is not null message "Amount is required";
check amount > 0 on amount;
require currency_code is not null else "Currency is required";
}Named validation declarations can group validation statements and validation functions. In this form, message, on, and else trailers are optional.
validation InvoiceValidation {
ensure amount > 0 message "Amount must be positive";
check vendor_id is not null on vendor_id;
require status is not null else "Status is required";
validate can_post(invoice: Invoice): bool {
return invoice.status == "ready";
}
}Validation functions also accept -> return markers and trailing parameter commas.
validation InvoiceValidation {
validate normalize(input: string, fallback: string = "n/a",) -> string {
return input ?? fallback;
}
}Tests#
Tests are declaration blocks with context values, assertions, expectations, setup, and teardown.
For the full syntax reference, see Test declarations.
test InvoicePostingTest {
context invoice: Invoice = sample_invoice;
setup {
seed_posting_policy();
}
assert invoice.amount > 0;
expect can_post_invoice(invoice) to be true;
expect post_invoice(invoice) to throw PostingError;
teardown {
clear_posting_policy();
}
}Supported expectation matchers are:
| Matcher | Shape |
|---|---|
| Throw | expect expr to throw Type |
| Be | expect expr to be value |
| Equal | expect expr to equal value |
| Contain | expect expr to contain value |
Throw matchers accept full type expressions, including qualified names.
test QualifiedFailure {
expect post_invoice(invoice) to throw errors.PostingError;
}Error declarations#
The minimal error declaration form declares a named error.
error PostingError;User-facing failures should prefer declared messages with stable codes, severity, category, and localized text.
Try, catch, and finally#
try {
post_invoice(invoice);
} catch (err: PostingError) {
raise message posting_failed(invoice.invoice_id);
} finally {
release_posting_lock(invoice.invoice_id);
}Additional catch clauses in normal statements use typed parameters.
try {
post_invoice(invoice);
} catch (err: PostingError) {
raise message unexpected_posting_error();
}Message-backed raises#
Use message-backed raises for user-facing failures.
raise message posting_policy_required(company_code_param) with {
company_code: company_code_param;
};
raise posting_policy_required;
raise posting_policy_required(company_code_param);
throw message posting_policy_required(company_code_param);
throw error_value;The raise grammar supports message Name(args), message(args), or Name(args), with optional context entries after with. Message-backed raises can include arguments or omit them. Use throw <expression>; for non-message expression throws.
Closed failure behavior#
Missing required configuration should raise a declared message-backed error. Do not continue with fallback behavior for missing policies, unsupported modes, unknown actions, or invalid enum-like values.