Skip to content

Commit

Permalink
Validate definition files against schema (#402)
Browse files Browse the repository at this point in the history
  • Loading branch information
m-linner-ericsson authored Nov 20, 2024
1 parent bdec223 commit c13c64a
Show file tree
Hide file tree
Showing 3 changed files with 262 additions and 20 deletions.
217 changes: 217 additions & 0 deletions definition_schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://json-schema.org/draft/2020-12/schema",
"$vocabulary": {
"https://json-schema.org/draft/2020-12/vocab/core": true,
"https://json-schema.org/draft/2020-12/vocab/applicator": true,
"https://json-schema.org/draft/2020-12/vocab/unevaluated": true,
"https://json-schema.org/draft/2020-12/vocab/validation": true,
"https://json-schema.org/draft/2020-12/vocab/meta-data": true,
"https://json-schema.org/draft/2020-12/vocab/format-annotation": true,
"https://json-schema.org/draft/2020-12/vocab/content": true
},
"$dynamicAnchor": "meta",
"title": "Core and Validation specifications meta-schema",
"allOf": [
{
"$ref": "meta/core"
},
{
"$ref": "meta/applicator"
},
{
"$ref": "meta/unevaluated"
},
{
"$ref": "meta/validation"
},
{
"$ref": "meta/meta-data"
},
{
"$ref": "meta/format-annotation"
},
{
"$ref": "meta/content"
}
],
"unevaluatedProperties": false,
"type": [
"object",
"boolean"
],
"$comment": "This meta schema for the Eiffel Definitions files is a copy of a meta schema with some additions.",
"properties": {
"_description": {
"type": "string"
},
"_abbrev": {
"type": "string"
},
"_name": {
"type": "string"
},
"_version": {
"type": "string"
},
"additionalProperties": {
"type": "boolean"
},
"_format": {
"type": "string"
},
"_examples": {
"type": "array",
"items": {
"type": "object",
"properties": {
"title": {
"type": "string"
},
"url": {
"type": "string",
"format": "uri-reference"
}
},
"required": [
"title",
"url"
],
"additionalProperties": false
}
},
"_history": {
"type": "array",
"items": {
"type": "object",
"properties": {
"version": {
"type": "string"
},
"introduced_in": {
"type": "string"
},
"changes": {
"type": "string"
}
},
"required": [
"version",
"changes"
],
"additionalProperties": false
}
},
"_links": {
"type": "object",
"patternProperties": {
"^[A-Z_]+$": {
"type": "object",
"properties": {
"description": {
"type": "string"
},
"required": {
"type": "boolean"
},
"multiple": {
"type": "boolean"
},
"experimental": {
"type": "boolean"
},
"targets": {
"type": "object",
"properties": {
"any_type": {
"type": "boolean"
},
"types": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"any_type",
"types"
],
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"any_type": {
"const": true
}
}
},
"then": {
"properties": {
"types": {
"maxItems": 0
}
}
},
"else": {
"properties": {
"types": {
"minItems": 1
}
}
}
}
]
}
},
"required": [
"description",
"required",
"multiple",
"targets"
],
"additionalProperties": false
}
}
},
"properties": {
"type": "object"
},
"definitions": {
"$comment": "\"definitions\" has been replaced by \"$defs\".",
"type": "object",
"additionalProperties": {
"$dynamicRef": "#meta"
},
"deprecated": true,
"default": {}
},
"dependencies": {
"$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.",
"type": "object",
"additionalProperties": {
"anyOf": [
{
"$dynamicRef": "#meta"
},
{
"$ref": "meta/validation#/$defs/stringArray"
}
]
},
"deprecated": true,
"default": {}
},
"$recursiveAnchor": {
"$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".",
"$ref": "meta/core#/$defs/anchorString",
"deprecated": true
},
"$recursiveRef": {
"$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".",
"$ref": "meta/core#/$defs/uriReferenceString",
"deprecated": true
}
}
}
38 changes: 19 additions & 19 deletions eiffel-syntax-and-usage/event-schemas.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,26 @@ The documentation of each event is available in a Markdown file that covers the

Both schemas and documentation files are generated from _schema definition files_; YAML files with a custom schema based on the JSON schema specification. Like the schema files themselves there's one file per event and version, e.g. [definitions/EiffelCompositionDefinedEvent/3.2.0.yml](../definitions/EiffelCompositionDefinedEvent/3.2.0.yml). The main difference between these files and normal JSON schema files, apart from the YAML representation, is an additional set of keys with an underscore prefix that contains additional metadata that can't be part of the JSON schema itself. Those keys are used when generating the documentation and are simply ignored when the schema definition files are turned into schema files. The following table describes these additional top-level keys.

