Schema Additions¶
Schema additions declare new fields that an extension registers on existing Lace objects. When the extension is active, the validator accepts these fields. When inactive, they produce unknown-field warnings.
Registration targets¶
Each key under [schema] maps to a location in the Lace object model:
| Target key | Where the field appears |
|---|---|
scope_options |
The options {} block of any scope in .expect() / .check() |
condition_options |
The options {} block of any condition in .assert() |
timeout |
The timeout {} call config sub-object |
redirects |
The redirects {} call config sub-object |
security |
The security {} call config sub-object |
call |
The root call config object |
Where registered fields land¶
scope_optionsandcondition_optionsfields appear directly in theoptions {}object -- a rule readsscope.options?.notificationorcondition.options?.notification.- Fields registered on
timeout,redirects,securityandcallare parsed into anextensionssub-object of the owning object and surface there in the resolved call config (calls[n].configin the result,call.configin hook context). Read them ascall.config.timeout?.extensions?.notification, orcall.config.extensions?.<field>for root-level (call) fields.
For example, with laceNotifications active, this script:
get("https://api.example.com/login", {
timeout: { ms: 500, notification: text("Login timed out") }
})
.expect(status: 200)
records the resolved timeout config as { "ms": 500, "extensions": { "notification": { "tag": "text", "value": "Login timed out" } }, "action": "fail", "retries": 0 }.
Field definitions¶
Each field is declared as a key under the target with a table of properties:
[schema.scope_options]
silentOnRepeat = { type = "bool", default = "true" }
notification = { type = "notification_expr" }
| Property | Required | Description |
|---|---|---|
type |
Yes | Type name -- either a built-in type or a name defined in [types] |
default |
No | Documented default, as a string. Informational only: executors do not inject it into options or config objects. A rule sees null for an absent field and must apply the default itself (laceNotifications' is_silent function is the pattern). |
required |
No | Boolean. If true, the validator emits an error when the field is absent and the extension is active. Default false. |
Type system¶
Built-in types¶
| Name | Description |
|---|---|
string |
UTF-8 string |
int |
Integer |
float |
Floating point |
bool |
true or false |
null |
Null value |
any |
Any type |
array<T> |
Array of type T |
map<K, V> |
Object with key type K and value type V |
string? |
Nullable string (shorthand for string | null) |
Custom types with tagged unions¶
Define custom types in the [types] section using one_of for tagged unions. A tagged union declares a set of variants -- the value must match exactly one. This is how laceNotifications declares its two notification types:
# The resolved type that appears in the result.
[types.notification_val]
one_of = [
{ tag = "template", fields = { name = "string" } },
{ tag = "text", fields = { value = "string" } },
{ tag = "structured", fields = { data = "any" } }
]
# The scripting-time superset: adds op_map, resolved to a
# notification_val before emission.
[types.notification_expr]
one_of = [
{ tag = "template", fields = { name = "string" } },
{ tag = "text", fields = { value = "string" } },
{ tag = "structured", fields = { data = "any" } },
{ tag = "op_map", fields = { ops = "map<string, notification_expr>" } }
]
Each variant has a tag (the discriminator) and fields (the variant's data). In the rule language, you check the tag first, then access the variant's fields:
Tag constructors. Every one_of variant is callable by its tag, both from .lace option values and from rule bodies: text("..."), template("..."), structured({ ... }), op_map({ ... }). The call takes the variant's fields as positional arguments and produces { tag: "<tag>", <field>: ... } -- text("down") is { tag: "text", value: "down" }.
You can also define simple type aliases:
Example: laceNotifications schema¶
The laceNotifications extension registers notification and silentOnRepeat on scopes and conditions, and notification alone on timeouts:
[schema.scope_options]
silentOnRepeat = { type = "bool", default = "true" }
notification = { type = "notification_expr" }
[schema.condition_options]
silentOnRepeat = { type = "bool", default = "true" }
notification = { type = "notification_expr" }
[schema.timeout]
notification = { type = "notification_expr" }
This means that when laceNotifications is active, .expect() / .check() scope and .assert() condition options {} blocks accept notification and silentOnRepeat, and timeout {} blocks accept notification (recorded under config.timeout.extensions.notification). There is no silentOnRepeat on timeouts.
TOML format constraint¶
.laceext files must parse with any TOML 1.0 parser, and TOML 1.0 forbids an inline table ({ ... }) from spanning multiple lines. A multi-line array whose elements are each a single-line inline table is valid -- that is the form the bundled extensions use for one_of lists:
# Wrong -- an inline table broken across lines
[types.event]
one_of = [
{ tag = "a",
fields = { id = "string" } }
]
# Correct -- each inline table on one line (the array may span lines)
[types.event]
one_of = [
{ tag = "a", fields = { id = "string" } },
{ tag = "b", fields = { name = "string" } }
]
# Also correct -- array of sub-tables
[[types.event.one_of]]
tag = "a"
[types.event.one_of.fields]
id = "string"
Single-line inline tables like { type = "bool", default = "true" } are always fine.