Validate scenarios against json schema (#1475)

Closes #1428

Since the authoritative validation of scenario files is actually performed by virtue of `swarm` parsing them, this CI job actually exists to ensure the JSON Schema descriptions are accurate.  This is important for two purposes:
* Documentation is generated from the JSON Schema files (#1436)
* JSON Schema has integration with VS Code and other IDEs

# Testing

Verified that the schema checker action does indeed work by intentionally pushing an invalid scenario file in f789f81.
This commit is contained in:
Karl Ostmo 2023-08-27 19:34:03 -07:00 committed by GitHub
parent 09f8aee9fc
commit bfc0c143b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 382 additions and 45 deletions

28
.github/workflows/scenario-schema.yml vendored Normal file
View File

@ -0,0 +1,28 @@
name: JSON schema
on:
push:
paths:
- 'data/scenarios/**.yaml'
branches:
- main
pull_request:
paths:
- 'data/scenarios/**.yaml'
branches:
- main
jobs:
validate-scenario-schema:
name: Validate scenarios against schema
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install check-jsonschema
- run: |
scripts/validate-json-schemas.sh

View File

@ -356,8 +356,8 @@ robots:
- [0, gate]
- [10, hinge]
- name: sheep
description:
- meandering livestock
description: |
meandering livestock
display:
invisible: false
char: '@'

View File

@ -14,6 +14,7 @@ entities:
char: 'Y'
description:
- Your scooter
robots: []
world:
palette:
'x': [grass, null, base]

View File

@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/attribute.json",
"title": "Scenario-local attributes",
"description": "Local attribute definitions",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Name of attribute"
},
"fg": {
"type": "string",
"description": "Foreground color"
},
"bg": {
"type": "string",
"description": "Background color"
},
"style": {
"description": "Style properties list",
"type": "array",
"items": {
"type": "string"
}
}
}
}

View File

@ -4,6 +4,7 @@
"title": "Swarm entity combustion",
"description": "Properties of combustion",
"type": "object",
"additionalProperties": false,
"properties": {
"ignition": {
"default": 0.5,
@ -26,7 +27,7 @@
},
"product": {
"default": "ash",
"type": "string",
"type": ["string", "null"],
"description": "What entity, if any, is left over after combustion"
}
}

View File

@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/cosmic-loc.json",
"title": "Cosmic location",
"description": "Planar location plus subworld",
"type": "object",
"additionalProperties": false,
"properties": {
"subworld": {
"type": "string",
"description": "Name of subworld"
},
"loc": {"$ref": "./planar-loc.json"}
}
}

View File

@ -4,6 +4,7 @@
"title": "Swarm entity display",
"description": "How to display an entity or robot in the Swarm game",
"type": "object",
"additionalProperties": false,
"properties": {
"char": {
"default": " ",

View File

@ -7,6 +7,7 @@
"items": {
"description": "Description of an entity in the Swarm game",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
@ -14,7 +15,7 @@
},
"display": {
"type": "object",
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/display.json",
"$ref": "./display.json",
"description": "Display information for the entity."
},
"plural": {
@ -63,7 +64,7 @@
},
"combustion": {
"type": "object",
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/combustion.json",
"$ref": "./combustion.json",
"description": "Properties of combustion."
},
"yields": {

View File

@ -0,0 +1,15 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/explicit-waypoint.json",
"title": "Waypoint",
"description": "Explicit waypoint definition",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"description": "Waypoint name",
"type": "string"
},
"loc": {"$ref": "./planar-loc.json"}
}
}

View File

@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/objective.json",
"title": "Scenario goals and their prerequisites",
"description": "The top-level objectives field contains a list of objectives that must be completed in sequence. Each objective has a goal description and a condition.",
"type": "object",
"additionalProperties": false,
"properties": {
"goal": {
"type": "array",
"items": [
{
"type": "string"
}
],
"description": "The goal description as a list of paragraphs that the player can read."
},
"condition": {
"description": "A swarm program that will be hypothetically run each tick to check if the condition is fulfilled.",
"type": "string"
},
"id": {
"description": "A short identifier for referencing as a prerequisite",
"type": "string"
},
"optional": {
"description": "Whether completion of this objective is required to achieve a 'Win' of the scenario",
"type": "boolean"
},
"hidden": {
"description": "Whether this goal should be suppressed from the Goals dialog prior to achieving it",
"type": "boolean"
},
"teaser": {
"description": "A compact (2-3 word) summary of the goal",
"type": "string"
},
"prerequisite": {}
}
}

View File

