diff options
author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-05-23 15:49:25 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-05-23 15:49:25 -0400 |
commit | 54e102f343b651ac41b2ce34d38a3a4638192d4a (patch) | |
tree | ffd0c0473807e0ff5299d32942290cb2d2d411c1 /ext/js/data/json-schema.js | |
parent | 8e330d54d6bca67b06a6afda3119b0e4bded41e6 (diff) |
Json schema ref support (#1708)
* Add basic support for JSON schema $ref
* Add tests
Diffstat (limited to 'ext/js/data/json-schema.js')
-rw-r--r-- | ext/js/data/json-schema.js | 89 |
1 files changed, 87 insertions, 2 deletions
diff --git a/ext/js/data/json-schema.js b/ext/js/data/json-schema.js index bd6d9022..a6306c3a 100644 --- a/ext/js/data/json-schema.js +++ b/ext/js/data/json-schema.js @@ -25,6 +25,7 @@ class JsonSchema { this._startSchema = schema; this._rootSchema = typeof rootSchema !== 'undefined' ? rootSchema : schema; this._regexCache = null; + this._refCache = null; this._valueStack = []; this._schemaStack = []; @@ -73,7 +74,8 @@ class JsonSchema { } getObjectPropertySchema(property) { - this._schemaPush(this._startSchema, null); + const startSchemaInfo = this._getResolveSchemaInfo({schema: this._startSchema, path: null}); + this._schemaPush(startSchemaInfo.schema, startSchemaInfo.path); try { const schemaInfo = this._getObjectPropertySchemaInfo(property); return schemaInfo !== null ? new JsonSchema(schemaInfo.schema, this._rootSchema) : null; @@ -83,7 +85,8 @@ class JsonSchema { } getArrayItemSchema(index) { - this._schemaPush(this._startSchema, null); + const startSchemaInfo = this._getResolveSchemaInfo({schema: this._startSchema, path: null}); + this._schemaPush(startSchemaInfo.schema, startSchemaInfo.path); try { const schemaInfo = this._getArrayItemSchemaInfo(index); return schemaInfo !== null ? new JsonSchema(schemaInfo.schema, this._rootSchema) : null; @@ -267,6 +270,71 @@ class JsonSchema { return value1 === value2; } + _getResolveSchemaInfo(schemaInfo) { + const ref = schemaInfo.schema.$ref; + if (typeof ref !== 'string') { return schemaInfo; } + + const {path: basePath} = schemaInfo; + const {schema, path} = this._getReference(ref); + if (Array.isArray(basePath)) { + path.unshift(...basePath); + } else { + path.unshift(basePath); + } + return {schema, path}; + } + + _getReference(ref) { + if (!ref.startsWith('#/')) { + throw this._createError(`Unsupported reference path: ${ref}`); + } + + let info; + if (this._refCache !== null) { + info = this._refCache.get(ref); + } else { + this._refCache = new Map(); + } + + if (typeof info === 'undefined') { + info = this._getReferenceUncached(ref); + this._refCache.set(ref, info); + } + + return {schema: info.schema, path: [...info.path]}; + } + + _getReferenceUncached(ref) { + const visited = new Set(); + const path = []; + while (true) { + if (visited.has(ref)) { + throw this._createError(`Recursive reference: ${ref}`); + } + visited.add(ref); + + const pathParts = ref.substring(2).split('/'); + let schema = this._rootSchema; + try { + for (const pathPart of pathParts) { + schema = schema[pathPart]; + } + } catch (e) { + throw this._createError(`Invalid reference: ${ref}`); + } + if (!this._isObject(schema)) { + throw this._createError(`Invalid reference: ${ref}`); + } + + path.push(null, ...pathParts); + + ref = schema.$ref; + if (typeof ref !== 'string') { + return {schema, path}; + } + } + } + // Validation _isValidCurrent(value) { @@ -279,6 +347,22 @@ class JsonSchema { } _validate(value) { + const ref = this._schema.$ref; + const schemaInfo = (typeof ref === 'string') ? this._getReference(ref) : null; + + if (schemaInfo === null) { + this._validateInner(value); + } else { + this._schemaPush(schemaInfo.schema, schemaInfo.path); + try { + this._validateInner(value); + } finally { + this._schemaPop(); + } + } + } + + _validateInner(value) { this._validateSingleSchema(value); this._validateConditional(value); this._validateAllOf(value); @@ -638,6 +722,7 @@ class JsonSchema { } _getValidValueOrDefault(path, value, schemaInfo) { + schemaInfo = this._getResolveSchemaInfo(schemaInfo); this._schemaPush(schemaInfo.schema, schemaInfo.path); this._valuePush(value, path); try { |