From c103dc04b94a55e33ed56b94a6e6889bebe47d0c Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin Date: Sun, 7 Aug 2016 00:37:30 +0100 Subject: [PATCH] feat: support validating [meta-]schemas against themselves, closes #259 --- lib/ajv.js | 17 ++-- spec/issues.spec.js | 9 ++ spec/remotes/hyper-schema.json | 167 +++++++++++++++++++++++++++++++++ 3 files changed, 186 insertions(+), 7 deletions(-) create mode 100644 spec/remotes/hyper-schema.json diff --git a/lib/ajv.js b/lib/ajv.js index 64ecba2..f06251d 100644 --- a/lib/ajv.js +++ b/lib/ajv.js @@ -106,8 +106,7 @@ function Ajv(opts) { * @return {Function} validating function */ function compile(schema, _meta) { - var schemaObj = _addSchema(schema); - schemaObj.meta = _meta; + var schemaObj = _addSchema(schema, undefined, _meta); return schemaObj.validate || _compile(schemaObj); } @@ -127,8 +126,7 @@ function Ajv(opts) { // can key/id have # inside? key = resolve.normalizeId(key || schema.id); checkUnique(key); - var schemaObj = self._schemas[key] = _addSchema(schema, _skipValidation, true); - schemaObj.meta = _meta; + self._schemas[key] = _addSchema(schema, _skipValidation, _meta, true); } @@ -248,7 +246,7 @@ function Ajv(opts) { } - function _addSchema(schema, skipValidation, shouldAddSchema) { + function _addSchema(schema, skipValidation, meta, shouldAddSchema) { if (typeof schema != 'object') throw new Error('schema should be object'); var jsonStr = stableStringify(schema); var cached = self._cache.get(jsonStr); @@ -259,7 +257,9 @@ function Ajv(opts) { var id = resolve.normalizeId(schema.id); if (id && shouldAddSchema) checkUnique(id); - if (self._opts.validateSchema !== false && !skipValidation) + var willValidate = self._opts.validateSchema !== false && !skipValidation; + var recursiveMeta; + if (willValidate && !(recursiveMeta = schema.id && schema.id == schema.$schema)) validateSchema(schema, true); var localRefs = resolve.ids.call(self, schema); @@ -268,12 +268,15 @@ function Ajv(opts) { id: id, schema: schema, localRefs: localRefs, - jsonStr: jsonStr + jsonStr: jsonStr, + meta: meta }); if (id[0] != '#' && shouldAddSchema) self._refs[id] = schemaObj; self._cache.put(jsonStr, schemaObj); + if (willValidate && recursiveMeta) validateSchema(schema, true); + return schemaObj; } diff --git a/spec/issues.spec.js b/spec/issues.spec.js index 4c30c8d..a8904df 100644 --- a/spec/issues.spec.js +++ b/spec/issues.spec.js @@ -457,3 +457,12 @@ describe('issue #240, mutually recursive fragment refs reference a common schema validate({ data: { type: 'Foo', id: '123' } }) .should.equal(false); } }); + + +describe('issue #259, support validating [meta-]schemas against themselves', function() { + it('should add schema before validation if "id" is the same as "$schema"', function() { + var ajv = new Ajv; + var hyperSchema = require('./remotes/hyper-schema.json'); + ajv.addMetaSchema(hyperSchema); + }); +}); diff --git a/spec/remotes/hyper-schema.json b/spec/remotes/hyper-schema.json new file mode 100644 index 0000000..fee0cfb --- /dev/null +++ b/spec/remotes/hyper-schema.json @@ -0,0 +1,167 @@ +{ + "$schema": "http://json-schema.org/draft-04/hyper-schema#", + "id": "http://json-schema.org/draft-04/hyper-schema#", + "title": "JSON Hyper-Schema", + "allOf": [ + { + "$ref": "http://json-schema.org/draft-04/schema#" + } + ], + "properties": { + "additionalItems": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#" + } + ] + }, + "additionalProperties": { + "anyOf": [ + { + "type": "boolean" + }, + { + "$ref": "#" + } + ] + }, + "dependencies": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#" + }, + { + "type": "array" + } + ] + } + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ] + }, + "definitions": { + "additionalProperties": { + "$ref": "#" + } + }, + "patternProperties": { + "additionalProperties": { + "$ref": "#" + } + }, + "properties": { + "additionalProperties": { + "$ref": "#" + } + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + }, + + "links": { + "type": "array", + "items": { + "$ref": "#/definitions/linkDescription" + } + }, + "fragmentResolution": { + "type": "string" + }, + "media": { + "type": "object", + "properties": { + "type": { + "description": "A media type, as described in RFC 2046", + "type": "string" + }, + "binaryEncoding": { + "description": "A content encoding scheme, as described in RFC 2045", + "type": "string" + } + } + }, + "pathStart": { + "description": "Instances' URIs must start with this value for this schema to apply to them", + "type": "string", + "format": "uri" + } + }, + "definitions": { + "schemaArray": { + "type": "array", + "items": { + "$ref": "#" + } + }, + "linkDescription": { + "title": "Link Description Object", + "type": "object", + "required": [ "href", "rel" ], + "properties": { + "href": { + "description": "a URI template, as defined by RFC 6570, with the addition of the $, ( and ) characters for pre-processing", + "type": "string" + }, + "rel": { + "description": "relation to the target resource of the link", + "type": "string" + }, + "title": { + "description": "a title for the link", + "type": "string" + }, + "targetSchema": { + "description": "JSON Schema describing the link target", + "$ref": "#" + }, + "mediaType": { + "description": "media type (as defined by RFC 2046) describing the link target", + "type": "string" + }, + "method": { + "description": "method for requesting the target of the link (e.g. for HTTP this might be \"GET\" or \"DELETE\")", + "type": "string" + }, + "encType": { + "description": "The media type in which to submit data along with the request", + "type": "string", + "default": "application/json" + }, + "schema": { + "description": "Schema describing the data to submit along with the request", + "$ref": "#" + } + } + } + }, + "links": [ + { + "rel": "self", + "href": "{+id}" + }, + { + "rel": "full", + "href": "{+($ref)}" + } + ] +}