Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# official-ajv-draft-04-meta-schema

## What this file is

`official-ajv-draft-04-meta-schema.json` is the canonical JSON Schema draft-04 meta-schema — the schema that describes what a valid draft-04 JSON Schema looks like. It is sourced from the `ajv-draft-04` npm package (`node_modules/ajv-draft-04/dist/refs/json-schema-draft-04.json`), which itself mirrors the official specification document published by the JSON Schema organization.

## Why it exists here

`@adobe/reactor-validator` uses AJV v8 to validate Tags extension descriptors against the turbine platform schemas (`@adobe/reactor-turbine-schemas`). Those schemas declare `"$schema": "http://json-schema.org/draft-04/schema#"`, meaning they are draft-04 JSON Schemas.

AJV v8 ships meta-schemas for draft-06, draft-07, draft 2019-09, and draft 2020-12 — but not draft-04. Previously, the `ajv-draft-04` package was used as a dependency to supply both the draft-04 vocabulary and this meta-schema. That package declares `ajv@^8` as an optional peer dependency, which causes a runtime resolution failure for downstream consumers: npm nests `ajv@8` under this package's own `node_modules`, while `ajv-draft-04` gets hoisted to the consumer's top-level `node_modules` and resolves `require('ajv/dist/core')` against whatever AJV version the consumer has installed — which may be v6.

To eliminate that broken dependency chain, `ajv-draft-04` was removed and AJV v8 is now configured directly. This file provides the draft-04 meta-schema so AJV can validate that the turbine platform schemas are well-formed draft-04 schemas at compile time, preserving the same correctness guarantees as before.

## Source

- npm package: `ajv-draft-04` (any version)
- path within package: `dist/refs/json-schema-draft-04.json`
- upstream specification: http://json-schema.org/draft-04/schema
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
{
"id": "http://json-schema.org/draft-04/schema#",
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Core schema meta-schema",
"definitions": {
"schemaArray": {
"type": "array",
"minItems": 1,
"items": {"$ref": "#"}
},
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [{"$ref": "#/definitions/positiveInteger"}, {"default": 0}]
},
"simpleTypes": {
"enum": ["array", "boolean", "integer", "null", "number", "object", "string"]
},
"stringArray": {
"type": "array",
"items": {"type": "string"},
"minItems": 1,
"uniqueItems": true
}
},
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uri"
},
"$schema": {
"type": "string",
"format": "uri"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"default": {},
"multipleOf": {
"type": "number",
"minimum": 0,
"exclusiveMinimum": true
},
"maximum": {
"type": "number"
},
"exclusiveMaximum": {
"type": "boolean",
"default": false
},
"minimum": {
"type": "number"
},
"exclusiveMinimum": {
"type": "boolean",
"default": false
},
"maxLength": {"$ref": "#/definitions/positiveInteger"},
"minLength": {"$ref": "#/definitions/positiveIntegerDefault0"},
"pattern": {
"type": "string",
"format": "regex"
},
"additionalItems": {
"anyOf": [{"type": "boolean"}, {"$ref": "#"}],
"default": {}
},
"items": {
"anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/schemaArray"}],
"default": {}
},
"maxItems": {"$ref": "#/definitions/positiveInteger"},
"minItems": {"$ref": "#/definitions/positiveIntegerDefault0"},
"uniqueItems": {
"type": "boolean",
"default": false
},
"maxProperties": {"$ref": "#/definitions/positiveInteger"},
"minProperties": {"$ref": "#/definitions/positiveIntegerDefault0"},
"required": {"$ref": "#/definitions/stringArray"},
"additionalProperties": {
"anyOf": [{"type": "boolean"}, {"$ref": "#"}],
"default": {}
},
"definitions": {
"type": "object",
"additionalProperties": {"$ref": "#"},
"default": {}
},
"properties": {
"type": "object",
"additionalProperties": {"$ref": "#"},
"default": {}
},
"patternProperties": {
"type": "object",
"additionalProperties": {"$ref": "#"},
"default": {}
},
"dependencies": {
"type": "object",
"additionalProperties": {
"anyOf": [{"$ref": "#"}, {"$ref": "#/definitions/stringArray"}]
}
},
"enum": {
"type": "array",
"minItems": 1,
"uniqueItems": true
},
"type": {
"anyOf": [
{"$ref": "#/definitions/simpleTypes"},
{
"type": "array",
"items": {"$ref": "#/definitions/simpleTypes"},
"minItems": 1,
"uniqueItems": true
}
]
},
"allOf": {"$ref": "#/definitions/schemaArray"},
"anyOf": {"$ref": "#/definitions/schemaArray"},
"oneOf": {"$ref": "#/definitions/schemaArray"},
"not": {"$ref": "#"}
},
"dependencies": {
"exclusiveMaximum": ["maximum"],
"exclusiveMinimum": ["minimum"]
},
"default": {}
}
133 changes: 133 additions & 0 deletions lib/createDraft4Ajv.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/***************************************************************************************
* (c) 2017 Adobe. All rights reserved.
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy
* of the License at http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
* OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
****************************************************************************************/