@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/placement.json",
"title": "Swarm structure placement",
"description": "Structure placement",
"type": "object",
"additionalProperties": false,
"properties": {
"src": {
"type": "string",
"description": "Name of structure definition"
},
"offset": {
"$ref": "./planar-loc.json"
},
"orient": {
"description": "Orientation of structure",
"type": "object",
"additionalProperties": false,
"properties": {
"up": {
"type": "string"
},
"flip": {
"type": "boolean"
}
}
}
}
}

View File

@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/planar-loc.json",
"title": "Planar location",
"description": "x and y coordinates of a location in a particular world",
"type": "array",
"items": [
{
"name": "X coordinate",
"type": "number"
},
{
"name": "Y coordinate",
"type": "number"
}
]
}

37
data/schema/portal.json Normal file
View File

@ -0,0 +1,37 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/portal.json",
"title": "Portals",
"description": "Portal definition",
"type": "object",
"additionalProperties": false,
"properties": {
"entrance": {
"type": "string",
"description": "Name of entrance waypoint"
},
"reorient": {
"description": "Passing through this portal changes a robot's orientation",
"type": "string"
},
"consistent": {
"description": "Whether this portal is spatially consistent across worlds",
"type": "boolean"
},
"exitInfo": {
"description": "Exit definition",
"type": "object",
"additionalProperties": false,
"properties": {
"exit": {
"type": "string",
"description": "Name of exit waypoint"
},
"subworldName": {
"type": "string",
"description": "Name of exit subworld"
}
}
}
}
}

View File

