diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-05-22 15:45:20 -0400 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-05-22 15:45:20 -0400 | 
| commit | d16739a83a20e1729e08dbcbbc155be15972d146 (patch) | |
| tree | f7c12fa946a688750365ecf0f66227fcad4f927d | |
| parent | b48052ff320f1a68aac317158c4c757a70b14f04 (diff) | |
Json schema validation improvements (#1697)
* Create new JsonSchema class
* Add proxy handler
* Update tests
* Update validation scripts
* Update backend
* Update audio downloader
* Update options util
* Update dictionary importer
* Update json schema file reference
* Remove old json-schema.js
* Rename new json-schema.js
* Update file names
* Rename class
| -rw-r--r-- | dev/dictionary-validate.js | 6 | ||||
| -rw-r--r-- | dev/schema-validate.js | 4 | ||||
| -rw-r--r-- | ext/js/background/backend.js | 5 | ||||
| -rw-r--r-- | ext/js/background/profile-conditions-util.js | 12 | ||||
| -rw-r--r-- | ext/js/data/json-schema.js | 967 | ||||
| -rw-r--r-- | ext/js/data/options-util.js | 14 | ||||
| -rw-r--r-- | ext/js/language/dictionary-importer.js | 24 | ||||
| -rw-r--r-- | ext/js/media/audio-downloader.js | 12 | ||||
| -rw-r--r-- | test/test-json-schema.js | 8 | ||||
| -rw-r--r-- | test/test-profile-conditions-util.js | 11 | 
10 files changed, 574 insertions, 489 deletions
| diff --git a/dev/dictionary-validate.js b/dev/dictionary-validate.js index 29f2eae4..83889fc8 100644 --- a/dev/dictionary-validate.js +++ b/dev/dictionary-validate.js @@ -27,7 +27,7 @@ vm.execute([      'js/general/cache-map.js',      'js/data/json-schema.js'  ]); -const JsonSchemaValidator = vm.get('JsonSchemaValidator'); +const JsonSchema = vm.get('JsonSchema');  function readSchema(relativeFileName) { @@ -46,7 +46,7 @@ async function validateDictionaryBanks(zip, fileNameFormat, schema) {          if (!file) { break; }          const data = JSON.parse(await file.async('string')); -        new JsonSchemaValidator().validate(data, schema); +        new JsonSchema(schema).validate(data);          ++index;      } @@ -61,7 +61,7 @@ async function validateDictionary(archive, schemas) {      const index = JSON.parse(await indexFile.async('string'));      const version = index.format || index.version; -    new JsonSchemaValidator().validate(index, schemas.index); +    new JsonSchema(schemas.index).validate(index);      await validateDictionaryBanks(archive, 'term_bank_?.json', version === 1 ? schemas.termBankV1 : schemas.termBankV3);      await validateDictionaryBanks(archive, 'term_meta_bank_?.json', schemas.termMetaBankV3); diff --git a/dev/schema-validate.js b/dev/schema-validate.js index 45285700..77fbd0c8 100644 --- a/dev/schema-validate.js +++ b/dev/schema-validate.js @@ -25,7 +25,7 @@ vm.execute([      'js/general/cache-map.js',      'js/data/json-schema.js'  ]); -const JsonSchemaValidator = vm.get('JsonSchemaValidator'); +const JsonSchema = vm.get('JsonSchema');  function main() { @@ -47,7 +47,7 @@ function main() {              console.log(`Validating ${dataFileName}...`);              const dataSource = fs.readFileSync(dataFileName, {encoding: 'utf8'});              const data = JSON.parse(dataSource); -            new JsonSchemaValidator().validate(data, schema); +            new JsonSchema(schema).validate(data);              const end = performance.now();              console.log(`No issues detected (${((end - start) / 1000).toFixed(2)}s)`);          } catch (e) { diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index e94ad065..2368b5d0 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -24,7 +24,6 @@   * DictionaryDatabase   * Environment   * JapaneseUtil - * JsonSchemaValidator   * Mecab   * MediaUtil   * ObjectPropertyAccessor @@ -58,7 +57,6 @@ class Backend {              clipboardReader: this._clipboardReader          });          this._options = null; -        this._profileConditionsSchemaValidator = new JsonSchemaValidator();          this._profileConditionsSchemaCache = [];          this._profileConditionsUtil = new ProfileConditionsUtil();          this._defaultAnkiFieldTemplates = null; @@ -1018,7 +1016,7 @@ class Backend {                  this._profileConditionsSchemaCache.push(schema);              } -            if (conditionGroups.length > 0 && this._profileConditionsSchemaValidator.isValid(optionsContext, schema)) { +            if (conditionGroups.length > 0 && schema.isValid(optionsContext)) {                  return profile;              }              ++index; @@ -1029,7 +1027,6 @@ class Backend {      _clearProfileConditionsSchemaCache() {          this._profileConditionsSchemaCache = []; -        this._profileConditionsSchemaValidator.clearCache();      }      _checkLastError() { diff --git a/ext/js/background/profile-conditions-util.js b/ext/js/background/profile-conditions-util.js index dcd60796..2de25420 100644 --- a/ext/js/background/profile-conditions-util.js +++ b/ext/js/background/profile-conditions-util.js @@ -15,6 +15,10 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +/* global + * JsonSchema + */ +  /**   * Utility class to help processing profile conditions.   */ @@ -109,11 +113,13 @@ class ProfileConditionsUtil {                  default: anyOf.push({allOf}); break;              }          } +        let schema;          switch (anyOf.length) { -            case 0: return {}; -            case 1: return anyOf[0]; -            default: return {anyOf}; +            case 0: schema = {}; break; +            case 1: schema = anyOf[0]; break; +            default: schema = {anyOf}; break;          } +        return new JsonSchema(schema);      }      /** diff --git a/ext/js/data/json-schema.js b/ext/js/data/json-schema.js index 6a590911..b5fac452 100644 --- a/ext/js/data/json-schema.js +++ b/ext/js/data/json-schema.js @@ -19,218 +19,202 @@   * CacheMap   */ -class JsonSchemaProxyHandler { -    constructor(schema, jsonSchemaValidator) { -        this._schema = schema; -        this._jsonSchemaValidator = jsonSchemaValidator; -    } +class JsonSchema { +    constructor(schema, rootSchema) { +        this._schema = null; +        this._startSchema = schema; +        this._rootSchema = typeof rootSchema !== 'undefined' ? rootSchema : schema; +        this._regexCache = null; +        this._valuePath = []; +        this._schemaPath = []; -    getPrototypeOf(target) { -        return Object.getPrototypeOf(target); +        this._schemaPush(null, null); +        this._valuePush(null, null);      } -    setPrototypeOf() { -        throw new Error('setPrototypeOf not supported'); +    get schema() { +        return this._startSchema;      } -    isExtensible(target) { -        return Object.isExtensible(target); +    get rootSchema() { +        return this._rootSchema;      } -    preventExtensions(target) { -        Object.preventExtensions(target); -        return true; +    createProxy(value) { +        return ( +            typeof value === 'object' && value !== null ? +            new Proxy(value, new JsonSchemaProxyHandler(this)) : +            value +        );      } -    getOwnPropertyDescriptor(target, property) { -        return Object.getOwnPropertyDescriptor(target, property); +    isValid(value) { +        try { +            this.validate(value); +            return true; +        } catch (e) { +            return false; +        }      } -    defineProperty() { -        throw new Error('defineProperty not supported'); +    validate(value) { +        this._schemaPush(null, this._startSchema); +        this._valuePush(null, value); +        try { +            this._validate(value); +        } finally { +            this._valuePop(); +            this._schemaPop(); +        }      } -    has(target, property) { -        return property in target; +    getValidValueOrDefault(value) { +        return this._getValidValueOrDefault(null, value, [{path: null, schema: this._startSchema}]);      } -    get(target, property) { -        if (typeof property === 'symbol') { -            return target[property]; -        } - -        if (Array.isArray(target)) { -            if (typeof property === 'string' && /^\d+$/.test(property)) { -                property = parseInt(property, 10); -            } else if (typeof property === 'string') { -                return target[property]; -            } -        } - -        const propertySchema = this._jsonSchemaValidator.getPropertySchema(this._schema, property, target); -        if (propertySchema === null) { -            return; +    getObjectPropertySchema(property) { +        this._schemaPush(null, this._startSchema); +        try { +            const schemaPath = this._getObjectPropertySchemaPath(property); +            return schemaPath !== null ? new JsonSchema(schemaPath[schemaPath.length - 1].schema, this._rootSchema) : null; +        } finally { +            this._schemaPop();          } - -        const value = target[property]; -        return value !== null && typeof value === 'object' ? this._jsonSchemaValidator.createProxy(value, propertySchema) : value;      } -    set(target, property, value) { -        if (Array.isArray(target)) { -            if (typeof property === 'string' && /^\d+$/.test(property)) { -                property = parseInt(property, 10); -                if (property > target.length) { -                    throw new Error('Array index out of range'); -                } -            } else if (typeof property === 'string') { -                target[property] = value; -                return true; -            } -        } - -        const propertySchema = this._jsonSchemaValidator.getPropertySchema(this._schema, property, target); -        if (propertySchema === null) { -            throw new Error(`Property ${property} not supported`); +    getArrayItemSchema(index) { +        this._schemaPush(null, this._startSchema); +        try { +            const schemaPath = this._getArrayItemSchemaPath(index); +            return schemaPath !== null ? new JsonSchema(schemaPath[schemaPath.length - 1].schema, this._rootSchema) : null; +        } finally { +            this._schemaPop();          } - -        value = clone(value); - -        this._jsonSchemaValidator.validate(value, propertySchema); - -        target[property] = value; -        return true;      } -    deleteProperty(target, property) { -        const required = ( -            (typeof target === 'object' && target !== null) ? -            (Array.isArray(target) || this._jsonSchemaValidator.isObjectPropertyRequired(this._schema, property)) : -            true -        ); -        if (required) { -            throw new Error(`${property} cannot be deleted`); -        } -        return Reflect.deleteProperty(target, property); +    isObjectPropertyRequired(property) { +        const {required} = this._startSchema; +        return Array.isArray(required) && required.includes(property);      } -    ownKeys(target) { -        return Reflect.ownKeys(target); -    } +    // Stack -    apply() { -        throw new Error('apply not supported'); +    _valuePush(path, value) { +        this._valuePath.push({path, value});      } -    construct() { -        throw new Error('construct not supported'); +    _valuePop() { +        this._valuePath.pop();      } -} -class JsonSchemaValidator { -    constructor() { -        this._regexCache = new CacheMap(100); +    _schemaPush(path, schema) { +        this._schemaPath.push({path, schema}); +        this._schema = schema;      } -    createProxy(target, schema) { -        return new Proxy(target, new JsonSchemaProxyHandler(schema, this)); +    _schemaPop() { +        this._schemaPath.pop(); +        this._schema = this._schemaPath[this._schemaPath.length - 1].schema;      } -    isValid(value, schema) { -        try { -            this.validate(value, schema); -            return true; -        } catch (e) { -            return false; +    // Private + +    _createError(message) { +        const valuePath = []; +        for (let i = 1, ii = this._valuePath.length; i < ii; ++i) { +            const {path, value} = this._valuePath[i]; +            valuePath.push({path, value});          } -    } -    validate(value, schema) { -        const info = new JsonSchemaTraversalInfo(value, schema); -        this._validate(value, schema, info); -    } +        const schemaPath = []; +        for (let i = 1, ii = this._schemaPath.length; i < ii; ++i) { +            const {path, schema} = this._schemaPath[i]; +            schemaPath.push({path, schema}); +        } -    getValidValueOrDefault(schema, value) { -        const info = new JsonSchemaTraversalInfo(value, schema); -        return this._getValidValueOrDefault(schema, value, info); +        const error = new Error(message); +        error.value = valuePath[valuePath.length - 1].value; +        error.schema = this._schema; +        error.valuePath = valuePath; +        error.schemaPath = schemaPath; +        return error;      } -    getPropertySchema(schema, property, value) { -        return this._getPropertySchema(schema, property, value, null); +    _isObject(value) { +        return typeof value === 'object' && value !== null && !Array.isArray(value);      } -    clearCache() { -        this._regexCache.clear(); -    } +    _getRegex(pattern, flags) { +        if (this._regexCache === null) { +            this._regexCache = new CacheMap(100); +        } -    isObjectPropertyRequired(schema, property) { -        const {required} = schema; -        return Array.isArray(required) && required.includes(property); +        const key = `${flags}:${pattern}`; +        let regex = this._regexCache.get(key); +        if (typeof regex === 'undefined') { +            regex = new RegExp(pattern, flags); +            this._regexCache.set(key, regex); +        } +        return regex;      } -    // Private - -    _getPropertySchema(schema, property, value, path) { -        const type = this._getSchemaOrValueType(schema, value); -        switch (type) { -            case 'object': -            { -                const properties = schema.properties; -                if (this._isObject(properties)) { -                    const propertySchema = properties[property]; -                    if (this._isObject(propertySchema)) { -                        if (path !== null) { path.push(['properties', properties], [property, propertySchema]); } -                        return propertySchema; -                    } -                } +    _getUnconstrainedSchema() { +        return {}; +    } -                const additionalProperties = schema.additionalProperties; -                if (additionalProperties === false) { -                    return null; -                } else if (this._isObject(additionalProperties)) { -                    if (path !== null) { path.push(['additionalProperties', additionalProperties]); } -                    return additionalProperties; -                } else { -                    const result = JsonSchemaValidator.unconstrainedSchema; -                    if (path !== null) { path.push([null, result]); } -                    return result; -                } +    _getObjectPropertySchemaPath(property) { +        const {properties} = this._schema; +        if (this._isObject(properties)) { +            const propertySchema = properties[property]; +            if (this._isObject(propertySchema)) { +                return [ +                    {path: 'properties', schema: properties}, +                    {path: property, schema: propertySchema} +                ];              } -            case 'array': -            { -                const items = schema.items; -                if (this._isObject(items)) { -                    return items; -                } -                if (Array.isArray(items)) { -                    if (property >= 0 && property < items.length) { -                        const propertySchema = items[property]; -                        if (this._isObject(propertySchema)) { -                            if (path !== null) { path.push(['items', items], [property, propertySchema]); } -                            return propertySchema; -                        } -                    } -                } +        } -                const additionalItems = schema.additionalItems; -                if (additionalItems === false) { -                    return null; -                } else if (this._isObject(additionalItems)) { -                    if (path !== null) { path.push(['additionalItems', additionalItems]); } -                    return additionalItems; -                } else { -                    const result = JsonSchemaValidator.unconstrainedSchema; -                    if (path !== null) { path.push([null, result]); } -                    return result; +        const {additionalProperties} = this._schema; +        if (additionalProperties === false) { +            return null; +        } else if (this._isObject(additionalProperties)) { +            return [{path: 'additionalProperties', schema: additionalProperties}]; +        } else { +            const result = this._getUnconstrainedSchema(); +            return [{path: null, schema: result}]; +        } +    } + +    _getArrayItemSchemaPath(index) { +        const {items} = this._schema; +        if (this._isObject(items)) { +            return [{path: 'items', schema: items}]; +        } +        if (Array.isArray(items)) { +            if (index >= 0 && index < items.length) { +                const propertySchema = items[index]; +                if (this._isObject(propertySchema)) { +                    return [ +                        {path: 'items', schema: items}, +                        {path: index, schema: propertySchema} +                    ];                  }              } -            default: -                return null; +        } + +        const {additionalItems} = this._schema; +        if (additionalItems === false) { +            return null; +        } else if (this._isObject(additionalItems)) { +            return [{path: 'additionalItems', schema: additionalItems}]; +        } else { +            const result = this._getUnconstrainedSchema(); +            return [{path: null, schema: result}];          }      } -    _getSchemaOrValueType(schema, value) { -        const type = schema.type; +    _getSchemaOrValueType(value) { +        const {type} = this._schema;          if (Array.isArray(type)) {              if (typeof value !== 'undefined') { @@ -242,345 +226,391 @@ class JsonSchemaValidator {              return null;          } -        if (typeof type === 'undefined') { -            if (typeof value !== 'undefined') { -                return this._getValueType(value); +        if (typeof type !== 'undefined') { return type; } +        return (typeof value !== 'undefined') ? this._getValueType(value) : null; +    } + +    _getValueType(value) { +        const type = typeof value; +        if (type === 'object') { +            if (value === null) { return 'null'; } +            if (Array.isArray(value)) { return 'array'; } +        } +        return type; +    } + +    _isValueTypeAny(value, type, schemaTypes) { +        if (typeof schemaTypes === 'string') { +            return this._isValueType(value, type, schemaTypes); +        } else if (Array.isArray(schemaTypes)) { +            for (const schemaType of schemaTypes) { +                if (this._isValueType(value, type, schemaType)) { +                    return true; +                }              } -            return null; +            return false;          } +        return true; +    } -        return type; +    _isValueType(value, type, schemaType) { +        return ( +            type === schemaType || +            (schemaType === 'integer' && Math.floor(value) === value) +        );      } -    _validate(value, schema, info) { -        this._validateSingleSchema(value, schema, info); -        this._validateConditional(value, schema, info); -        this._validateAllOf(value, schema, info); -        this._validateAnyOf(value, schema, info); -        this._validateOneOf(value, schema, info); -        this._validateNoneOf(value, schema, info); +    _valuesAreEqualAny(value1, valueList) { +        for (const value2 of valueList) { +            if (this._valuesAreEqual(value1, value2)) { +                return true; +            } +        } +        return false; +    } + +    _valuesAreEqual(value1, value2) { +        return value1 === value2; +    } + +    // Validation + +    _isValidCurrent(value) { +        try { +            this._validate(value); +            return true; +        } catch (e) { +            return false; +        }      } -    _validateConditional(value, schema, info) { -        const ifSchema = schema.if; +    _validate(value) { +        this._validateSingleSchema(value); +        this._validateConditional(value); +        this._validateAllOf(value); +        this._validateAnyOf(value); +        this._validateOneOf(value); +        this._validateNoneOf(value); +    } + +    _validateConditional(value) { +        const ifSchema = this._schema.if;          if (!this._isObject(ifSchema)) { return; }          let okay = true; -        info.schemaPush('if', ifSchema); +        this._schemaPush('if', ifSchema);          try { -            this._validate(value, ifSchema, info); +            this._validate(value);          } catch (e) {              okay = false; +        } finally { +            this._schemaPop();          } -        info.schemaPop(); -        const nextSchema = okay ? schema.then : schema.else; -        if (this._isObject(nextSchema)) { -            info.schemaPush(okay ? 'then' : 'else', nextSchema); -            this._validate(value, nextSchema, info); -            info.schemaPop(); +        const nextSchema = okay ? this._schema.then : this._schema.else; +        if (this._isObject(nextSchema)) { return; } + +        this._schemaPush(okay ? 'then' : 'else', nextSchema); +        try { +            this._validate(value); +        } finally { +            this._schemaPop();          }      } -    _validateAllOf(value, schema, info) { -        const subSchemas = schema.allOf; +    _validateAllOf(value) { +        const subSchemas = this._schema.allOf;          if (!Array.isArray(subSchemas)) { return; } -        info.schemaPush('allOf', subSchemas); -        for (let i = 0; i < subSchemas.length; ++i) { -            const subSchema = subSchemas[i]; -            info.schemaPush(i, subSchema); -            this._validate(value, subSchema, info); -            info.schemaPop(); +        this._schemaPush('allOf', subSchemas); +        try { +            for (let i = 0, ii = subSchemas.length; i < ii; ++i) { +                const subSchema = subSchemas[i]; +                if (!this._isObject(subSchema)) { continue; } + +                this._schemaPush(i, subSchema); +                try { +                    this._validate(value); +                } finally { +                    this._schemaPop(); +                } +            } +        } finally { +            this._schemaPop();          } -        info.schemaPop();      } -    _validateAnyOf(value, schema, info) { -        const subSchemas = schema.anyOf; +    _validateAnyOf(value) { +        const subSchemas = this._schema.anyOf;          if (!Array.isArray(subSchemas)) { return; } -        info.schemaPush('anyOf', subSchemas); -        for (let i = 0; i < subSchemas.length; ++i) { -            const subSchema = subSchemas[i]; -            info.schemaPush(i, subSchema); -            try { -                this._validate(value, subSchema, info); -                return; -            } catch (e) { -                // NOP +        this._schemaPush('anyOf', subSchemas); +        try { +            for (let i = 0, ii = subSchemas.length; i < ii; ++i) { +                const subSchema = subSchemas[i]; +                if (!this._isObject(subSchema)) { continue; } + +                this._schemaPush(i, subSchema); +                try { +                    this._validate(value); +                    return; +                } catch (e) { +                    // NOP +                } finally { +                    this._schemaPop(); +                }              } -            info.schemaPop(); -        } -        throw new JsonSchemaValidationError('0 anyOf schemas matched', value, schema, info); -        // info.schemaPop(); // Unreachable +            throw this._createError('0 anyOf schemas matched'); +        } finally { +            this._schemaPop(); +        }      } -    _validateOneOf(value, schema, info) { -        const subSchemas = schema.oneOf; +    _validateOneOf(value) { +        const subSchemas = this._schema.oneOf;          if (!Array.isArray(subSchemas)) { return; } -        info.schemaPush('oneOf', subSchemas); -        let count = 0; -        for (let i = 0; i < subSchemas.length; ++i) { -            const subSchema = subSchemas[i]; -            info.schemaPush(i, subSchema); -            try { -                this._validate(value, subSchema, info); -                ++count; -            } catch (e) { -                // NOP +        this._schemaPush('oneOf', subSchemas); +        try { +            let count = 0; +            for (let i = 0, ii = subSchemas.length; i < ii; ++i) { +                const subSchema = subSchemas[i]; +                if (!this._isObject(subSchema)) { continue; } + +                this._schemaPush(i, subSchema); +                try { +                    this._validate(value); +                    ++count; +                } catch (e) { +                    // NOP +                } finally { +                    this._schemaPop(); +                }              } -            info.schemaPop(); -        } -        if (count !== 1) { -            throw new JsonSchemaValidationError(`${count} oneOf schemas matched`, value, schema, info); +            if (count !== 1) { +                throw this._createError(`${count} oneOf schemas matched`); +            } +        } finally { +            this._schemaPop();          } - -        info.schemaPop();      } -    _validateNoneOf(value, schema, info) { -        const subSchemas = schema.not; +    _validateNoneOf(value) { +        const subSchemas = this._schema.not;          if (!Array.isArray(subSchemas)) { return; } -        info.schemaPush('not', subSchemas); -        for (let i = 0; i < subSchemas.length; ++i) { -            const subSchema = subSchemas[i]; -            info.schemaPush(i, subSchema); -            try { -                this._validate(value, subSchema, info); -            } catch (e) { -                info.schemaPop(); -                continue; +        this._schemaPush('not', subSchemas); +        try { +            for (let i = 0, ii = subSchemas.length; i < ii; ++i) { +                const subSchema = subSchemas[i]; +                if (!this._isObject(subSchema)) { continue; } + +                this._schemaPush(i, subSchema); +                try { +                    this._validate(value); +                } catch (e) { +                    continue; +                } finally { +                    this._schemaPop(); +                } +                throw this._createError(`not[${i}] schema matched`);              } -            throw new JsonSchemaValidationError(`not[${i}] schema matched`, value, schema, info); +        } finally { +            this._schemaPop();          } -        info.schemaPop();      } -    _validateSingleSchema(value, schema, info) { +    _validateSingleSchema(value) {          const type = this._getValueType(value); -        const schemaType = schema.type; +        const schemaType = this._schema.type;          if (!this._isValueTypeAny(value, type, schemaType)) { -            throw new JsonSchemaValidationError(`Value type ${type} does not match schema type ${schemaType}`, value, schema, info); +            throw this._createError(`Value type ${type} does not match schema type ${schemaType}`);          } -        const schemaConst = schema.const; +        const schemaConst = this._schema.const;          if (typeof schemaConst !== 'undefined' && !this._valuesAreEqual(value, schemaConst)) { -            throw new JsonSchemaValidationError('Invalid constant value', value, schema, info); +            throw this._createError('Invalid constant value');          } -        const schemaEnum = schema.enum; +        const schemaEnum = this._schema.enum;          if (Array.isArray(schemaEnum) && !this._valuesAreEqualAny(value, schemaEnum)) { -            throw new JsonSchemaValidationError('Invalid enum value', value, schema, info); +            throw this._createError('Invalid enum value');          }          switch (type) {              case 'number': -                this._validateNumber(value, schema, info); +                this._validateNumber(value);                  break;              case 'string': -                this._validateString(value, schema, info); +                this._validateString(value);                  break;              case 'array': -                this._validateArray(value, schema, info); +                this._validateArray(value);                  break;              case 'object': -                this._validateObject(value, schema, info); +                this._validateObject(value);                  break;          }      } -    _validateNumber(value, schema, info) { -        const multipleOf = schema.multipleOf; +    _validateNumber(value) { +        const {multipleOf} = this._schema;          if (typeof multipleOf === 'number' && Math.floor(value / multipleOf) * multipleOf !== value) { -            throw new JsonSchemaValidationError(`Number is not a multiple of ${multipleOf}`, value, schema, info); +            throw this._createError(`Number is not a multiple of ${multipleOf}`);          } -        const minimum = schema.minimum; +        const {minimum} = this._schema;          if (typeof minimum === 'number' && value < minimum) { -            throw new JsonSchemaValidationError(`Number is less than ${minimum}`, value, schema, info); +            throw this._createError(`Number is less than ${minimum}`);          } -        const exclusiveMinimum = schema.exclusiveMinimum; +        const {exclusiveMinimum} = this._schema;          if (typeof exclusiveMinimum === 'number' && value <= exclusiveMinimum) { -            throw new JsonSchemaValidationError(`Number is less than or equal to ${exclusiveMinimum}`, value, schema, info); +            throw this._createError(`Number is less than or equal to ${exclusiveMinimum}`);          } -        const maximum = schema.maximum; +        const {maximum} = this._schema;          if (typeof maximum === 'number' && value > maximum) { -            throw new JsonSchemaValidationError(`Number is greater than ${maximum}`, value, schema, info); +            throw this._createError(`Number is greater than ${maximum}`);          } -        const exclusiveMaximum = schema.exclusiveMaximum; +        const {exclusiveMaximum} = this._schema;          if (typeof exclusiveMaximum === 'number' && value >= exclusiveMaximum) { -            throw new JsonSchemaValidationError(`Number is greater than or equal to ${exclusiveMaximum}`, value, schema, info); +            throw this._createError(`Number is greater than or equal to ${exclusiveMaximum}`);          }      } -    _validateString(value, schema, info) { -        const minLength = schema.minLength; +    _validateString(value) { +        const {minLength} = this._schema;          if (typeof minLength === 'number' && value.length < minLength) { -            throw new JsonSchemaValidationError('String length too short', value, schema, info); +            throw this._createError('String length too short');          } -        const maxLength = schema.maxLength; +        const {maxLength} = this._schema;          if (typeof maxLength === 'number' && value.length > maxLength) { -            throw new JsonSchemaValidationError('String length too long', value, schema, info); +            throw this._createError('String length too long');          } -        const pattern = schema.pattern; +        const {pattern} = this._schema;          if (typeof pattern === 'string') { -            let patternFlags = schema.patternFlags; +            let {patternFlags} = this._schema;              if (typeof patternFlags !== 'string') { patternFlags = ''; }              let regex;              try {                  regex = this._getRegex(pattern, patternFlags);              } catch (e) { -                throw new JsonSchemaValidationError(`Pattern is invalid (${e.message})`, value, schema, info); +                throw this._createError(`Pattern is invalid (${e.message})`);              }              if (!regex.test(value)) { -                throw new JsonSchemaValidationError('Pattern match failed', value, schema, info); +                throw this._createError('Pattern match failed');              }          }      } -    _validateArray(value, schema, info) { -        const minItems = schema.minItems; -        if (typeof minItems === 'number' && value.length < minItems) { -            throw new JsonSchemaValidationError('Array length too short', value, schema, info); +    _validateArray(value) { +        const {length} = value; + +        const {minItems} = this._schema; +        if (typeof minItems === 'number' && length < minItems) { +            throw this._createError('Array length too short');          } -        const maxItems = schema.maxItems; -        if (typeof maxItems === 'number' && value.length > maxItems) { -            throw new JsonSchemaValidationError('Array length too long', value, schema, info); +        const {maxItems} = this._schema; +        if (typeof maxItems === 'number' && length > maxItems) { +            throw this._createError('Array length too long');          } -        this._validateArrayContains(value, schema, info); +        this._validateArrayContains(value); -        for (let i = 0, ii = value.length; i < ii; ++i) { -            const schemaPath = []; -            const propertySchema = this._getPropertySchema(schema, i, value, schemaPath); -            if (propertySchema === null) { -                throw new JsonSchemaValidationError(`No schema found for array[${i}]`, value, schema, info); +        for (let i = 0; i < length; ++i) { +            const schemaPath = this._getArrayItemSchemaPath(i); +            if (schemaPath === null) { +                throw this._createError(`No schema found for array[${i}]`);              }              const propertyValue = value[i]; -            for (const [p, s] of schemaPath) { info.schemaPush(p, s); } -            info.valuePush(i, propertyValue); -            this._validate(propertyValue, propertySchema, info); -            info.valuePop(); -            for (let j = 0, jj = schemaPath.length; j < jj; ++j) { info.schemaPop(); } +            for (const {path, schema} of schemaPath) { this._schemaPush(path, schema); } +            this._valuePush(i, propertyValue); +            try { +                this._validate(propertyValue); +            } finally { +                this._valuePop(); +                for (let j = 0, jj = schemaPath.length; j < jj; ++j) { this._schemaPop(); } +            }          }      } -    _validateArrayContains(value, schema, info) { -        const containsSchema = schema.contains; +    _validateArrayContains(value) { +        const containsSchema = this._schema.contains;          if (!this._isObject(containsSchema)) { return; } -        info.schemaPush('contains', containsSchema); -        for (let i = 0, ii = value.length; i < ii; ++i) { -            const propertyValue = value[i]; -            info.valuePush(i, propertyValue); -            try { -                this._validate(propertyValue, containsSchema, info); -                info.schemaPop(); -                return; -            } catch (e) { -                // NOP +        this._schemaPush('contains', containsSchema); +        try { +            for (let i = 0, ii = value.length; i < ii; ++i) { +                const propertyValue = value[i]; +                this._valuePush(i, propertyValue); +                try { +                    this._validate(propertyValue); +                    return; +                } catch (e) { +                    // NOP +                } finally { +                    this._valuePop(); +                }              } -            info.valuePop(); +            throw this._createError('contains schema didn\'t match'); +        } finally { +            this._schemaPop();          } -        throw new JsonSchemaValidationError('contains schema didn\'t match', value, schema, info);      } -    _validateObject(value, schema, info) { +    _validateObject(value) {          const properties = new Set(Object.getOwnPropertyNames(value)); -        const required = schema.required; +        const {required} = this._schema;          if (Array.isArray(required)) {              for (const property of required) {                  if (!properties.has(property)) { -                    throw new JsonSchemaValidationError(`Missing property ${property}`, value, schema, info); +                    throw this._createError(`Missing property ${property}`);                  }              }          } -        const minProperties = schema.minProperties; +        const {minProperties} = this._schema;          if (typeof minProperties === 'number' && properties.length < minProperties) { -            throw new JsonSchemaValidationError('Not enough object properties', value, schema, info); +            throw this._createError('Not enough object properties');          } -        const maxProperties = schema.maxProperties; +        const {maxProperties} = this._schema;          if (typeof maxProperties === 'number' && properties.length > maxProperties) { -            throw new JsonSchemaValidationError('Too many object properties', value, schema, info); +            throw this._createError('Too many object properties');          }          for (const property of properties) { -            const schemaPath = []; -            const propertySchema = this._getPropertySchema(schema, property, value, schemaPath); -            if (propertySchema === null) { -                throw new JsonSchemaValidationError(`No schema found for ${property}`, value, schema, info); +            const schemaPath = this._getObjectPropertySchemaPath(property); +            if (schemaPath === null) { +                throw this._createError(`No schema found for ${property}`);              }              const propertyValue = value[property]; -            for (const [p, s] of schemaPath) { info.schemaPush(p, s); } -            info.valuePush(property, propertyValue); -            this._validate(propertyValue, propertySchema, info); -            info.valuePop(); -            for (let i = 0; i < schemaPath.length; ++i) { info.schemaPop(); } -        } -    } - -    _isValueTypeAny(value, type, schemaTypes) { -        if (typeof schemaTypes === 'string') { -            return this._isValueType(value, type, schemaTypes); -        } else if (Array.isArray(schemaTypes)) { -            for (const schemaType of schemaTypes) { -                if (this._isValueType(value, type, schemaType)) { -                    return true; -                } -            } -            return false; -        } -        return true; -    } - -    _isValueType(value, type, schemaType) { -        return ( -            type === schemaType || -            (schemaType === 'integer' && Math.floor(value) === value) -        ); -    } - -    _getValueType(value) { -        const type = typeof value; -        if (type === 'object') { -            if (value === null) { return 'null'; } -            if (Array.isArray(value)) { return 'array'; } -        } -        return type; -    } - -    _valuesAreEqualAny(value1, valueList) { -        for (const value2 of valueList) { -            if (this._valuesAreEqual(value1, value2)) { -                return true; +            for (const {path, schema} of schemaPath) { this._schemaPush(path, schema); } +            this._valuePush(property, propertyValue); +            try { +                this._validate(propertyValue); +            } finally { +                this._valuePop(); +                for (let j = 0, jj = schemaPath.length; j < jj; ++j) { this._schemaPop(); }              }          } -        return false;      } -    _valuesAreEqual(value1, value2) { -        return value1 === value2; -    } +    // Creation      _getDefaultTypeValue(type) {          if (typeof type === 'string') { @@ -603,9 +633,8 @@ class JsonSchemaValidator {          return null;      } -    _getDefaultSchemaValue(schema) { -        const schemaType = schema.type; -        const schemaDefault = schema.default; +    _getDefaultSchemaValue() { +        const {type: schemaType, default: schemaDefault} = this._schema;          return (              typeof schemaDefault !== 'undefined' &&              this._isValueTypeAny(schemaDefault, this._getValueType(schemaDefault), schemaType) ? @@ -614,24 +643,35 @@ class JsonSchemaValidator {          );      } -    _getValidValueOrDefault(schema, value, info) { +    _getValidValueOrDefault(path, value, schemaPath) { +        this._valuePush(path, value); +        for (const {path: path2, schema} of schemaPath) { this._schemaPush(path2, schema); } +        try { +            return this._getValidValueOrDefaultInner(value); +        } finally { +            for (let i = 0, ii = schemaPath.length; i < ii; ++i) { this._schemaPop(); } +            this._valuePop(); +        } +    } + +    _getValidValueOrDefaultInner(value) {          let type = this._getValueType(value); -        if (typeof value === 'undefined' || !this._isValueTypeAny(value, type, schema.type)) { -            value = this._getDefaultSchemaValue(schema); +        if (typeof value === 'undefined' || !this._isValueTypeAny(value, type, this._schema.type)) { +            value = this._getDefaultSchemaValue();              type = this._getValueType(value);          }          switch (type) {              case 'object': -                value = this._populateObjectDefaults(value, schema, info); +                value = this._populateObjectDefaults(value);                  break;              case 'array': -                value = this._populateArrayDefaults(value, schema, info); +                value = this._populateArrayDefaults(value);                  break;              default: -                if (!this.isValid(value, schema)) { -                    const schemaDefault = this._getDefaultSchemaValue(schema); -                    if (this.isValid(schemaDefault, schema)) { +                if (!this._isValidCurrent(value)) { +                    const schemaDefault = this._getDefaultSchemaValue(); +                    if (this._isValidCurrent(schemaDefault)) {                          value = schemaDefault;                      }                  } @@ -641,126 +681,169 @@ class JsonSchemaValidator {          return value;      } -    _populateObjectDefaults(value, schema, info) { +    _populateObjectDefaults(value) {          const properties = new Set(Object.getOwnPropertyNames(value)); -        const required = schema.required; +        const {required} = this._schema;          if (Array.isArray(required)) {              for (const property of required) {                  properties.delete(property); - -                const propertySchema = this._getPropertySchema(schema, property, value, null); -                if (propertySchema === null) { continue; } -                info.valuePush(property, value); -                info.schemaPush(property, propertySchema); -                const hasValue = Object.prototype.hasOwnProperty.call(value, property); -                value[property] = this._getValidValueOrDefault(propertySchema, hasValue ? value[property] : void 0, info); -                info.schemaPop(); -                info.valuePop(); +                const schemaPath = this._getObjectPropertySchemaPath(property); +                if (schemaPath === null) { continue; } +                const propertyValue = Object.prototype.hasOwnProperty.call(value, property) ? value[property] : void 0; +                value[property] = this._getValidValueOrDefault(property, propertyValue, schemaPath);              }          }          for (const property of properties) { -            const propertySchema = this._getPropertySchema(schema, property, value, null); -            if (propertySchema === null) { +            const schemaPath = this._getObjectPropertySchemaPath(property); +            if (schemaPath === null) {                  Reflect.deleteProperty(value, property);              } else { -                info.valuePush(property, value); -                info.schemaPush(property, propertySchema); -                value[property] = this._getValidValueOrDefault(propertySchema, value[property], info); -                info.schemaPop(); -                info.valuePop(); +                value[property] = this._getValidValueOrDefault(property, value[property], schemaPath);              }          }          return value;      } -    _populateArrayDefaults(value, schema, info) { +    _populateArrayDefaults(value) {          for (let i = 0, ii = value.length; i < ii; ++i) { -            const propertySchema = this._getPropertySchema(schema, i, value, null); -            if (propertySchema === null) { continue; } -            info.valuePush(i, value); -            info.schemaPush(i, propertySchema); -            value[i] = this._getValidValueOrDefault(propertySchema, value[i], info); -            info.schemaPop(); -            info.valuePop(); +            const schemaPath = this._getArrayItemSchemaPath(i); +            if (schemaPath === null) { continue; } +            const propertyValue = value[i]; +            value[i] = this._getValidValueOrDefault(i, propertyValue, schemaPath);          } -        const minItems = schema.minItems; +        const {minItems, maxItems} = this._schema;          if (typeof minItems === 'number' && value.length < minItems) {              for (let i = value.length; i < minItems; ++i) { -                const propertySchema = this._getPropertySchema(schema, i, value, null); -                if (propertySchema === null) { break; } -                info.valuePush(i, value); -                info.schemaPush(i, propertySchema); -                const item = this._getValidValueOrDefault(propertySchema, void 0, info); -                info.schemaPop(); -                info.valuePop(); +                const schemaPath = this._getArrayItemSchemaPath(i); +                if (schemaPath === null) { break; } +                const item = this._getValidValueOrDefault(i, void 0, schemaPath);                  value.push(item);              }          } -        const maxItems = schema.maxItems;          if (typeof maxItems === 'number' && value.length > maxItems) {              value.splice(maxItems, value.length - maxItems);          }          return value;      } +} -    _isObject(value) { -        return typeof value === 'object' && value !== null && !Array.isArray(value); +class JsonSchemaProxyHandler { +    constructor(schema) { +        this._schema = schema; +        this._numberPattern = /^(?:0|[1-9]\d*)$/;      } -    _getRegex(pattern, flags) { -        const key = `${flags}:${pattern}`; -        let regex = this._regexCache.get(key); -        if (typeof regex === 'undefined') { -            regex = new RegExp(pattern, flags); -            this._regexCache.set(key, regex); +    getPrototypeOf(target) { +        return Object.getPrototypeOf(target); +    } + +    setPrototypeOf() { +        throw new Error('setPrototypeOf not supported'); +    } + +    isExtensible(target) { +        return Object.isExtensible(target); +    } + +    preventExtensions(target) { +        Object.preventExtensions(target); +        return true; +    } + +    getOwnPropertyDescriptor(target, property) { +        return Object.getOwnPropertyDescriptor(target, property); +    } + +    defineProperty() { +        throw new Error('defineProperty not supported'); +    } + +    has(target, property) { +        return property in target; +    } + +    get(target, property) { +        if (typeof property === 'symbol') { return target[property]; } + +        let propertySchema; +        if (Array.isArray(target)) { +            property = this._getArrayIndex(property); +            if (property === null) { +                // Note: this does not currently wrap mutating functions like push, pop, shift, unshift, splice +                return target[property]; +            } +            propertySchema = this._schema.getArrayItemSchema(property); +        } else { +            propertySchema = this._schema.getObjectPropertySchema(property);          } -        return regex; + +        if (propertySchema === null) { return void 0; } + +        const value = target[property]; +        return value !== null && typeof value === 'object' ? propertySchema.createProxy(value) : value;      } -} -Object.defineProperty(JsonSchemaValidator, 'unconstrainedSchema', { -    value: Object.freeze({}), -    configurable: false, -    enumerable: true, -    writable: false -}); +    set(target, property, value) { +        if (typeof property === 'symbol') { throw new Error(`Cannot assign symbol property ${property}`); } + +        let propertySchema; +        if (Array.isArray(target)) { +            property = this._getArrayIndex(property); +            if (property === null) { throw new Error(`Property ${property} cannot be assigned to array`); } +            if (property > target.length) { throw new Error('Array index out of range'); } +            propertySchema = this._schema.getArrayItemSchema(property); +        } else { +            propertySchema = this._schema.getObjectPropertySchema(property); +        } + +        if (propertySchema === null) { throw new Error(`Property ${property} not supported`); } + +        value = clone(value); +        propertySchema.validate(value); -class JsonSchemaTraversalInfo { -    constructor(value, schema) { -        this.valuePath = []; -        this.schemaPath = []; -        this.valuePush(null, value); -        this.schemaPush(null, schema); +        target[property] = value; +        return true;      } -    valuePush(path, value) { -        this.valuePath.push([path, value]); +    deleteProperty(target, property) { +        const required = ( +            (typeof target === 'object' && target !== null) ? +            (Array.isArray(target) || this._schema.isObjectPropertyRequired(property)) : +            true +        ); +        if (required) { +            throw new Error(`${property} cannot be deleted`); +        } +        return Reflect.deleteProperty(target, property);      } -    valuePop() { -        this.valuePath.pop(); +    ownKeys(target) { +        return Reflect.ownKeys(target);      } -    schemaPush(path, schema) { -        this.schemaPath.push([path, schema]); +    apply() { +        throw new Error('apply not supported');      } -    schemaPop() { -        this.schemaPath.pop(); +    construct() { +        throw new Error('construct not supported');      } -} -class JsonSchemaValidationError extends Error { -    constructor(message, value, schema, info) { -        super(message); -        this.value = value; -        this.schema = schema; -        this.info = info; +    // Private + +    _getArrayIndex(property) { +        if (typeof property === 'string' && this._numberPattern.test(property)) { +            return Number.parseInt(property, 10); +        } else if (typeof property === 'number' && Math.floor(property) === property && property >= 0) { +            return property; +        } else { +            return null; +        }      }  } diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 00ad890d..740afa76 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -16,19 +16,19 @@   */  /* global - * JsonSchemaValidator + * JsonSchema   * TemplatePatcher   */  class OptionsUtil {      constructor() { -        this._schemaValidator = new JsonSchemaValidator();          this._templatePatcher = null;          this._optionsSchema = null;      }      async prepare() { -        this._optionsSchema = await this._fetchAsset('/data/schemas/options-schema.json', true); +        const schema = await this._fetchAsset('/data/schemas/options-schema.json', true); +        this._optionsSchema = new JsonSchema(schema);      }      async update(options) { @@ -87,7 +87,7 @@ class OptionsUtil {          options = await this._applyUpdates(options, this._getVersionUpdates());          // Validation -        options = this._schemaValidator.getValidValueOrDefault(this._optionsSchema, options); +        options = this._optionsSchema.getValidValueOrDefault(options);          // Result          return options; @@ -135,17 +135,17 @@ class OptionsUtil {      getDefault() {          const optionsVersion = this._getVersionUpdates().length; -        const options = this._schemaValidator.getValidValueOrDefault(this._optionsSchema); +        const options = this._optionsSchema.getValidValueOrDefault();          options.version = optionsVersion;          return options;      }      createValidatingProxy(options) { -        return this._schemaValidator.createProxy(options, this._optionsSchema); +        return this._optionsSchema.createProxy(options);      }      validate(options) { -        return this._schemaValidator.validate(options, this._optionsSchema); +        return this._optionsSchema.validate(options);      }      // Legacy profile updating diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index e893ffb6..9365683b 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -17,14 +17,13 @@  /* global   * JSZip - * JsonSchemaValidator + * JsonSchema   * MediaUtil   */  class DictionaryImporter {      constructor() {          this._schemas = new Map(); -        this._jsonSchemaValidator = new JsonSchemaValidator();      }      async importDictionary(dictionaryDatabase, archiveSource, details, onProgress) { @@ -235,22 +234,27 @@ class DictionaryImporter {              return schemaPromise;          } -        schemaPromise = this._fetchJsonAsset(fileName); +        schemaPromise = this._createSchema(fileName);          this._schemas.set(fileName, schemaPromise);          return schemaPromise;      } +    async _createSchema(fileName) { +        const schema = await this._fetchJsonAsset(fileName); +        return new JsonSchema(schema); +    } +      _validateJsonSchema(value, schema, fileName) {          try { -            this._jsonSchemaValidator.validate(value, schema); +            schema.validate(value);          } catch (e) {              throw this._formatSchemaError(e, fileName);          }      }      _formatSchemaError(e, fileName) { -        const valuePathString = this._getSchemaErrorPathString(e.info.valuePath, 'dictionary'); -        const schemaPathString = this._getSchemaErrorPathString(e.info.schemaPath, 'schema'); +        const valuePathString = this._getSchemaErrorPathString(e.valuePath, 'dictionary'); +        const schemaPathString = this._getSchemaErrorPathString(e.schemaPath, 'schema');          const e2 = new Error(`Dictionary has invalid data in '${fileName}' for value '${valuePathString}', validated against '${schemaPathString}': ${e.message}`);          e2.data = e; @@ -260,16 +264,16 @@ class DictionaryImporter {      _getSchemaErrorPathString(infoList, base='') {          let result = base; -        for (const [part] of infoList) { -            switch (typeof part) { +        for (const {path} of infoList) { +            switch (typeof path) {                  case 'string':                      if (result.length > 0) {                          result += '.';                      } -                    result += part; +                    result += path;                      break;                  case 'number': -                    result += `[${part}]`; +                    result += `[${path}]`;                      break;              }          } diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 47cdaf13..577d1c1b 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -16,7 +16,7 @@   */  /* global - * JsonSchemaValidator + * JsonSchema   * NativeSimpleDOMParser   * SimpleDOMParser   */ @@ -26,7 +26,7 @@ class AudioDownloader {          this._japaneseUtil = japaneseUtil;          this._requestBuilder = requestBuilder;          this._customAudioListSchema = null; -        this._schemaValidator = null; +        this._customAudioListSchema = null;          this._getInfoHandlers = new Map([              ['jpod101', this._getInfoJpod101.bind(this)],              ['jpod101-alternate', this._getInfoJpod101Alternate.bind(this)], @@ -222,11 +222,11 @@ class AudioDownloader {          const responseJson = await response.json(); -        const schema = await this._getCustomAudioListSchema(); -        if (this._schemaValidator === null) { -            this._schemaValidator = new JsonSchemaValidator(); +        if (this._customAudioListSchema === null) { +            const schema = await this._getCustomAudioListSchema(); +            this._customAudioListSchema = new JsonSchema(schema);          } -        this._schemaValidator.validate(responseJson, schema); +        this._customAudioListSchema.validate(responseJson);          const results = [];          for (const {url: url2, name} of responseJson.audioSources) { diff --git a/test/test-json-schema.js b/test/test-json-schema.js index d082a53f..ba0131bf 100644 --- a/test/test-json-schema.js +++ b/test/test-json-schema.js @@ -25,19 +25,19 @@ vm.execute([      'js/general/cache-map.js',      'js/data/json-schema.js'  ]); -const JsonSchemaValidator = vm.get('JsonSchemaValidator'); +const JsonSchema = vm.get('JsonSchema');  function schemaValidate(schema, value) { -    return new JsonSchemaValidator().isValid(value, schema); +    return new JsonSchema(schema).isValid(value);  }  function getValidValueOrDefault(schema, value) { -    return new JsonSchemaValidator().getValidValueOrDefault(schema, value); +    return new JsonSchema(schema).getValidValueOrDefault(value);  }  function createProxy(schema, value) { -    return new JsonSchemaValidator().createProxy(value, schema); +    return new JsonSchema(schema).createProxy(value);  }  function clone(value) { diff --git a/test/test-profile-conditions-util.js b/test/test-profile-conditions-util.js index b77578de..2d076a7f 100644 --- a/test/test-profile-conditions-util.js +++ b/test/test-profile-conditions-util.js @@ -27,12 +27,7 @@ vm.execute([      'js/data/json-schema.js',      'js/background/profile-conditions-util.js'  ]); -const [JsonSchemaValidator, ProfileConditionsUtil] = vm.get(['JsonSchemaValidator', 'ProfileConditionsUtil']); - - -function schemaValidate(value, schema) { -    return new JsonSchemaValidator().isValid(value, schema); -} +const [ProfileConditionsUtil] = vm.get(['ProfileConditionsUtil']);  function testNormalizeContext() { @@ -1081,12 +1076,12 @@ function testSchemas() {          const profileConditionsUtil = new ProfileConditionsUtil();          const schema = profileConditionsUtil.createSchema(conditionGroups);          if (typeof expectedSchema !== 'undefined') { -            vm.assert.deepStrictEqual(schema, expectedSchema); +            vm.assert.deepStrictEqual(schema.schema, expectedSchema);          }          if (Array.isArray(inputs)) {              for (const {expected, context} of inputs) {                  const normalizedContext = profileConditionsUtil.normalizeContext(context); -                const actual = schemaValidate(normalizedContext, schema); +                const actual = schema.isValid(normalizedContext);                  assert.strictEqual(actual, expected);              }          } |