From 01b37316529752a1fe982f3028bf9065b5a5fbc1 Mon Sep 17 00:00:00 2001 From: Alex Korzhikov Date: Thu, 21 Sep 2017 17:20:42 +0200 Subject: [PATCH] refactor(state): Add draft-06 meta schema to tests; Add uriKeys variable to contain id/$id changes; Fix boolean schema descend resolution #41 --- lib/utils/environment.js | 7 + lib/utils/index.js | 3 + lib/utils/state.js | 21 +-- test/json-schema-test-suite.js | 1 + test/resources/draft-06-schema.json | 211 ++++++++++++++++++++++++++++ 5 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 test/resources/draft-06-schema.json diff --git a/lib/utils/environment.js b/lib/utils/environment.js index 215447e..80f8605 100644 --- a/lib/utils/environment.js +++ b/lib/utils/environment.js @@ -7,6 +7,7 @@ const properties = require('./properties'); const keywords = require('./keywords'); const validators = require('../validators'); const formats = require('./formats'); +const { uriKeys } = require('./'); const contains = require('../validators/contains'); const constant = require('../validators/const'); @@ -58,11 +59,17 @@ const environmentConfig = { propertyNames }); + // TODO optimize uri-reference regex... too long Object.assign(formats, { 'json-pointer': '!/^$|^\\/(?:~(?=[01])|[^~])*$/i.test(%s)', // add empty valid string, 'uri-reference': '!/^(?:[A-Za-z][A-Za-z0-9+\\-.]*:(?:\\/\\/(?:(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\\.[A-Za-z0-9\\-._~!$&\'()*+,;=:]+)\\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\\/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\\/(?:(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\\/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\\/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\\?(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*)?(?:\\#(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*)?|(?:\\/\\/(?:(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:]|%[0-9A-Fa-f]{2})*@)?(?:\\[(?:(?:(?:(?:[0-9A-Fa-f]{1,4}:){6}|::(?:[0-9A-Fa-f]{1,4}:){5}|(?:[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,1}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){3}|(?:(?:[0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})?::(?:[0-9A-Fa-f]{1,4}:){2}|(?:(?:[0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}:|(?:(?:[0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})?::)(?:[0-9A-Fa-f]{1,4}:[0-9A-Fa-f]{1,4}|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?:[0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})?::[0-9A-Fa-f]{1,4}|(?:(?:[0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})?::)|[Vv][0-9A-Fa-f]+\\.[A-Za-z0-9\\-._~!$&\'()*+,;=:]+)\\]|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)|(?:[A-Za-z0-9\\-._~!$&\'()*+,;=]|%[0-9A-Fa-f]{2})*)(?::[0-9]*)?(?:\\/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|\\/(?:(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})+(?:\\/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*)?|(?:[A-Za-z0-9\\-._~!$&\'()*+,;=@]|%[0-9A-Fa-f]{2})+(?:\\/(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@]|%[0-9A-Fa-f]{2})*)*|)(?:\\?(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*)?(?:\\#(?:[A-Za-z0-9\\-._~!$&\'()*+,;=:@\\/?]|%[0-9A-Fa-f]{2})*)?)$/i.test(%s)', 'uri-template': '!/^(?:(?:[^\\x00-\\x20"\'<>%\\\\^`{|}]|%[0-9a-f]{2})|\\{[+#.\\/;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\\:[1-9][0-9]{0,3}|\\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\\:[1-9][0-9]{0,3}|\\*)?)*\\})*$/i.test(%s)', }); + + if (uriKeys.indexOf('id') !== -1) { + uriKeys.splice(uriKeys.indexOf('id'), 1, '$id'); + uriKeys.id = '$id'; + } }, }; diff --git a/lib/utils/index.js b/lib/utils/index.js index 699cb14..f3accd7 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -185,6 +185,8 @@ function makeSchema(instance) { }; } +const uriKeys = Object.assign(['id', '$ref'], { id: 'id' }); + module.exports = { cleanId, asExpression, @@ -198,4 +200,5 @@ module.exports = { head, fragment, normalize, + uriKeys, }; diff --git a/lib/utils/state.js b/lib/utils/state.js index e862f4c..fc67181 100644 --- a/lib/utils/state.js +++ b/lib/utils/state.js @@ -9,6 +9,8 @@ const { cleanId, fragment, transformSchema, + uriKeys, + isSchema, } = require('./'); function State(schema = {}, env) { @@ -116,7 +118,6 @@ State.prototype = Object.assign(Object.create(Array.prototype), { } const reversed = this.slice().reverse(); - const uriKeys = ['id', '$ref']; // find last full URI schema let lastFullURIReference; @@ -173,14 +174,14 @@ State.prototype = Object.assign(Object.create(Array.prototype), { let parentSchema = this .slice() .reverse() - .find(({ id }) => !!id) + .find(({ [uriKeys.id]: id }) => !!id) || this[0]; // Same procedure as no path if (path) { // TODO make separate function ...byId // TODO preprocess ids like it was before (concat...), then check in env separately... - const resolvedId = cleanId(makePath([parentSchema.id, path])); + const resolvedId = cleanId(makePath([parentSchema[uriKeys.id], path])); // TODO refactor reverse, slice on every call parentSchema = ( hasProperty(this.env.resolved, resolvedId) && @@ -189,7 +190,7 @@ State.prototype = Object.assign(Object.create(Array.prototype), { this .slice() .reverse() - .find(({ id }) => cleanId(id) === resolvedId) || + .find(({ [uriKeys.id]: id }) => cleanId(id) === resolvedId) || parentSchema; } @@ -231,16 +232,16 @@ State.prototype = Object.assign(Object.create(Array.prototype), { const currentSchema = parts .map(normalize) .reduce((schema, part, index) => { - const subSchema = schema[part] || ( - schema.definitions && - schema.definitions[part] - ); + let subSchema = schema[part]; + if (!isSchema(subSchema)) { + subSchema = schema.definitions && schema.definitions[part]; + } if ( // last will be pushed on visit // @see /draft4/refRemote.json:http://localhost:1234/scope_change_defs2.json index !== parts.length - 1 && - hasProperty(subSchema, 'id') + hasProperty(subSchema, uriKeys.id) ) { this.push(subSchema); } @@ -248,7 +249,7 @@ State.prototype = Object.assign(Object.create(Array.prototype), { return subSchema; }, parentSchema); - return currentSchema || parentSchema; + return isSchema(currentSchema) ? currentSchema : parentSchema; }, /** * @name resolve diff --git a/test/json-schema-test-suite.js b/test/json-schema-test-suite.js index bfa1a82..c537e85 100644 --- a/test/json-schema-test-suite.js +++ b/test/json-schema-test-suite.js @@ -9,6 +9,7 @@ const refs = { 'http://localhost:1234/subSchemas.json': require('json-schema-test-suite/remotes/subSchemas.json'), 'http://localhost:1234/folder/folderInteger.json': require('json-schema-test-suite/remotes/folder/folderInteger.json'), 'http://json-schema.org/draft-04/schema': require('./resources/draft-04-schema.json'), + 'http://json-schema.org/draft-06/schema': require('./resources/draft-06-schema.json'), }; const factory = function djvTestSuiteAdapter(version) { diff --git a/test/resources/draft-06-schema.json b/test/resources/draft-06-schema.json new file mode 100644 index 0000000..16154dc --- /dev/null +++ b/test/resources/draft-06-schema.json @@ -0,0 +1,211 @@ +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#" + } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { + "$ref": "#/definitions/nonNegativeInteger" + }, + { + "default": 0 + } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true, + "default": [] + } + }, + "type": [ + "object", + "boolean" + ], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minLength": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "$ref": "#" + }, + "items": { + "anyOf": [ + { + "$ref": "#" + }, + { + "$ref": "#/definitions/schemaArray" + } + ], + "default": {} + }, + "maxItems": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minItems": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { + "$ref": "#" + }, + "maxProperties": { + "$ref": "#/definitions/nonNegativeInteger" + }, + "minProperties": { + "$ref": "#/definitions/nonNegativeIntegerDefault0" + }, + "required": { + "$ref": "#/definitions/stringArray" + }, + "additionalProperties": { + "$ref": "#" + }, + "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" + } + ] + } + }, + "propertyNames": { + "$ref": "#" + }, + "const": {}, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { + "$ref": "#/definitions/simpleTypes" + }, + { + "type": "array", + "items": { + "$ref": "#/definitions/simpleTypes" + }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { + "type": "string" + }, + "allOf": { + "$ref": "#/definitions/schemaArray" + }, + "anyOf": { + "$ref": "#/definitions/schemaArray" + }, + "oneOf": { + "$ref": "#/definitions/schemaArray" + }, + "not": { + "$ref": "#" + } + }, + "default": {} +} \ No newline at end of file