@ -24,18 +24,19 @@
]
}
],
"additionalProperties": false,
"properties": {
"in": {
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/inventory.json",
"$ref": "./inventory.json",
"description": "A list of ingredients consumed by the recipe. Each ingredient is a tuple consisting of an integer and an entity name, indicating the number of copies of the given entity that are needed."
},
"out": {
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/inventory.json",
"$ref": "./inventory.json",
"description": "A list of outputs produced by the recipe. It is a list of [count, entity name] tuples just like in."
},
"required": {
"default": [],
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/inventory.json",
"$ref": "./inventory.json",
"description": "A list of catalysts required by the recipe. They are neither consumed nor produced, but must be present in order for the recipe to be carried out. It is a list of [count, entity name] tuples just like in and out."
},
"time": {

View File

@ -4,6 +4,7 @@
"title": "Swarm robot",
"description": "Description of a robot in the Swarm game",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
@ -15,19 +16,11 @@
"description": "A description of the robot, given as a list of paragraphs. This is currently not used for much (perhaps not at all?)."
},
"loc": {
"default": null,
"type": "array",
"items": [
{
"name": "X coordinate",
"type": "number"
},
{
"name": "Y coordinate",
"type": "number"
}
],
"description": "An optional (x,y) starting location for the robot. If the loc field is specified, then a concrete robot will be created at the given location. If this field is omitted, then this robot record exists only as a template which can be referenced from a cell in the world palette. Concrete robots will then be created wherever the corresponding palette character is used in the world map."
"description": "An optional starting location for the robot. If the loc field is specified, then a concrete robot will be created at the given location. If this field is omitted, then this robot record exists only as a template which can be referenced from a cell in the world palette. Concrete robots will then be created wherever the corresponding palette character is used in the world map.",
"oneOf": [
{"$ref": "./cosmic-loc.json"},
{"$ref": "./planar-loc.json"}
]
},
"dir": {
"type": "array",
@ -46,7 +39,7 @@
},
"display": {
"default": "default",
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/display.json",
"$ref": "./display.json",
"description": "Display information for the robot. If this field is omitted, the default robot display will be used."
},
"program": {
@ -64,7 +57,7 @@
},
"inventory": {
"default": [],
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/inventory.json",
"$ref": "./inventory.json",
"description": "A list of [count, entity name] pairs, specifying the entities in the robot's starting inventory, and the number of each."
},
"system": {

View File

@ -4,6 +4,7 @@
"title": "Swarm scenario",
"description": "Scenario for the swarm game",
"type": "object",
"additionalProperties": false,
"properties": {
"version": {
"description": "The version number of the scenario schema. Currently, this should always be 1.",
@ -34,12 +35,12 @@
"entities": {
"description": "An optional list of custom entities, to be used in addition to the built-in entities. See description of Entities.",
"default": [],
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/entities.json"
"$ref": "./entities.json"
},
"recipes": {
"description": "An optional list of custom recipes, to be used in addition to the built-in recipes. They can refer to built-in entities as well as custom entities. See description of Recipes.",
"default": [],
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/recipes.json"
"$ref": "./recipes.json"
},
"known": {
"description": "A list of names of standard or custom entities which should have the Known property added to them; that is, robots should know what they are without having to scan them.",
@ -52,13 +53,34 @@
]
},
"world": {
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/world.json"
"$ref": "./world.json"
},
"attrs": {
"description": "A list of local attribute definitions",
"type": "array",
"items": {
"$ref": "./attribute.json"
}
},
"subworlds": {
"description": "A list of subworld definitions",
"type": "array",
"items": {
"$ref": "./world.json"
}
},
"structures": {
"description": "Structure definitions",
"type": "array",
"items": {
"$ref": "./structure.json"
}
},
"robots": {
"description": "A list of robots that will inhabit the world. See the description of Robots.",
"type": "array",
"items": {
"$ref": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/robot.json"
"$ref": "./robot.json"
}
},
"objectives": {
@ -66,24 +88,7 @@
"default": [],
"type": "array",
"items": {
"name": "objective",
"description": "The top-level objectives field contains a list of objectives that must be completed in sequence. Each objective has a goal description and a condition.",
"type": "object",
"properties": {
"goal": {
"type": "array",
"items": [
{
"type": "string"
}
],
"description": "The goal description as a list of paragraphs that the player can read."
},
"condition": {
"description": "A swarm program that will be hypothetically run each tick to check if the condition is fulfilled.",
"type": "string"
}
}
"$ref": "./objective.json"
}
},
"solution": {

View File

@ -0,0 +1,54 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://raw.githubusercontent.com/swarm-game/swarm/main/data/schema/structure.json",
"title": "Structures",
"description": "Structure definitions",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Name of this substructure"
},
"structure": {
"description": "Structure properties",
"type": "object",
"additionalProperties": false,
"properties": {
"map": {
"type": "string",
"description": "Cell-based representation of the structure using palette entries"
},
"mask": {
"type": "string",
"description": "A speceial palette character that indicates that map cell should be transparent"
},
"palette": {
"description": "Structure properties",
"type": "object"
},
"waypoints": {
"description": "Single-location waypoint definitions",
"type": "array",
"items": {
"$ref": "./explicit-waypoint.json"
}
},
"placements": {
"description": "Structure placements",
"type": "array",
"items": {
"$ref": "./placement.json"
}
},
"structures": {
"description": "Nested structure definitions",
"type": "array",
"items": {
"$ref": "#"
}
}
}
}
}
}

View File

@ -4,7 +4,40 @@
"title": "Swarm world",
"description": "Description of the world in the Swarm game",
"type": "object",
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"description": "Name of this subworld"
},
"default": {
"description": "Default world cell content",
"type": "array",
"items": {
"type": "string"
}
},
"structures": {
"description": "Structure definitions",
"type": "array",
"items": {
"$ref": "./structure.json"
}
},
"placements": {
"description": "Structure placements",
"type": "array",
"items": {
"$ref": "./placement.json"
}
},
"waypoints": {
"description": "Single-location waypoint definitions",
"type": "array",
"items": {
"$ref": "./explicit-waypoint.json"
}
},
"dsl": {
"default": null,
"type": "string",
@ -26,6 +59,13 @@
"examples": [{"T": ["grass", "tree"]}],
"description": "The palette maps single character keys to tuples representing contents of cells in the world, so that a world containing entities and robots can be drawn graphically. See Cells for the contents of the tuples representing a cell."
},
"portals": {
"description": "A list of portal definitions that reference waypoints.",
"type": "array",
"items": {
"$ref": "./portal.json"
}
},
"map": {
"default": "",
"type": "string",

View File

@ -29,6 +29,26 @@ You can get it by:
- **TBD** get the VSIX from GitHub releases
- **TBD** installing from the VS codium free marketplace
### YAML schema validation
To configure YAML editor tabs for schema validation, install the [YAML plugin](https://marketplace.visualstudio.com/items?itemName=redhat.vscode-yaml) and add the following to `.vscode/settings.json` under the workspace root:
```json
{
"yaml.schemas": {
"data/schema/scenario.json": [
"data/scenarios/**/*.yaml"
],
"data/schema/entities.json": [
"data/entities.yaml"
],
"data/schema/recipes.json": [
"data/recipes.yaml"
],
}
}
```
## Vim and Neovim
Currently there is neither highlighting nor LSP support for Vim,

View File

@ -0,0 +1,9 @@
#!/bin/bash -e
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
cd $SCRIPT_DIR/..
find data/scenarios -name "*.yaml" -type f -print0 | xargs -0 check-jsonschema --base-uri $(git rev-parse --show-toplevel)/data/schema/scenario.json --schemafile data/schema/scenario.json
check-jsonschema --base-uri $(git rev-parse --show-toplevel)/data/schema/entities.json --schemafile data/schema/entities.json data/entities.yaml
check-jsonschema --base-uri $(git rev-parse --show-toplevel)/data/schema/recipes.json --schemafile data/schema/recipes.json data/recipes.yaml