Rasd Forms — JSON Schemas
Machine-readable contracts for the two JSON documents Rasd Forms consumes. Prose specifications live in 04 · Form schema spec and 12 · Theming; the normative decisions in 00 · Design spine win over everything.
| File | $id | Validates |
|---|---|---|
rasd-form.schema.json | https://schemas.rasd.dev/form/v1.json | A Rasd Form Definition (RFD) v1: root, meta, settings (audit/encryption/theme), choiceLists, datasets, pages → elements, logic (calculated + triggers), ext. One $def per element type (TextElement … RepeatElement, CustomElement for x:*), dispatched from $defs/Element by an if/then chain on type. |
rasd-theme.schema.json | https://schemas.rasd.dev/theme/v1.json | A design-token theme; its #/$defs/ThemePartial is the shape of an RFD's settings.theme.overrides. |
Both are JSON Schema draft 2020-12. Every property carries a description — the schema doubles as reference documentation and drives editor autocompletion when a document starts with "$schema": "https://schemas.rasd.dev/form/v1.json". Worked examples are in ../examples/: pdm-food-distribution.form.json and facility-monitoring-visit.form.json (Arabic + English, groups, repeats, dataset-driven cascading selects, REL logic, triggers, media capture, ext payloads).
What the schema does and does not check
The schema is the structural pass: shapes, enums, patterns, per-type props, XOR rules (list/choices, default.value/default.expr, choices/source), reserved names, prototype-pollution keys. It is deliberately stricter than the runtime: unknown properties are rejected here (additionalProperties: false) while @rasd/core ignores-and-preserves them with W_UNKNOWN_PROPERTY. Open extension points stay open: ext everywhere, props of x:* elements, and appearance.
Everything JSON Schema cannot express is the semantic pass of validateFormDefinition() (and rasd validate): name uniqueness and shadowing across repeat scopes, references (props.list, source.dataset, skipTo pages, trigger targets), REL parsing and dependency cycles, defaultLocale ∈ locales, min ≤ max, size limits. A document that passes ajv can still fail rasd validate; the reverse is a bug.
Validating
With @rasd/cli (runs both passes, prints codes + JSON pointers, exit code 1 on errors):
npx @rasd/cli validate docs/examples/pdm-food-distribution.form.json
npx @rasd/cli theme check my-theme.json
With the ajv CLI (structural pass only). The form schema uses two annotation keywords, x-rasd-expr and x-rasd-localized (on $defs/Expr and $defs/LocalizedString), so ajv's unknown-keyword check must be relaxed with --strict-schema=false; all other strict checks stay on:
npm i -D ajv-cli@5 ajv-formats@3
npx ajv validate --spec=draft2020 -c ajv-formats --strict-schema=false \
-s docs/schema/rasd-form.schema.json -d "docs/examples/*.form.json"
npx ajv validate --spec=draft2020 -c ajv-formats \
-s docs/schema/rasd-theme.schema.json -d docs/examples/theme-agency-blue.json
Programmatically (this is what CI runs; the schema compiles under strict: true):
import Ajv2020 from "ajv/dist/2020.js";
import addFormats from "ajv-formats";
import schema from "./rasd-form.schema.json" with { type: "json" };
const ajv = new Ajv2020({ strict: true, strictRequired: false, allErrors: true });
addFormats(ajv);
ajv.addVocabulary(["x-rasd-expr", "x-rasd-localized"]);
const validate = ajv.compile(schema);
if (!validate(formJson)) console.error(validate.errors);
strictRequired is off because ajv compiles if/then before properties and would flag every conditional required (e.g. publicKeyId when encryption.mode ≠ "none"); it is also off in ajv's default strict mode. Python jsonschema (Draft202012Validator) and the VS Code JSON language service load the file unchanged — they ignore unknown annotation keywords.
Source of truth and CI
In the monorepo the zod schemas in @rasd/core are the executable source; pnpm schema:build regenerates this file (z.toJSONSchema, target 2020-12, plus $id, descriptions and the two annotation keywords) and pnpm schema:check fails CI on any drift. pnpm schema:test validates every docs/examples/*.form.json and every fixture in @rasd/testing against both zod and ajv; the two must agree. Hand-editing this file is fine for a proposal, but the zod change must land in the same PR.
Versioning of $id
https://schemas.rasd.dev/form/v1.jsonalways serves the latest 1.x; frozen copies are published at/form/v1.0.json,/form/v1.1.json, … the moment a minor ships. The same rule applies to/theme/v1.json.- A MINOR only adds: optional properties, element types, enum values, REL functions, codes. Documents written against 1.0 stay valid against 1.1; documents using 1.1 features fail against the frozen 1.0 file (that is what
rasd: "1.1"andrequiresare for). - A MAJOR changes meaning or removes something and gets a new
$id(/form/v2.json) plus a converter (convertDefinition). Deprecated properties are markeddeprecated: trueand keep working for the whole 1.x line and at least 12 months after announcement. - The document's
rasdproperty ("1.0") is pinned to major 1 by this schema's pattern;$schemamay point at the rolling URL, a frozen minor, or a repository-relative path.