// Factory that creates an AJV v8 instance configured for JSON Schema draft-04.
//
// AJV v8 speaks draft-07 natively and does not ship a draft-04 meta-schema. This module
// replicates what the ajv-draft-04 package provided, using only ajv's own internal modules,
// so downstream consumers are not exposed to ajv-draft-04's broken optional peer-dependency
// chain (see: https://github.com/adobe/reactor-validator/issues/XX).

'use strict';
var _ajvCore = require('ajv/dist/core');
var AjvCore = _ajvCore.default;
var _ = _ajvCore._;
var str = _ajvCore.str;
var ops = require('ajv/dist/compile/codegen').operators;
var addAJVFormats = require('ajv-formats');
var draft4MetaSchema = require('../OpenJSFoundation/draft04/official-ajv-draft-04-meta-schema/official-ajv-draft-04-meta-schema.json');

var DRAFT4_META_SCHEMA_ID = 'http://json-schema.org/draft-04/schema';

// JSON Schema draft-04 and draft-07 express "exclusive" range limits differently:
//
// draft-04: two keywords — a numeric "minimum" plus a boolean flag "exclusiveMinimum: true"
// e.g. { "minimum": 0, "exclusiveMinimum": true } means value must be > 0
//
// draft-07: one keyword — "exclusiveMinimum" is itself the numeric limit
// e.g. { "exclusiveMinimum": 0 } means value must be > 0
//
// AJV v8 speaks draft-07 natively, so it expects exclusiveMinimum/exclusiveMaximum to always
// be numbers. The draft-04 meta-schema we register (so AJV can validate that the turbine platform
// schemas are well-formed draft-04) uses the boolean flag style internally. Without custom
// handlers, AJV v8 throws "exclusiveMinimum value must be [number]" when it tries to compile
// that meta-schema.
//
// limitNumberDraft4 replaces AJV v8's built-in limitNumber. It handles "minimum"/"maximum" as
// numeric keywords but peeks at the parent schema for a boolean exclusiveMinimum/exclusiveMaximum
// flag, switching between strict (>) and non-strict (>=) comparisons accordingly.
//
// limitNumberExclusiveDraft4 registers "exclusiveMinimum"/"exclusiveMaximum" as valid boolean
// keywords so AJV does not reject them. It generates no data-validation code — the actual
// comparison logic lives in limitNumberDraft4 above.
var LIMIT_KWDS = {
maximum: {
exclusive: 'exclusiveMaximum',
ops: [
{ okStr: '<=', ok: ops.LTE, fail: ops.GT },
{ okStr: '<', ok: ops.LT, fail: ops.GTE },
],
},
minimum: {
exclusive: 'exclusiveMinimum',
ops: [
{ okStr: '>=', ok: ops.GTE, fail: ops.LT },
{ okStr: '>', ok: ops.GT, fail: ops.LTE },
],
},
};

function kwdOp(cxt) {
var kwd = LIMIT_KWDS[cxt.keyword];
var opsIdx = (cxt.parentSchema && cxt.parentSchema[kwd.exclusive]) ? 1 : 0;
return kwd.ops[opsIdx];
}

