feat: support $id, #384

master
Evgeny Poberezkin 2017-02-27 00:18:42 +00:00
parent 917f20c5c3
commit 4f631e61b8
13 changed files with 439 additions and 169 deletions

View File

@ -69,6 +69,7 @@ function Ajv(opts) {
this._loadingSchemas = {};
this._compilations = [];
this.RULES = rules();
this._getId = chooseGetId(opts);
opts.loopRequired = opts.loopRequired || Infinity;
if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true;
@ -136,9 +137,10 @@ function addSchema(schema, key, _skipValidation, _meta) {
for (var i=0; i<schema.length; i++) this.addSchema(schema[i], undefined, _skipValidation, _meta);
return;
}
if (schema.id !== undefined && typeof schema.id != 'string')
var id = this._getId(schema);
if (id !== undefined && typeof id != 'string')
throw new Error('schema id must be string');
key = resolve.normalizeId(key || schema.id);
key = resolve.normalizeId(key || id);
checkUnique(this, key);
this._schemas[key] = this._addSchema(schema, _skipValidation, _meta, true);
}
@ -193,7 +195,7 @@ function validateSchema(schema, throwOrLogError) {
function defaultMeta(self) {
var meta = self._opts.meta;
self._opts.defaultMeta = typeof meta == 'object'
? meta.id || meta
? self._getId(meta) || meta
: self.getSchema(META_SCHEMA_ID)
? META_SCHEMA_ID
: undefined;
@ -273,7 +275,7 @@ function removeSchema(schemaKeyRef) {
var serialize = this._opts.serialize;
var cacheKey = serialize ? serialize(schemaKeyRef) : schemaKeyRef;
this._cache.del(cacheKey);
var id = schemaKeyRef.id;
var id = this._getId(schemaKeyRef);
if (id) {
id = resolve.normalizeId(id);
delete this._schemas[id];
@ -305,12 +307,12 @@ function _addSchema(schema, skipValidation, meta, shouldAddSchema) {
shouldAddSchema = shouldAddSchema || this._opts.addUsedSchema !== false;
var id = resolve.normalizeId(schema.id);
var id = resolve.normalizeId(this._getId(schema));
if (id && shouldAddSchema) checkUnique(this, id);
var willValidate = this._opts.validateSchema !== false && !skipValidation;
var recursiveMeta;
if (willValidate && !(recursiveMeta = schema.id && schema.id == schema.$schema))
if (willValidate && !(recursiveMeta = id && id == resolve.normalizeId(schema.$schema)))
this.validateSchema(schema, true);
var localRefs = resolve.ids.call(this, schema);
@ -374,6 +376,34 @@ function _compile(schemaObj, root) {
}
function chooseGetId(opts) {
switch (opts.schemaId) {
case '$id': return _get$Id;
case 'id': return _getId;
default: return _get$IdOrId;
}
}
function _getId(schema) {
if (schema.$id) console.warn('schema $id ignored', schema.$id);
return schema.id;
}
function _get$Id(schema) {
if (schema.id) console.warn('schema id ignored', schema.id);
return schema.$id;
}
function _get$IdOrId(schema) {
if (schema.$id && schema.id && schema.$id != schema.id)
throw new Error('schema $id is different from id');
return schema.$id || schema.id;
}
/**
* Convert array of error message objects to string
* @this Ajv
@ -413,7 +443,7 @@ function addDraft6MetaSchema(self) {
var $dataSchema;
if (self._opts.$data) {
$dataSchema = require('./refs/$data.json');
self.addMetaSchema($dataSchema, $dataSchema.id, true);
self.addMetaSchema($dataSchema, $dataSchema.$id, true);
}
if (self._opts.meta === false) return;
var metaSchema = require('./refs/json-schema-draft-06.json');

View File

@ -68,7 +68,7 @@ function resolveSchema(root, ref) {
/* jshint validthis: true */
var p = url.parse(ref, false, true)
, refPath = _getFullPath(p)
, baseId = getFullPath(root.schema.id);
, baseId = getFullPath(this._getId(root.schema));
if (refPath !== baseId) {
var id = normalizeId(refPath);
var refVal = this._refs[id];
@ -89,7 +89,7 @@ function resolveSchema(root, ref) {
}
}
if (!root.schema) return;
baseId = getFullPath(root.schema.id);
baseId = getFullPath(this._getId(root.schema));
}
return getJsonPointer.call(this, p, baseId, root.schema, root);
}
@ -103,7 +103,8 @@ function resolveRecursive(root, ref, parsedRef) {
var schema = res.schema;
var baseId = res.baseId;
root = res.root;
if (schema.id) baseId = resolveUrl(baseId, schema.id);
var id = this._getId(schema);
if (id) baseId = resolveUrl(baseId, id);
return getJsonPointer.call(this, parsedRef, baseId, schema, root);
}
}
@ -123,7 +124,9 @@ function getJsonPointer(parsedRef, baseId, schema, root) {
part = util.unescapeFragment(part);
schema = schema[part];
if (schema === undefined) break;
if (schema.id && !PREVENT_SCOPE_CHANGE[part]) baseId = resolveUrl(baseId, schema.id);
var id;
if (!PREVENT_SCOPE_CHANGE[part] && (id = this._getId(schema)))
baseId = resolveUrl(baseId, id);
if (schema.$ref) {
var $ref = resolveUrl(baseId, schema.$ref);
var res = resolveSchema.call(this, root, $ref);
@ -227,7 +230,7 @@ function resolveUrl(baseId, id) {
function resolveIds(schema) {
/* eslint no-shadow: 0 */
/* jshint validthis: true */
var id = normalizeId(schema.id);
var id = normalizeId(this._getId(schema));
var localRefs = {};
_resolveIds.call(this, schema, getFullPath(id, false), id);
return localRefs;
@ -239,11 +242,9 @@ function resolveIds(schema) {
for (var i=0; i<schema.length; i++)
_resolveIds.call(this, schema[i], fullPath+'/'+i, baseId);
} else if (schema && typeof schema == 'object') {
if (typeof schema.id == 'string') {
var id = baseId = baseId
? url.resolve(baseId, schema.id)
: schema.id;
id = normalizeId(id);
var id = this._getId(schema);
if (typeof id == 'string') {
id = baseId = normalizeId(baseId ? url.resolve(baseId, id) : id);
var refVal = this._refs[id];
if (typeof refVal == 'string') refVal = this._refs[refVal];

View File

@ -16,7 +16,8 @@
{{
var $async = it.schema.$async === true
, $refKeywords = it.util.schemaHasRulesExcept(it.schema, it.RULES.all, '$ref');
, $refKeywords = it.util.schemaHasRulesExcept(it.schema, it.RULES.all, '$ref')
, $id = it.self._getId(it.schema);
}}
{{? it.isTop }}
@ -40,8 +41,8 @@
{{?}}
(data, dataPath, parentData, parentDataProperty, rootData) {
'use strict';
{{? it.schema.id && (it.opts.sourceCode || it.opts.processCode) }}
{{= '/\*# sourceURL=' + it.schema.id + ' */' }}
{{? $id && (it.opts.sourceCode || it.opts.processCode) }}
{{= '/\*# sourceURL=' + $id + ' */' }}
{{?}}
{{?}}
@ -83,7 +84,7 @@
, $lvl = it.level = 0
, $dataLvl = it.dataLevel = 0
, $data = 'data';
it.rootId = it.resolve.fullPath(it.root.schema.id);
it.rootId = it.resolve.fullPath(it.self._getId(it.root.schema));
it.baseId = it.baseId || it.rootId;
delete it.isTop;
@ -99,7 +100,7 @@
, $dataLvl = it.dataLevel
, $data = 'data' + ($dataLvl || '');
if (it.schema.id) it.baseId = it.resolve.url(it.baseId, it.schema.id);
if ($id) it.baseId = it.resolve.url(it.baseId, $id);
if ($async && !it.async) throw new Error('async schema in sync schema');
}}

View File

@ -1,6 +1,6 @@
{
"id": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#",
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#",
"description": "Meta-schema for $data reference (JSON-schema extension proposal)",
"type": "object",
"required": [ "$data" ],

View File

@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-06/schema#",
"id": "http://json-schema.org/draft-06/schema#",
"$id": "http://json-schema.org/draft-06/schema#",
"title": "Core schema meta-schema",
"definitions": {
"schemaArray": {
@ -37,7 +37,7 @@
},
"type": ["object", "boolean"],
"properties": {
"id": {
"$id": {
"type": "string",
"format": "uri-ref"
},

View File

@ -1170,4 +1170,53 @@ describe('Ajv Options', function () {
});
});
});
describe('schemaId', function() {
describe('= undefined (default)', function() {
it('should throw if both id and $id are available and different', function() {
var ajv = new Ajv;
ajv.compile({
id: 'mySchema',
$id: 'mySchema'
});
should.throw(function() {
ajv.compile({
id: 'mySchema1',
$id: 'mySchema2'
});
});
});
});
describe('= "id"', function() {
it('should use id and ignore $id', function() {
var ajv = new Ajv({schemaId: 'id'});
ajv.addSchema({ id: 'mySchema1', type: 'string' });
var validate = ajv.getSchema('mySchema1');
validate('foo') .should.equal(true);
validate(1) .should.equal(false);
validate = ajv.compile({ $id: 'mySchema2', type: 'string' });
should.not.exist(ajv.getSchema('mySchema2'));
});
});
describe('= "$id"', function() {
it('should use $id and ignore id', function() {
var ajv = new Ajv({schemaId: '$id'});
ajv.addSchema({ $id: 'mySchema1', type: 'string' });
var validate = ajv.getSchema('mySchema1');
validate('foo') .should.equal(true);
validate(1) .should.equal(false);
validate = ajv.compile({ id: 'mySchema2', type: 'string' });
should.not.exist(ajv.getSchema('mySchema2'));
});
});
});
});

View File

@ -1,13 +1,22 @@
[
{
"description": "root ref in remote ref (#13)",
"schema": {
"id": "http://localhost:1234/object",
"type": "object",
"properties": {
"name": { "$ref": "name.json#/definitions/orNull" }
"schemas": [
{
"id": "http://localhost:1234/issue13_1",
"type": "object",
"properties": {
"name": { "$ref": "name.json#/definitions/orNull" }
}
},
{
"$id": "http://localhost:1234/issue13_2",
"type": "object",
"properties": {
"name": { "$ref": "name.json#/definitions/orNull" }
}
}
},
],
"tests": [
{
"description": "string is valid",

View File

@ -1,11 +1,18 @@
[
{
"description": "ref in remote ref with ids",
"schema": {
"id": "http://localhost:1234/issue14a.json",
"type": "array",
"items": { "$ref": "foo.json" }
},
"schemas": [
{
"id": "http://localhost:1234/issue14a_1.json",
"type": "array",
"items": { "$ref": "foo.json" }
},
{
"$id": "http://localhost:1234/issue14a_2.json",
"type": "array",
"items": { "$ref": "foo.json" }
}
],
"tests": [
{
"description": "string is valid",
@ -29,11 +36,18 @@
},
{
"description": "remote ref in definitions in remote ref with ids (#14)",
"schema": {
"id": "http://localhost:1234/issue14b.json",
"type": "array",
"items": { "$ref": "buu.json#/definitions/buu" }
},
"schemas": [
{
"id": "http://localhost:1234/issue14b_1.json",
"type": "array",
"items": { "$ref": "buu.json#/definitions/buu" }
},
{
"$id": "http://localhost:1234/issue14b_2.json",
"type": "array",
"items": { "$ref": "buu.json#/definitions/buu" }
}
],
"tests": [
{
"description": "string is valid",

View File

@ -1,21 +1,38 @@
[
{
"description": "sibling property has id (#170)",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_object",
"type": "object",
"properties": {
"title": {
"id": "http://example.com/title",
"type": "string"
"schemas": [
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_object_1",
"type": "object",
"properties": {
"title": {
"id": "http://example.com/title",
"type": "string"
},
"file": { "$ref": "#/definitions/file-entry" }
},
"file": { "$ref": "#/definitions/file-entry" }
"definitions": {
"file-entry": { "type": "string" }
}
},
"definitions": {
"file-entry": { "type": "string" }
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://example.com/base_object_2",
"type": "object",
"properties": {
"title": {
"$id": "http://example.com/title",
"type": "string"
},
"file": { "$ref": "#/definitions/file-entry" }
},
"definitions": {
"file-entry": { "type": "string" }
}
}
},
],
"tests": [
{
"description": "valid object",
@ -37,21 +54,38 @@
},
{
"description": "sibling item has id",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_array",
"type": "array",
"items": [
{
"id": "http://example.com/0",
"type": "string"
},
{ "$ref": "#/definitions/file-entry" }
],
"definitions": {
"file-entry": { "type": "string" }
"schemas": [
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_array_1",
"type": "array",
"items": [
{
"id": "http://example.com/0",
"type": "string"
},
{ "$ref": "#/definitions/file-entry" }
],
"definitions": {
"file-entry": { "type": "string" }
}
},
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://example.com/base_array_2",
"type": "array",
"items": [
{
"$id": "http://example.com/0",
"type": "string"
},
{ "$ref": "#/definitions/file-entry" }
],
"definitions": {
"file-entry": { "type": "string" }
}
}
},
],
"tests": [
{
"description": "valid array",
@ -67,20 +101,36 @@
},
{
"description": "sibling schema in anyOf has id",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_anyof",
"anyOf": [
{
"id": "http://example.com/0",
"type": "number"
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
"schemas": [
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_anyof_1",
"anyOf": [
{
"id": "http://example.com/0",
"type": "number"
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
}
},
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://example.com/base_anyof_2",
"anyOf": [
{
"$id": "http://example.com/0",
"type": "number"
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
}
}
},
],
"tests": [
{
"description": "valid string",
@ -101,20 +151,36 @@
},
{
"description": "sibling schema in oneOf has id",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_oneof",
"oneOf": [
{
"id": "http://example.com/0",
"type": "number"
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
"schemas": [
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_oneof_1",
"oneOf": [
{
"id": "http://example.com/0",
"type": "number"
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
}
},
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://example.com/base_oneof_2",
"oneOf": [
{
"$id": "http://example.com/0",
"type": "number"
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
}
}
},
],
"tests": [
{
"description": "valid string",
@ -135,21 +201,38 @@
},
{
"description": "sibling schema in allOf has id",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_allof",
"allOf": [
{
"id": "http://example.com/0",
"type": "string",
"maxLength": 3
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
"schemas": [
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_allof_1",
"allOf": [
{
"id": "http://example.com/0",
"type": "string",
"maxLength": 3
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
}
},
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://example.com/base_allof_2",
"allOf": [
{
"$id": "http://example.com/0",
"type": "string",
"maxLength": 3
},
{ "$ref": "#/definitions/def" }
],
"definitions": {
"def": { "type": "string" }
}
}
},
],
"tests": [
{
"description": "valid string",
@ -165,21 +248,38 @@
},
{
"description": "sibling schema in dependencies has id",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_dependencies",
"type": "object",
"dependencies": {
"foo": {
"id": "http://example.com/foo",
"required": [ "bar" ]
"schemas": [
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://example.com/base_dependencies_1",
"type": "object",
"dependencies": {
"foo": {
"id": "http://example.com/foo",
"required": [ "bar" ]
},
"bar": { "$ref": "#/definitions/def" }
},
"bar": { "$ref": "#/definitions/def" }
"definitions": {
"def": { "required": [ "baz" ] }
}
},
"definitions": {
"def": { "required": [ "baz" ] }
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "http://example.com/base_dependencies_2",
"type": "object",
"dependencies": {
"foo": {
"$id": "http://example.com/foo",
"required": [ "bar" ]
},
"bar": { "$ref": "#/definitions/def" }
},
"definitions": {
"def": { "required": [ "baz" ] }
}
}
},
],
"tests": [
{
"description": "valid object",

View File

@ -1,15 +1,26 @@
[
{
"description": "IDs in refs without root id (#1)",
"schema": {
"definitions": {
"int": {
"id": "#int",
"type": "integer"
}
"schemas": [
{
"definitions": {
"int": {
"id": "#int",
"type": "integer"
}
},
"$ref": "#int"
},
"$ref": "#int"
},
{
"definitions": {
"int": {
"$id": "#int",
"type": "integer"
}
},
"$ref": "#int"
}
],
"tests": [
{ "description": "valid", "data": 1, "valid": true },
{ "description": "invalid", "data": "foo", "valid": false }
@ -17,16 +28,28 @@
},
{
"description": "IDs in refs with root id",
"schema": {
"id": "http://example.com/int.json",
"definitions": {
"int": {
"id": "#int",
"type": "integer"
}
"schemas": [
{
"id": "http://example.com/int_1.json",
"definitions": {
"int": {
"id": "#int",
"type": "integer"
}
},
"$ref": "#int"
},
"$ref": "#int"
},
{
"$id": "http://example.com/int_2.json",
"definitions": {
"int": {
"$id": "#int",
"type": "integer"
}
},
"$ref": "#int"
}
],
"tests": [
{ "description": "valid", "data": 1, "valid": true },
{ "description": "invalid", "data": "foo", "valid": false }

View File

@ -1,33 +1,62 @@
[
{
"description": "Recursive reference (#27)",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "testrec",
"type": "object",
"properties": {
"layout": {
"id": "layout",
"type": "object",
"properties": {
"layout": { "type": "string" },
"panels": {
"type": "array",
"items": {
"anyOf": [
{ "type": "string" },
{ "$ref": "layout" }
]
"schemas": [
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "testrec_1",
"type": "object",
"properties": {
"layout": {
"id": "layout",
"type": "object",
"properties": {
"layout": { "type": "string" },
"panels": {
"type": "array",
"items": {
"anyOf": [
{ "type": "string" },
{ "$ref": "layout" }
]
}
}
}
},
"required": [
"layout",
"panels"
]
},
"required": [
"layout",
"panels"
]
}
}
},
{
"$schema": "http://json-schema.org/draft-06/schema#",
"$id": "testrec_2",
"type": "object",
"properties": {
"layout": {
"$id": "layout",
"type": "object",
"properties": {
"layout": { "type": "string" },
"panels": {
"type": "array",
"items": {
"anyOf": [
{ "type": "string" },
{ "$ref": "layout" }
]
}
}
},
"required": [
"layout",
"panels"
]
}
}
}
},
],
"tests": [
{
"description": "empty object is valid",

View File

@ -1,12 +1,20 @@
[
{
"description": "id property in referenced schema in object that is not a schema (#63)",
"schema": {
"type" : "object",
"properties": {
"title": { "$ref": "http://json-schema.org/draft-04/schema#/properties/title" }
"schemas": [
{
"type" : "object",
"properties": {
"title": { "$ref": "http://json-schema.org/draft-04/schema#/properties/title" }
}
},
{
"type" : "object",
"properties": {
"title": { "$ref": "http://json-schema.org/draft-06/schema#/properties/title" }
}
}
},
],
"tests": [
{
"description": "empty object is valid",

View File

@ -2,7 +2,7 @@
{
"description": "hash ref inside hash ref in remote ref (#70, was passing)",
"schema": {
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
},
"tests": [
{ "data": 1, "valid": true, "description": "positive integer is valid" },
@ -12,10 +12,16 @@
},
{
"description": "hash ref inside hash ref in remote ref with id (#70, was passing)",
"schema": {
"id": "http://example.com/my_schema.json",
"schemas": [
{
"id": "http://example.com/my_schema_1.json",
"$ref": "http://json-schema.org/draft-04/schema#/definitions/positiveIntegerDefault0"
},
},
{
"$id": "http://example.com/my_schema_2.json",
"$ref": "http://json-schema.org/draft-06/schema#/definitions/positiveIntegerDefault0"
}
],
"tests": [
{ "data": 1, "valid": true, "description": "positive integer is valid" },
{ "data": 0, "valid": true, "description": "zero is valid" },