Appendix A — JSON Schema
Contents of DISL 0.2
The normative JSON Schema is published as disl.schema.json (JSON Schema draft 2020-12), $id https://etalii.net/adp/disl/schema/0.2/disl.schema.json. The 0.1 schema address stays published, and a document that names it is read against the 0.2 schema (2.9). Its root, #/$defs/Specification, validates specifications. Stored diagrams are validated by DID's schema, did.schema.json, which references this schema's QualifiedId, SemVer and Point. The schema is kept together with this document, and every specification example in section 17 validates against it.
All objects in the schema:
- forbid unknown properties (
additionalProperties: false), except extension properties matching^x-; - accept an optional
docproperty where this specification allows documentation; - use shared
$defsfor recurring concepts (Expression,Dynamic,Paint,Stroke,SnapRule,Label,Action, …).
Rules that JSON Schema cannot express — name resolution, CEL type checking, inheritance narrowing, reserved names, compatibility of placement bindings with axis kinds, and the other checks in 14.1 step 9 — are the job of a DISL validator.
In total the schema has 170 $defs entries; the 35 in bold are new in 0.2, and Finding and ValidatorOutput describe what a headless validator prints (8.6), not a specification. The stored-diagram structures Document, ElementRecord, RelationRecord, ViewRecord, NodeView and EdgeView of the earlier combined schema moved to DID's schema.
The following excerpt shows how per-axis snapping is encoded. A snap rule is either a string ("none", "inherit", "inherit-x", "inherit-y", or a profile name) or an object that carries exactly one rule key, enforced with oneOf over required.
{
"Snapping": {
"description": "Snapping restricts positions and sizes to discrete values, defined per axis in the axis's own units; it is declared in a snap profile, on a coordinate system, a viewpoint canvas, a node or edge notation, or a placement, and the most specific declaration wins per property and per axis. It holds a snap rule per axis (or both), rules for sizes, rotation, bendpoints and labels, the mode (always or magnetic), which targets participate, the tolerance, and the bypass key. See section 5.9.",
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"userToggle": {
"type": "boolean"
},
"mode": {
"enum": [
"always",
"magnetic"
]
},
"x": {
"$ref": "#/$defs/SnapRule"
},
"y": {
"$ref": "#/$defs/SnapRule"
},
"both": {
"$ref": "#/$defs/SnapRule"
},
"angle": {
"$ref": "#/$defs/SnapRule"
},
"radius": {
"$ref": "#/$defs/SnapRule"
},
"size": {
"$ref": "#/$defs/AxisRules"
},
"reference": {
"enum": [
"anchor",
"bounds",
"center",
"edges"
]
},
"rotation": {
"type": "object",
"properties": {
"step": {
"type": "number"
},
"tolerance": {
"type": "number"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
},
"bendpoints": {
"anyOf": [
{
"enum": [
"inherit",
"none"
]
},
{
"$ref": "#/$defs/AxisRules"
}
]
},
"labels": {
"anyOf": [
{
"const": "none"
},
{
"type": "object",
"properties": {
"positions": {
"type": "array",
"items": true
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
}
]
},
"targets": {
"type": "array",
"items": {
"enum": [
"rule",
"objects",
"guides",
"ports",
"parent"
]
}
},
"toleranceScreenPx": {
"type": "number"
},
"bypassModifier": {
"$ref": "#/$defs/Modifier"
},
"feedback": {
"type": "object",
"properties": {
"showGhost": {
"type": "boolean"
},
"showValue": {
"type": "boolean"
},
"highlightBand": {
"type": "boolean"
},
"style": {
"$ref": "#/$defs/StyleRef"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
},
"applyToProgrammatic": {
"type": "boolean"
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
},
"SnapRuleObject": {
"description": "A snap rule object is the object form of a snap rule and holds exactly one rule key: grid, values, calendar (time units), ticks (visible axis ticks), bands (ordinal bands), divisions (equal parts), ratio (aspect ratios), cel (a custom function), byZoom (zoom-dependent rules) or plugin. Common properties beside the key clamp the result with min and max, set the rounding direction (nearest by default) and override the mode. See section 5.10.",
"type": "object",
"properties": {
"grid": {
"type": "object",
"properties": {
"spacing": {
"anyOf": [
{
"type": "number"
},
{
"type": "string"
}
]
},
"offset": true,
"subdivisions": {
"type": "integer"
},
"subdivideAtZoom": {
"type": "number"
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"required": [
"spacing"
],
"patternProperties": {
"^x-": true
}
},
"values": {
"anyOf": [
{
"type": "array",
"items": true
},
{
"$ref": "#/$defs/CelValue"
}
]
},
"calendar": {
"type": "object",
"properties": {
"unit": {
"enum": [
"millisecond",
"second",
"minute",
"hour",
"day",
"week",
"month",
"quarter",
"year"
]
},
"step": {
"type": "integer"
},
"align": {
"enum": [
"start",
"end",
"nearest"
]
},
"workingTime": {
"type": "boolean"
},
"calendar": {
"type": "string"
},
"at": {
"type": "string"
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"required": [
"unit"
],
"patternProperties": {
"^x-": true
}
},
"ticks": {
"type": "object",
"properties": {
"level": {
"enum": [
"major",
"minor"
]
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
},
"bands": {
"type": "object",
"properties": {
"align": {
"enum": [
"start",
"center",
"end",
"keep-offset"
]
},
"offsetGrid": {
"type": "number"
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
},
"divisions": {
"type": "object",
"properties": {
"count": {
"type": "integer"
},
"of": {
"enum": [
"parent",
"canvas",
"axis"
]
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"required": [
"count"
],
"patternProperties": {
"^x-": true
}
},
"ratio": {
"type": "object",
"properties": {
"ratios": {
"type": "array",
"items": {
"type": "number"
}
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"required": [
"ratios"
],
"patternProperties": {
"^x-": true
}
},
"cel": {
"$ref": "#/$defs/CelSource"
},
"byZoom": {
"type": "array",
"items": {
"type": "object",
"properties": {
"maxZoom": {
"type": "number"
},
"rule": {
"$ref": "#/$defs/SnapRule"
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"required": [
"rule"
],
"patternProperties": {
"^x-": true
}
},
"minItems": 1
},
"plugin": {
"$ref": "#/$defs/QualifiedId"
},
"args": {
"type": "object"
},
"min": true,
"max": true,
"direction": {
"enum": [
"nearest",
"floor",
"ceil"
]
},
"mode": {
"enum": [
"always",
"magnetic"
]
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
},
"oneOf": [
{
"required": [
"grid"
]
},
{
"required": [
"values"
]
},
{
"required": [
"calendar"
]
},
{
"required": [
"ticks"
]
},
{
"required": [
"bands"
]
},
{
"required": [
"divisions"
]
},
{
"required": [
"ratio"
]
},
{
"required": [
"cel"
]
},
{
"required": [
"byZoom"
]
},
{
"required": [
"plugin"
]
}
]
}
}
{
"Placement": {
"description": "Placement tells the runtime where each coordinate of a node comes from; it is declared in the node notation, so one model type can be placed differently per viewpoint. It names the coordinate system and gives a placement source for each axis's position and for its opposite edge or extent (x2 excludes width, y2 excludes height), plus the anchor. It also controls moving and resizing per axis, stacking within a band, and clamping to the parent. See section 5.8.",
"type": "object",
"properties": {
"system": {
"$ref": "#/$defs/SimpleId"
},
"x": {
"$ref": "#/$defs/PlacementSource"
},
"y": {
"$ref": "#/$defs/PlacementSource"
},
"x2": {
"$ref": "#/$defs/PlacementSource"
},
"y2": {
"$ref": "#/$defs/PlacementSource"
},
"width": {
"$ref": "#/$defs/PlacementSource"
},
"height": {
"$ref": "#/$defs/PlacementSource"
},
"angle": {
"$ref": "#/$defs/PlacementSource"
},
"radius": {
"$ref": "#/$defs/PlacementSource"
},
"anchor": {
"$ref": "#/$defs/Anchor"
},
"movable": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "object",
"properties": {
"x": {
"type": "boolean"
},
"y": {
"type": "boolean"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
}
]
},
"resizable": {
"anyOf": [
{
"type": "boolean"
},
{
"type": "object",
"properties": {
"x": {
"type": "boolean"
},
"y": {
"type": "boolean"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
}
]
},
"stack": {
"type": "object",
"properties": {
"mode": {
"enum": [
"overlap",
"stack",
"pack"
]
},
"order": {
"$ref": "#/$defs/Expression"
},
"rowSize": {
"type": "number"
},
"growBand": {
"type": "boolean"
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
}
},
"clampToParent": {
"type": "boolean"
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"patternProperties": {
"^x-": true
},
"not": {
"anyOf": [
{
"required": [
"x2",
"width"
]
},
{
"required": [
"y2",
"height"
]
}
]
}
},
"PlacementSource": {
"description": "A placement source says where one coordinate of a placement comes from. The string free, or omitting it, stores the value in view data; an attribute binding reads and writes a model attribute, optionally with a constant offset; a CEL expression computes a read-only value, and write actions let a move update the model from it; layout true leaves it to the layout algorithm. A bound attribute's type must suit the axis. See section 5.8.",
"anyOf": [
{
"const": "free"
},
{
"$ref": "#/$defs/AttrBinding"
},
{
"type": "object",
"properties": {
"cel": {
"$ref": "#/$defs/CelSource"
},
"write": {
"type": "array",
"items": {
"$ref": "#/$defs/Action"
}
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"required": [
"cel"
],
"patternProperties": {
"^x-": true
}
},
{
"type": "object",
"properties": {
"layout": {
"const": true
},
"doc": {
"$ref": "#/$defs/Doc"
}
},
"additionalProperties": false,
"required": [
"layout"
],
"patternProperties": {
"^x-": true
}
}
]
}
}