4.5 Schemas, validation errors, and JSON merge PATCH
When you want your records to have a guaranteed shape, install a JSON Schema
on the dataset and turn on enforceSchema. The data service then validates
every write (and every PATCH result) and rejects bad data with a precise,
machine-readable error.
Installing a schema
A schema is a fixed child of the dataset called .schema.json, written with the
store contract:
curl.exe -X PUT http://127.0.0.1:3100/data/people/.schema.json `
-H "Content-Type: application/schema+json" `
--data '{ "type": "object", "required": ["name"],
"properties": { "name": {"type":"string"},
"age": {"type":"integer","minimum":0} } }'
The PUT compile-checks the schema first, so a malformed schema is rejected
at install time rather than failing later on a write. Validators are compiled and
cached, so enforcement is cheap.
Validation on write
With enforceSchema: true, a write that violates the schema fails with
422 validation_failed and an errors array pinpointing each problem:
{ "code": "validation_failed", "status": 422,
"errors": [ { "path": "/age", "error": "must be >= 0" } ] }
The path is a JSON Pointer into the record, so a client (or a form UI) can map
the error straight to a field.
Schema-carrying content type
When a record has a schema, reads advertise it:
Content-Type: application/json; schema="…/people/.schema.json"
Link: <…/people/.schema.json>; rel="describedby"
So a client can discover and fetch the rules a record obeys without out-of-band knowledge — the same schema the runtime enforces is the one it points you to (no drift).
JSON merge PATCH
PATCH applies an RFC 7386 JSON merge patch: the body is merged into the
existing record (a null value deletes a field), the merged result is
re-validated, and the new record is returned.
curl.exe -X PATCH http://127.0.0.1:3100/data/people/ada `
-H "Content-Type: application/json" --data '{ "age": 36 }'
Because the result is validated, a PATCH can't sneak a record into an invalid
state. If the merge would violate the schema, you get the same
422 validation_failed with the offending paths, and the stored record is
untouched.
When to enforce
Turn enforceSchema on for datasets that back forms, APIs, or anything where a
malformed record would cause downstream bugs. Leave it off for free-form or
scratch data. It's a per-mount setting, so you can run strict and loose data
mounts side by side.
Field-level access (fieldLevelAuthz)
Mount access (5.4) gates a whole record. To restrict individual fields,
set fieldLevelAuthz: true on the data mount and annotate properties in the
schema with x-rs-read / x-rs-write — role specs in the same grammar as mount
access (all, authenticated, a role name, or role /path/{email}):
{ "type": "object", "properties": {
"name": { "type": "string" },
"roles": { "type": "string", "x-rs-write": "A" }, // only A may change it
"ssn": { "type": "string", "x-rs-read": "A", "x-rs-write": "A" }
} }
- Reads redact. A
GETomits each field whosex-rs-readthe caller doesn't satisfy — they get the rest of the record. TheETagis computed over the stored (unredacted) record, so concurrency is stable. - Writes reject. Changing a field the caller can read but can't write is
403. Fields the caller can't read are never touched — they're preserved from the stored record — so a read→edit→write-back can't drop data it couldn't see.PATCHis the natural edit path: it merges against the full stored record, so a redacted field is simply absent and preserved. - The schema is policy, so editing it needs an operator. Because the rules
live in
.schema.json, on afieldLevelAuthzmount writing the schema requires a tenant operator (5.0) — otherwise the rules could be removed to bypass them.
This is how you let users edit their own record without letting them grant
themselves privileged roles: annotate the users dataset's roles field
x-rs-write: "<operator role>". Unannotated fields are unrestricted (mount
access is still the coarse gate). Top-level fields only for now; data exposed
through a query mount bypasses field redaction (gate sensitive datasets at the
mount there).
Last in this part: the listing/pagination details and the delete guard shared by all stores.
← Previous: 4.4 The data service · Manual home · Next: 4.6 Listings, pagination, delete guard →