var limitNumberDraft4 = {
keyword: Object.keys(LIMIT_KWDS),
type: 'number',
schemaType: 'number',
$data: true,
error: {
message: function(cxt) { return str`must be ${kwdOp(cxt).okStr} ${cxt.schemaCode}`; },
params: function(cxt) { return _`{comparison: ${kwdOp(cxt).okStr}, limit: ${cxt.schemaCode}}`; },
},
code: function(cxt) {
cxt.fail$data(_`${cxt.data} ${kwdOp(cxt).fail} ${cxt.schemaCode} || isNaN(${cxt.data})`);
},
};

var limitNumberExclusiveDraft4 = {
keyword: ['exclusiveMaximum', 'exclusiveMinimum'],
type: 'number',
schemaType: 'boolean',
code: function(cxt) {
var paired = { exclusiveMaximum: 'maximum', exclusiveMinimum: 'minimum' };
if (cxt.parentSchema[paired[cxt.keyword]] === undefined) {
throw new Error(cxt.keyword + ' can only be used with ' + paired[cxt.keyword]);
}
},
};

var draft4CoreVocab = [
'$schema', 'id', '$defs', '$comment', 'definitions',
require('ajv/dist/vocabularies/core/ref').default,
];

var draft4ValidationVocab = [
limitNumberDraft4,
limitNumberExclusiveDraft4,
require('ajv/dist/vocabularies/validation/multipleOf').default,
require('ajv/dist/vocabularies/validation/limitLength').default,
require('ajv/dist/vocabularies/validation/pattern').default,
require('ajv/dist/vocabularies/validation/limitProperties').default,
require('ajv/dist/vocabularies/validation/required').default,
require('ajv/dist/vocabularies/validation/limitItems').default,
require('ajv/dist/vocabularies/validation/uniqueItems').default,
{ keyword: 'type', schemaType: ['string', 'array'] },
{ keyword: 'nullable', schemaType: 'boolean' },
require('ajv/dist/vocabularies/validation/const').default,
require('ajv/dist/vocabularies/validation/enum').default,
];

module.exports = function createDraft4Ajv() {
var ajv = new AjvCore({ schemaId: 'id', strict: false, defaultMeta: DRAFT4_META_SCHEMA_ID });
ajv.addVocabulary(draft4CoreVocab);
ajv.addVocabulary(draft4ValidationVocab);
ajv.addVocabulary(require('ajv/dist/vocabularies/applicator').default());
ajv.addVocabulary(require('ajv/dist/vocabularies/format').default);
ajv.addVocabulary(['title', 'description', 'default']);
ajv.addMetaSchema(draft4MetaSchema, DRAFT4_META_SCHEMA_ID, false);
ajv.refs['http://json-schema.org/schema'] = DRAFT4_META_SCHEMA_ID;
addAJVFormats(ajv);
return ajv;
};
7 changes: 3 additions & 4 deletions lib/validateSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
// Returns undefined if valid, or an error string if not.

'use strict';
var Ajv = require("ajv-draft-04");
var addAJVFormats = require("ajv-formats");
var createDraft4Ajv = require('./createDraft4Ajv');

var schemas = {
web: require('@adobe/reactor-turbine-schemas/schemas/extension-package-web.json'),
edge: require('@adobe/reactor-turbine-schemas/schemas/extension-package-edge.json'),
mobile: require('@adobe/reactor-turbine-schemas/schemas/extension-package-mobile.json')
};

var ajv = createDraft4Ajv();

var validateJsonStructure = function(extensionDescriptor) {
var platform = extensionDescriptor.platform;
if (!platform) {
Expand All @@ -33,8 +34,6 @@ var validateJsonStructure = function(extensionDescriptor) {
if (!extensionDescriptorSchema) {
return 'unknown platform "' + platform + '".';
}
var ajv = new Ajv({ schemaId: 'auto', strict: false });
addAJVFormats(ajv);
if (!ajv.validate(extensionDescriptorSchema, extensionDescriptor)) {
return ajv.errorsText();
}
Expand Down
Loading
Loading