From cd42d20823b2cc5765be10fc4535672953bf6741 Mon Sep 17 00:00:00 2001 From: Francois Daoust Date: Thu, 15 Sep 2022 00:04:04 +0200 Subject: [PATCH] Add schemas for all JSON extracts This provides a first level of schema validation for curated data extracts, see #657 for context. Goal is to make it easier to detect and document (through a changelog, so also useful for #704) situations where we change the structure of data extracts. Schemas, notably those that deal with parsed IDL structures, could go deeper into details. Tests are run against the curated version of data. That is not necessary for extracts that aren't actually curated (dfns, headings, ids, links, refs), just more convenient not to have branching logic in the test code. --- package-lock.json | 124 ++++++++++++++++++++++++- package.json | 2 + schemas/common.json | 134 ++++++++++++++++++++++++++++ schemas/events.json | 52 +++++++++++ schemas/extract-css.json | 73 +++++++++++++++ schemas/extract-dfns.json | 59 ++++++++++++ schemas/extract-events.json | 39 ++++++++ schemas/extract-headings.json | 27 ++++++ schemas/extract-idlnamesparsed.json | 5 ++ schemas/extract-idlparsed.json | 67 ++++++++++++++ schemas/extract-ids.json | 16 ++++ schemas/extract-links.json | 28 ++++++ schemas/extract-refs.json | 21 +++++ schemas/idlnames.json | 16 ++++ schemas/index.json | 40 +++++++++ test/schemas.js | 66 ++++++++++++++ 16 files changed, 765 insertions(+), 4 deletions(-) create mode 100644 schemas/common.json create mode 100644 schemas/events.json create mode 100644 schemas/extract-css.json create mode 100644 schemas/extract-dfns.json create mode 100644 schemas/extract-events.json create mode 100644 schemas/extract-headings.json create mode 100644 schemas/extract-idlnamesparsed.json create mode 100644 schemas/extract-idlparsed.json create mode 100644 schemas/extract-ids.json create mode 100644 schemas/extract-links.json create mode 100644 schemas/extract-refs.json create mode 100644 schemas/idlnames.json create mode 100644 schemas/index.json create mode 100644 test/schemas.js diff --git a/package-lock.json b/package-lock.json index cf7c4997a899..10ccc955218c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,10 @@ "name": "webref", "version": "0.0.1", "license": "MIT", + "dependencies": { + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1" + }, "devDependencies": { "@actions/core": "1.9.1", "@jsdevtools/npm-publish": "1.4.3", @@ -20,7 +24,7 @@ "css-tree": "2.2.1", "flags": "0.1.3", "mocha": "10.0.0", - "reffy": "^10.0.1", + "reffy": "10.0.1", "rimraf": "3.0.2", "webidl2": "24.2.2" }, @@ -367,6 +371,37 @@ "node": ">= 6.0.0" } }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -845,6 +880,11 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -1216,6 +1256,11 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -1516,6 +1561,14 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, "node_modules/puppeteer": { "version": "17.1.1", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-17.1.1.tgz", @@ -1604,6 +1657,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1875,6 +1936,14 @@ "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", "dev": true }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2158,7 +2227,7 @@ }, "packages/css": { "name": "@webref/css", - "version": "5.1.1", + "version": "5.1.2", "dev": true, "license": "MIT", "peerDependencies": { @@ -2173,13 +2242,13 @@ }, "packages/events": { "name": "@webref/events", - "version": "1.0.1", + "version": "1.1.0", "dev": true, "license": "MIT" }, "packages/idl": { "name": "@webref/idl", - "version": "3.16.0", + "version": "3.16.1", "dev": true, "license": "MIT", "peerDependencies": { @@ -2484,6 +2553,25 @@ "debug": "4" } }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + } + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -2840,6 +2928,11 @@ "yauzl": "^2.10.0" } }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -3100,6 +3193,11 @@ "argparse": "^2.0.1" } }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3317,6 +3415,11 @@ "once": "^1.3.1" } }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, "puppeteer": { "version": "17.1.1", "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-17.1.1.tgz", @@ -3386,6 +3489,11 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -3584,6 +3692,14 @@ "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", "dev": true }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index dd666e2684d6..1aadb2404c80 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,8 @@ "@webref/elements": "file:packages/elements", "@webref/events": "file:packages/events", "@webref/idl": "file:packages/idl", + "ajv": "^8.11.0", + "ajv-formats": "^2.1.1", "css-tree": "2.2.1", "flags": "0.1.3", "mocha": "10.0.0", diff --git a/schemas/common.json b/schemas/common.json new file mode 100644 index 000000000000..bb9e395cc09a --- /dev/null +++ b/schemas/common.json @@ -0,0 +1,134 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/common.json", + + "$defs": { + "url": { + "type": "string", + "format": "uri" + }, + + "nullableurl": { + "oneOf": [ + { "$ref": "#/$defs/url" }, + { "type": "null" } + ], + "$comment": "Extracts sometimes use null values for URLs, they should probably rather drop the property" + }, + + "title": { + "type": "string" + }, + + "shortname": { + "type": "string", + "pattern": "^[\\w\\-]+((?<=-\\d+)\\.\\d+)?$", + "$comment": "Same definition as in browser-specs" + }, + + "specInExtract": { + "type": "object", + "additionalProperties": false, + "properties": { + "title": { "$ref": "#/$defs/title" }, + "url": { "$ref": "#/$defs/url" } + }, + "required": ["title", "url"] + }, + + "cssPropertyName": { + "type": "string" + }, + + "cssValue": { + "type": "string" + }, + + "interface": { + "type": "string", + "pattern": "^[A-Z]([A-Za-z0-9_])*|console$", + "$comment": "console is the only interface name that starts with a lower-case character" + }, + + "global": { + "oneOf": [ + { "$ref": "#/$defs/interface" }, + { "type": "string", "const": "*" } + ] + }, + + "id": { + "type": "string" + }, + + "headingNumber": { + "type": "string", + "pattern": "^(\\d+|[A-Z])(\\.\\d+)*$", + "$comment": "Note appendices start with an upper-case A-Z character" + }, + + "interfaces": { + "type": "array", + "items": { "$ref": "#/$defs/interface" } + }, + + "interfacesByGlobal": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/global" }, + "additionalProperties": { "$ref": "#/$defs/interfaces" } + }, + + "idlFragmentInSpec": { + "type": "object", + "additionalProperties": false, + "required": ["spec", "fragment"], + "properties": { + "spec": { "$ref": "#/$defs/specInExtract" }, + "fragment": { "type": "string" }, + "href": { "$ref": "#/$defs/url" } + } + }, + + "idlnameparsed": { + "type": "object", + "additionalProperties": false, + "required": ["name", "type", "defined", "extended", "includes"], + "properties": { + "name": { "$ref": "#/$defs/interface" }, + "type": { + "type": "string", + "enum": ["dictionary", "interface", "interface mixin", "enum", "typedef", + "callback", "callback interface", "namespace"] + }, + "defined": { "$ref": "#/$defs/idlFragmentInSpec" }, + "extended": { + "type": "array", + "items": { "$ref": "#/$defs/idlFragmentInSpec" } + }, + "inheritance": { + "oneOf": [ + { "type": "null" }, + { "$ref": "#/$defs/idlnameparsed" } + ] + }, + "includes": { + "type": "array", + "items": { "$ref": "#/$defs/idlnameparsed" } + } + } + }, + + "references": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "url": { "$ref": "#/$defs/url" } + } + } + } + } +} \ No newline at end of file diff --git a/schemas/events.json b/schemas/events.json new file mode 100644 index 000000000000..5d6bfa6d2ad5 --- /dev/null +++ b/schemas/events.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/events.json", + + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["type", "interface", "targets"], + "properties": { + "type": { "type": "string" }, + "interface": { "$ref": "common.json#/$defs/interface" }, + "targets": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["target"], + "properties": { + "target": { "$ref": "common.json#/$defs/interface" }, + "bubbles": { "type": "boolean" }, + "bubblingPath": { + "type": "array", + "items": { "$ref": "common.json#/$defs/interface" } + } + } + } + }, + "href": { "$ref": "common.json#/$defs/nullableurl" }, + "src": { + "type": "object", + "additionalProperties": false, + "properties": { + "format": { "type": "string" }, + "href": { "$ref": "common.json#/$defs/nullableurl" } + } + }, + "extendedIn": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["spec", "href"], + "properties": { + "spec": { "$ref": "common.json#/$defs/shortname" }, + "href": { "$ref": "common.json#/$defs/nullableurl" } + } + } + } + } + } +} diff --git a/schemas/extract-css.json b/schemas/extract-css.json new file mode 100644 index 000000000000..f556f85e2e3c --- /dev/null +++ b/schemas/extract-css.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-css.json", + + "type": "object", + "additionalProperties": false, + "required": ["spec", "properties", "atrules", "valuespaces"], + "properties": { + "spec": { "$ref": "common.json#/$defs/specInExtract" }, + + "properties": { + "type": "object", + "propertyNames": { "$ref": "common.json#/$defs/cssPropertyName" }, + "additionalProperties": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { "$ref": "common.json#/$defs/cssPropertyName" }, + "value": { "$ref": "common.json#/$defs/cssValue" }, + "styleDeclaration": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + } + } + } + }, + + "atrules": { + "type": "object", + "propertyNames": { + "type": "string", + "pattern": "^@" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "value": { "$ref": "common.json#/$defs/cssValue" }, + "descriptors": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { "type": "string" }, + "for": { "type": "string" }, + "value": { "$ref": "common.json#/$defs/cssValue" } + } + } + } + } + } + }, + + "valuespaces": { + "type": "object", + "propertyNames": { + "type": "string", + "pattern": "^<[^>]+>$" + }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "prose": { "type": "string" }, + "value": { "$ref": "common.json#/$defs/cssValue" }, + "legacyValue": { "$ref": "common.json#/$defs/cssValue" } + } + } + } + } +} diff --git a/schemas/extract-dfns.json b/schemas/extract-dfns.json new file mode 100644 index 000000000000..26e63d98fdeb --- /dev/null +++ b/schemas/extract-dfns.json @@ -0,0 +1,59 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-dfns.json", + + "type": "object", + "additionalProperties": false, + "required": ["spec", "dfns"], + "properties": { + "spec": { "$ref": "common.json#/$defs/specInExtract" }, + + "dfns": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "href", "linkingText", "localLinkingText", + "type", "for", "access", "informative", "heading", "definedIn"], + "properties": { + "id": { "$ref": "common.json#/$defs/id" }, + "href": { "$ref": "common.json#/$defs/url" }, + "linkingText": { + "type": "array", + "items": { "type": "string" } + }, + "localLinkingText": { + "type": "array", + "items": { "type": "string" } + }, + "type": { "type": "string" }, + "for": { + "type": "array", + "items": { "type": "string" } + }, + "access": { + "type": "string", + "enum": ["private", "public"] + }, + "informative": { + "type": "boolean" + }, + "heading": { + "type": "object", + "additionalProperties": false, + "required": ["id", "href", "title"], + "properties": { + "id": { "$ref": "common.json#/$defs/id" }, + "href": { "$ref": "common.json#/$defs/url" }, + "title": { "type": "string" }, + "number": { "$ref": "common.json#/$defs/headingNumber" } + } + }, + "definedIn": { + "type": "string" + } + } + } + } + } +} diff --git a/schemas/extract-events.json b/schemas/extract-events.json new file mode 100644 index 000000000000..9d0c57c93445 --- /dev/null +++ b/schemas/extract-events.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-events.json", + + "type": "object", + "additionalProperties": false, + "required": ["spec", "events"], + "properties": { + "spec": { "$ref": "common.json#/$defs/specInExtract" }, + + "events": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["type", "interface"], + "properties": { + "type": { "type": "string" }, + "interface": { "$ref": "common.json#/$defs/interface" }, + "targets": { + "type": "array", + "items": { "$ref": "common.json#/$defs/interface" } + }, + "bubbles": { "type": "boolean" }, + "isExtension": { "type": "boolean" }, + "href": { "$ref": "common.json#/$defs/nullableurl" }, + "src": { + "type": "object", + "additionalProperties": false, + "properties": { + "format": { "type": "string" }, + "href": { "$ref": "common.json#/$defs/nullableurl" } + } + } + } + } + } + } +} diff --git a/schemas/extract-headings.json b/schemas/extract-headings.json new file mode 100644 index 000000000000..86e77c774bea --- /dev/null +++ b/schemas/extract-headings.json @@ -0,0 +1,27 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-headings.json", + + "type": "object", + "additionalProperties": false, + "required": ["spec", "headings"], + "properties": { + "spec": { "$ref": "common.json#/$defs/specInExtract" }, + + "headings": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["id", "href", "title", "level"], + "properties": { + "id": { "$ref": "common.json#/$defs/id" }, + "href": { "$ref": "common.json#/$defs/url" }, + "title": { "type": "string" }, + "level": { "type": "integer" }, + "number": { "$ref": "common.json#/$defs/headingNumber" } + } + } + } + } +} diff --git a/schemas/extract-idlnamesparsed.json b/schemas/extract-idlnamesparsed.json new file mode 100644 index 000000000000..54d34580a98d --- /dev/null +++ b/schemas/extract-idlnamesparsed.json @@ -0,0 +1,5 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-idlnamesparsed.json", + "$ref": "common.json#/$defs/idlnameparsed" +} diff --git a/schemas/extract-idlparsed.json b/schemas/extract-idlparsed.json new file mode 100644 index 000000000000..ae8bd71da995 --- /dev/null +++ b/schemas/extract-idlparsed.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-idlparsed.json", + + "type": "object", + "additionalProperties": false, + "required": ["spec", "idlparsed"], + "properties": { + "spec": { "$ref": "common.json#/$defs/specInExtract" }, + + "idlparsed": { + "type": "object", + "additionalProperties": false, + "required": ["jsNames", "idlNames", "idlExtendedNames", "globals", + "exposed", "dependencies", "externalDependencies", "hasObsoleteIdl"], + "properties": { + "jsNames": { + "type": "object", + "additionalProperties": false, + "required": ["constructors", "functions"], + "properties": { + "constructors": { "$ref": "common.json#/$defs/interfacesByGlobal" }, + "functions": { "$ref": "common.json#/$defs/interfacesByGlobal" } + } + }, + "idlNames": { + "type": "object", + "propertyNames": { "$ref": "common.json#/$defs/interface" }, + "additionalProperties": { + "type": "object", + "additionalProperties": true, + "required": ["fragment", "type"], + "properties": { + "fragment": { "type": "string" }, + "type": { "type": "string" } + } + } + }, + "idlExtendedNames": { + "type": "object", + "propertyNames": { "$ref": "common.json#/$defs/interface" }, + "additionalProperties": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": true, + "required": ["fragment", "type"], + "properties": { + "fragment": { "type": "string" }, + "type": { "type": "string" } + } + } + } + }, + "globals": { "$ref": "common.json#/$defs/interfacesByGlobal" }, + "exposed": { "$ref": "common.json#/$defs/interfacesByGlobal" }, + "dependencies": { + "type": "object", + "propertyNames": { "$ref": "common.json#/$defs/interface" }, + "additionalProperties": { "$ref": "common.json#/$defs/interfaces" } + }, + "externalDependencies": { "$ref": "common.json#/$defs/interfaces" }, + "hasObsoleteIdl": { "type": "boolean" } + } + } + } +} diff --git a/schemas/extract-ids.json b/schemas/extract-ids.json new file mode 100644 index 000000000000..42613099d0b3 --- /dev/null +++ b/schemas/extract-ids.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-ids.json", + + "type": "object", + "additionalProperties": false, + "required": ["spec", "ids"], + "properties": { + "spec": { "$ref": "common.json#/$defs/specInExtract" }, + + "ids": { + "type": "array", + "items": { "$ref": "common.json#/$defs/url" } + } + } +} diff --git a/schemas/extract-links.json b/schemas/extract-links.json new file mode 100644 index 000000000000..5b9367cab70e --- /dev/null +++ b/schemas/extract-links.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-links.json", + + "type": "object", + "additionalProperties": false, + "required": ["spec", "links"], + "properties": { + "spec": { "$ref": "common.json#/$defs/specInExtract" }, + + "links": { + "type": "object", + "propertyNames": { "$ref": "common.json#/$defs/url" }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "properties": { + "anchors": { + "type": "array", + "items": { "$ref": "common.json#/$defs/id" }, + "minItems": 1 + }, + "specShortname": { "$ref": "common.json#/$defs/shortname" } + } + } + } + } +} diff --git a/schemas/extract-refs.json b/schemas/extract-refs.json new file mode 100644 index 000000000000..6df78a984cb4 --- /dev/null +++ b/schemas/extract-refs.json @@ -0,0 +1,21 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/extract-refs.json", + + "type": "object", + "additionalProperties": false, + "required": ["spec", "refs"], + "properties": { + "spec": { "$ref": "common.json#/$defs/specInExtract" }, + + "refs": { + "type": "object", + "additionalProperties": false, + "required": ["normative", "informative"], + "properties": { + "normative": { "$ref": "common.json#/$defs/references" }, + "informative": { "$ref": "common.json#/$defs/references" } + } + } + } +} diff --git a/schemas/idlnames.json b/schemas/idlnames.json new file mode 100644 index 000000000000..5fa35959ae85 --- /dev/null +++ b/schemas/idlnames.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/idlnames.json", + + "type": "object", + "propertyNames": { "$ref": "common.json#/$defs/interface" }, + "additionalProperties": { + "type": "object", + "additionalProperties": false, + "required": ["fragment", "parsed"], + "properties": { + "fragment": { "type": "string" }, + "parsed": { "type": "string" } + } + } +} diff --git a/schemas/index.json b/schemas/index.json new file mode 100644 index 000000000000..83a1111fdfe1 --- /dev/null +++ b/schemas/index.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$id": "https://w3c.github.io/webref/schemas/index.json", + + "type": "object", + "additionalProperties": false, + "required": ["type", "title", "date", "stats", "crawler", "results"], + "properties": { + "type": { "type": "string", "const": "crawl" }, + "title": { "type": "string" }, + "date": { + "type": "string", + "pattern": "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z" + }, + "crawler": { "type": "string" }, + + "options": { "type": "object" }, + + "stats": { + "type": "object", + "additionalProperties": false, + "required": ["crawled", "errors"], + "properties": { + "crawled": { + "type": "integer" + }, + "errors": { + "type": "integer" + } + } + }, + + "results": { + "type": "array", + "items": { + "type": "object" + } + } + } +} diff --git a/test/schemas.js b/test/schemas.js new file mode 100644 index 000000000000..2d32a2463d76 --- /dev/null +++ b/test/schemas.js @@ -0,0 +1,66 @@ +/** + * Test individual elements extracts. + * + * The tests run against the curated view of the extracts. + */ + +const fs = require('fs'); +const path = require('path'); +const Ajv = require('ajv'); +const addFormats = require('ajv-formats'); +const assert = require('assert').strict; +const commonSchema = require('../schemas/common.json'); + +const schemaFiles = fs.readdirSync(path.join(__dirname, '..', 'schemas')); +for (const schemaFile of schemaFiles) { + if (schemaFile.endsWith('.json')) { + const schema = require(path.join('..', 'schemas', schemaFile)); + const ajv = new Ajv(); + addFormats(ajv); + + if (schemaFile.startsWith('extract-')) { + const dataFolder = schemaFile.replace(/^extract-(.*)\.json$/, '$1'); + + describe(`The ${dataFolder} folder`, function () { + it('is linked to a valid JSON schema', () => { + const isSchemaValid = ajv.validateSchema(schema); + assert.ok(isSchemaValid); + }); + + describe(`The ${dataFolder} folder`, function () { + const folder = path.join(__dirname, '..', 'curated', dataFolder); + const files = fs.readdirSync(folder); + const validate = ajv.addSchema(commonSchema).compile(schema); + + for (const file of files) { + if (file.endsWith('.json')) { + it(`has valid data in ${file}`, () => { + const data = require(path.join(folder, file)); + const isValid = validate(data, { format: 'full' }); + assert.strictEqual(validate.errors, null); + assert.ok(isValid); + }); + } + } + }); + }); + } + + else if (fs.existsSync(path.join(__dirname, '..', 'curated', schemaFile))) { + describe(`The ${schemaFile} file`, function () { + it('is linked to a valid JSON schema', () => { + const isSchemaValid = ajv.validateSchema(schema); + assert.ok(isSchemaValid); + }); + + it('has valid data', () => { + const validate = ajv.addSchema(commonSchema).compile(schema); + const data = require(path.join('..', 'curated', schemaFile)); + const isValid = validate(data, { format: 'full' }); + assert.strictEqual(validate.errors, null); + assert.ok(isValid); + }); + }); + } + } +}