RusDox document specs have an explicit contract version:
version: 1
The same model is supported in YAML, JSON, and TOML. New specs and published examples must declare the version. During the pre-1.0 transition, a missing field is read as legacy version 1 so old files keep rendering, but the schema and editor tooling report the omission and recommend migration.
Compatibility policy
- This release supports document spec version 1.
- Unknown future versions fail semantic validation before any output is written.
- Additive optional fields and new block variants may appear in compatible RusDox minor releases.
- Existing field meaning, enum meaning, and escaping behavior do not change inside a spec version.
- A removal, incompatible default, or expression-semantics change requires a new spec version and a migration path.
- The Rust API and CLI still follow crate SemVer independently of the spec version.
This is the frozen v1 contract. Read Stability, versioning, and support for the cross-surface SemVer rules, deprecation window, MSRV, and release policy.
Generate the canonical schema directly from the installed binary:
rusdox schema rusdox schema --output rusdox-spec-v1.schema.json
The checked-in schema is generated from the Rust/serde types. Its public ID is https://othmaneblial.github.io/rusdox/schema/rusdox-spec-v1.schema.json and it applies to YAML, JSON, and TOML object shapes.
Migration
Inspect a file without changing it:
rusdox migrate legacy.yaml --check
Print the migrated form to stdout:
rusdox migrate legacy.yaml
Write atomically in place or to a separate file:
rusdox migrate legacy.yaml --in-place rusdox migrate legacy.json --output current.json
YAML migration preserves leading author comments. JSON and TOML are parsed and pretty-printed. Future versions are never silently downgraded.
Deterministic expressions
Nested paths work in values:
text: "{{ customer.address.city }}"
Supported filters are upper, lower, title, trim, and default("text"):
text: "{{ customer.name | trim | title }}" text: "{{ customer.owner | default(\"unassigned\") | upper }}"
Use doubled delimiters for literal braces:
text: "Show {{{{ customer.name }}}} literally"
This becomes Show {{ customer.name }} literally. Values are always inserted as document text and escaped by the DOCX/PDF writers. There is no raw OOXML, shell, function-call, network, or general-purpose evaluation mode.
The bounded conditional block selects one branch by truthiness or scalar equality:
- type: when path: customer.active equals: true blocks:
- type: body text: Active account otherwise:
- type: body text: Inactive account
Conditions and filters share the same behavior in YAML, JSON, and TOML.
VS Code
The dependency-free extension in editors/vscode bundles the schema and provides block/field completion, hover help, enum suggestions, and inline diagnostics. Its local language-service tests run with:
npm test --prefix editors/vscode
The extension never uploads document content.