| Key | Description |
| --------------- | ----------------------- |
| `_name` | The name of the type, e.g. "EiffelCompositionDefinedEvent". This string must match the name of the parent directory. |
| `_version` | The version of the event or other type, e.g. "3.1.0". This string must match the name of the definition file, except for the filename suffix. |
| `_abbrev` | The abbreviation of the event name, e.g. "CD" for EiffelCompositionDefinedEvent. |
| `_description` | An overall description of the event. |
| `_links` | An object describing the valid link types for the event. |
| `_links.<link type>.description` | A description of the link type. |
| `_links.<link type>.required` | A boolean value indicating whether a link of this type is required. Optional, default false. |
| `_links.<link type>.multiple` | A boolean value indicating whether multiple links of this type is allowed. Optional, default false. |
| `_links.<link type>.experimental` | A boolean value indicating whether the link type is experimental, i.e. the only valid target is an experimental event type. Optional, default false. |
| `_links.<link type>.targets.any_type` | A boolean value indicating whether the link can point to an event of any type. |
| `_links.<link type>.targets.types` | A string array of event names that the link type may point to. Must be non-empty if `any_type` is false, and must be empty if `any_type` is true. |
| Key | Description |
| --------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------|
| `_name` | The name of the type, e.g. "EiffelCompositionDefinedEvent". This string must match the name of the parent directory. |
| `_version` | The version of the event or other type, e.g. "3.1.0". This string must match the name of the definition file, except for the filename suffix. |
| `_abbrev` | The abbreviation of the event name, e.g. "CD" for EiffelCompositionDefinedEvent. |
| `_description` | An overall description of the event. |
| `_links` | An object describing the valid link types for the event. |
| `_links.<link type>.description` | A description of the link type. |
| `_links.<link type>.required` | A boolean value indicating whether a link of this type is required. |
| `_links.<link type>.multiple` | A boolean value indicating whether multiple links of this type is allowed. |
| `_links.<link type>.experimental` | A boolean value indicating whether the link type is experimental, i.e. the only valid target is an experimental event type. Optional, default false. |
| `_links.<link type>.targets.any_type` | A boolean value indicating whether the link can point to an event of any type. |
| `_links.<link type>.targets.types` | A string array of event names that the link type may point to. Must be non-empty if `any_type` is false, and must be empty if `any_type` is true. |
| `_history` | An array of objects describing the event type's version history, up to and including the current version. The items should be sorted in reverse order. |
| `_history.version` | The event version described in this item. |
| `_history.introduced_in` | The Git tag of the first edition where this version was available, or null if the version hasn't been released in a protocol edition. |
| `_history.changes` | A short description of the changes in this item's event version. |
| `_examples` | An array of objects describing examples of this event. |
| `_examples.title` | The name of the example. |
| `_examples.url` | The URL of the example. |
| `_history.version` | The event version described in this item. |
| `_history.introduced_in` | The Git tag of the first edition where this version was available, or null if the version hasn't been released in a protocol edition. |
| `_history.changes` | A short description of the changes in this item's event version. |
| `_examples` | An array of objects describing examples of this event. |
| `_examples.title` | The name of the example. |
| `_examples.url` | The URL of the example. |

In addition, the object that describes each property in the schema supports a few additional keys that are specific to the schema definition format and will be omitted when the schema file is produced:

Expand Down
27 changes: 26 additions & 1 deletion test_definitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
from pathlib import Path

import jsonschema
import pytest

import definition_loader
Expand Down Expand Up @@ -50,6 +51,12 @@ def __init__(self, path: Path):
OTHER_DEFINITION_IDS = [d.id for d in OTHER_DEFINITIONS]


@pytest.fixture(scope="session")
def yaml_validator():
definition_schema = json.load(open("definition_schema.json"))
return jsonschema.Draft202012Validator(definition_schema)


@pytest.fixture(scope="session")
def manifest():
return generate_manifest.Manifest("event_manifest.yml")
Expand Down Expand Up @@ -114,6 +121,24 @@ def test_filename_matches_type_version_fields(definition_file):
assert version == definition_file.definition["_version"]


@pytest.mark.parametrize(
"definition_file",
EVENT_DEFINITIONS_W_LINKS_REQUIRED + OTHER_DEFINITIONS,
ids=EVENT_DEFINITION_W_LINKS_REQUIRED_IDS + OTHER_DEFINITION_IDS,
)
def test_schema_validation(definition_file, yaml_validator):
# If you try to just add on to the meta schema it doesn't work see this for more info
# https://stackoverflow.com/questions/54719010/how-do-you-extend-json-schema-meta-schema-to-support-new-properties
#
# You should be able to create a vocabulary, but I have not managed. See the following for more
# https://stackoverflow.com/questions/64138556/how-do-i-define-my-own-json-schema-keyword-and-vocabulary
# https://json-schema.org/draft/2019-09/json-schema-core#rfc.appendix.D

# Note that additionalProperties and properties have been included again. Assuming that in the meta schema these
# properties can only reside in certain places not valid for our definition files
yaml_validator.validate(definition_file.definition)


@pytest.mark.parametrize(
"definition_file",
EVENT_DEFINITIONS_W_LINKS_REQUIRED,
Expand Down

0 comments on commit c13c64a

Please sign in to comment.