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 | |
| parent | 8e330d54d6bca67b06a6afda3119b0e4bded41e6 (diff) | |
Json schema ref support (#1708)
* Add basic support for JSON schema $ref
* Add tests
Diffstat (limited to 'ext/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 { |