JSON schema refactor (#731)
* Remove JsonSchema.clone * Move createProxy function * Group public properties first * Create private version of getPropertySchema * Mark functions as private * Use non-static getValidValueOrDefault * Mark private * Make public validate function not take an info parameter * Remove JsonSchema * Add isValid function * Use isValid for some tests * Fix incorrect type
This commit is contained in:
parent
d8649f40d5
commit
d582c7a0f8
@ -24,7 +24,7 @@
|
|||||||
* DictionaryDatabase
|
* DictionaryDatabase
|
||||||
* DictionaryImporter
|
* DictionaryImporter
|
||||||
* Environment
|
* Environment
|
||||||
* JsonSchema
|
* JsonSchemaValidator
|
||||||
* Mecab
|
* Mecab
|
||||||
* ObjectPropertyAccessor
|
* ObjectPropertyAccessor
|
||||||
* OptionsUtil
|
* OptionsUtil
|
||||||
@ -48,6 +48,7 @@ class Backend {
|
|||||||
this._clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)});
|
this._clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)});
|
||||||
this._options = null;
|
this._options = null;
|
||||||
this._optionsSchema = null;
|
this._optionsSchema = null;
|
||||||
|
this._optionsSchemaValidator = new JsonSchemaValidator();
|
||||||
this._defaultAnkiFieldTemplates = null;
|
this._defaultAnkiFieldTemplates = null;
|
||||||
this._requestBuilder = new RequestBuilder();
|
this._requestBuilder = new RequestBuilder();
|
||||||
this._audioUriBuilder = new AudioUriBuilder({
|
this._audioUriBuilder = new AudioUriBuilder({
|
||||||
@ -204,7 +205,7 @@ class Backend {
|
|||||||
this._optionsSchema = await this._fetchAsset('/bg/data/options-schema.json', true);
|
this._optionsSchema = await this._fetchAsset('/bg/data/options-schema.json', true);
|
||||||
this._defaultAnkiFieldTemplates = (await this._fetchAsset('/bg/data/default-anki-field-templates.handlebars')).trim();
|
this._defaultAnkiFieldTemplates = (await this._fetchAsset('/bg/data/default-anki-field-templates.handlebars')).trim();
|
||||||
this._options = await OptionsUtil.load();
|
this._options = await OptionsUtil.load();
|
||||||
this._options = JsonSchema.getValidValueOrDefault(this._optionsSchema, this._options);
|
this._options = this._optionsSchemaValidator.getValidValueOrDefault(this._optionsSchema, this._options);
|
||||||
|
|
||||||
this._applyOptions('background');
|
this._applyOptions('background');
|
||||||
|
|
||||||
@ -235,7 +236,7 @@ class Backend {
|
|||||||
|
|
||||||
getFullOptions(useSchema=false) {
|
getFullOptions(useSchema=false) {
|
||||||
const options = this._options;
|
const options = this._options;
|
||||||
return useSchema ? JsonSchema.createProxy(options, this._optionsSchema) : options;
|
return useSchema ? this._optionsSchemaValidator.createProxy(options, this._optionsSchema) : options;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOptions(optionsContext, useSchema=false) {
|
getOptions(optionsContext, useSchema=false) {
|
||||||
@ -792,7 +793,7 @@ class Backend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async _onApiSetAllSettings({value, source}) {
|
async _onApiSetAllSettings({value, source}) {
|
||||||
this._options = JsonSchema.getValidValueOrDefault(this._optionsSchema, value);
|
this._options = this._optionsSchemaValidator.getValidValueOrDefault(this._optionsSchema, value);
|
||||||
await this._onApiOptionsSave({source});
|
await this._onApiOptionsSave({source});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,13 +17,14 @@
|
|||||||
|
|
||||||
/* global
|
/* global
|
||||||
* JSZip
|
* JSZip
|
||||||
* JsonSchema
|
* JsonSchemaValidator
|
||||||
* mediaUtility
|
* mediaUtility
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class DictionaryImporter {
|
class DictionaryImporter {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._schemas = new Map();
|
this._schemas = new Map();
|
||||||
|
this._jsonSchemaValidator = new JsonSchemaValidator();
|
||||||
}
|
}
|
||||||
|
|
||||||
async importDictionary(dictionaryDatabase, archiveSource, details, onProgress) {
|
async importDictionary(dictionaryDatabase, archiveSource, details, onProgress) {
|
||||||
@ -241,7 +242,7 @@ class DictionaryImporter {
|
|||||||
|
|
||||||
_validateJsonSchema(value, schema, fileName) {
|
_validateJsonSchema(value, schema, fileName) {
|
||||||
try {
|
try {
|
||||||
JsonSchema.validate(value, schema);
|
this._jsonSchemaValidator.validate(value, schema);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw this._formatSchemaError(e, fileName);
|
throw this._formatSchemaError(e, fileName);
|
||||||
}
|
}
|
||||||
|
@ -73,7 +73,7 @@ class JsonSchemaProxyHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const value = target[property];
|
const value = target[property];
|
||||||
return value !== null && typeof value === 'object' ? JsonSchema.createProxy(value, propertySchema) : value;
|
return value !== null && typeof value === 'object' ? this._jsonSchemaValidator.createProxy(value, propertySchema) : value;
|
||||||
}
|
}
|
||||||
|
|
||||||
set(target, property, value) {
|
set(target, property, value) {
|
||||||
@ -94,9 +94,9 @@ class JsonSchemaProxyHandler {
|
|||||||
throw new Error(`Property ${property} not supported`);
|
throw new Error(`Property ${property} not supported`);
|
||||||
}
|
}
|
||||||
|
|
||||||
value = JsonSchema.clone(value);
|
value = clone(value);
|
||||||
|
|
||||||
this._jsonSchemaValidator.validate(value, propertySchema, new JsonSchemaTraversalInfo(value, propertySchema));
|
this._jsonSchemaValidator.validate(value, propertySchema);
|
||||||
|
|
||||||
target[property] = value;
|
target[property] = value;
|
||||||
return true;
|
return true;
|
||||||
@ -128,15 +128,70 @@ class JsonSchemaValidator {
|
|||||||
this._regexCache = new CacheMap(100, (pattern, flags) => new RegExp(pattern, flags));
|
this._regexCache = new CacheMap(100, (pattern, flags) => new RegExp(pattern, flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
getPropertySchema(schema, property, value, path=null) {
|
createProxy(target, schema) {
|
||||||
const type = this.getSchemaOrValueType(schema, value);
|
return new Proxy(target, new JsonSchemaProxyHandler(schema, this));
|
||||||
|
}
|
||||||
|
|
||||||
|
isValid(value, schema) {
|
||||||
|
try {
|
||||||
|
this.validate(value, schema);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(value, schema) {
|
||||||
|
const info = new JsonSchemaTraversalInfo(value, schema);
|
||||||
|
this._validate(value, schema, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
getValidValueOrDefault(schema, value) {
|
||||||
|
let type = this._getValueType(value);
|
||||||
|
const schemaType = schema.type;
|
||||||
|
if (!this._isValueTypeAny(value, type, schemaType)) {
|
||||||
|
let assignDefault = true;
|
||||||
|
|
||||||
|
const schemaDefault = schema.default;
|
||||||
|
if (typeof schemaDefault !== 'undefined') {
|
||||||
|
value = clone(schemaDefault);
|
||||||
|
type = this._getValueType(value);
|
||||||
|
assignDefault = !this._isValueTypeAny(value, type, schemaType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assignDefault) {
|
||||||
|
value = this._getDefaultTypeValue(schemaType);
|
||||||
|
type = this._getValueType(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'object':
|
||||||
|
value = this._populateObjectDefaults(value, schema);
|
||||||
|
break;
|
||||||
|
case 'array':
|
||||||
|
value = this._populateArrayDefaults(value, schema);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPropertySchema(schema, property, value) {
|
||||||
|
return this._getPropertySchema(schema, property, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private
|
||||||
|
|
||||||
|
_getPropertySchema(schema, property, value, path) {
|
||||||
|
const type = this._getSchemaOrValueType(schema, value);
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'object':
|
case 'object':
|
||||||
{
|
{
|
||||||
const properties = schema.properties;
|
const properties = schema.properties;
|
||||||
if (this.isObject(properties)) {
|
if (this._isObject(properties)) {
|
||||||
const propertySchema = properties[property];
|
const propertySchema = properties[property];
|
||||||
if (this.isObject(propertySchema)) {
|
if (this._isObject(propertySchema)) {
|
||||||
if (path !== null) { path.push(['properties', properties], [property, propertySchema]); }
|
if (path !== null) { path.push(['properties', properties], [property, propertySchema]); }
|
||||||
return propertySchema;
|
return propertySchema;
|
||||||
}
|
}
|
||||||
@ -145,7 +200,7 @@ class JsonSchemaValidator {
|
|||||||
const additionalProperties = schema.additionalProperties;
|
const additionalProperties = schema.additionalProperties;
|
||||||
if (additionalProperties === false) {
|
if (additionalProperties === false) {
|
||||||
return null;
|
return null;
|
||||||
} else if (this.isObject(additionalProperties)) {
|
} else if (this._isObject(additionalProperties)) {
|
||||||
if (path !== null) { path.push(['additionalProperties', additionalProperties]); }
|
if (path !== null) { path.push(['additionalProperties', additionalProperties]); }
|
||||||
return additionalProperties;
|
return additionalProperties;
|
||||||
} else {
|
} else {
|
||||||
@ -157,13 +212,13 @@ class JsonSchemaValidator {
|
|||||||
case 'array':
|
case 'array':
|
||||||
{
|
{
|
||||||
const items = schema.items;
|
const items = schema.items;
|
||||||
if (this.isObject(items)) {
|
if (this._isObject(items)) {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
if (Array.isArray(items)) {
|
if (Array.isArray(items)) {
|
||||||
if (property >= 0 && property < items.length) {
|
if (property >= 0 && property < items.length) {
|
||||||
const propertySchema = items[property];
|
const propertySchema = items[property];
|
||||||
if (this.isObject(propertySchema)) {
|
if (this._isObject(propertySchema)) {
|
||||||
if (path !== null) { path.push(['items', items], [property, propertySchema]); }
|
if (path !== null) { path.push(['items', items], [property, propertySchema]); }
|
||||||
return propertySchema;
|
return propertySchema;
|
||||||
}
|
}
|
||||||
@ -173,7 +228,7 @@ class JsonSchemaValidator {
|
|||||||
const additionalItems = schema.additionalItems;
|
const additionalItems = schema.additionalItems;
|
||||||
if (additionalItems === false) {
|
if (additionalItems === false) {
|
||||||
return null;
|
return null;
|
||||||
} else if (this.isObject(additionalItems)) {
|
} else if (this._isObject(additionalItems)) {
|
||||||
if (path !== null) { path.push(['additionalItems', additionalItems]); }
|
if (path !== null) { path.push(['additionalItems', additionalItems]); }
|
||||||
return additionalItems;
|
return additionalItems;
|
||||||
} else {
|
} else {
|
||||||
@ -187,12 +242,12 @@ class JsonSchemaValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getSchemaOrValueType(schema, value) {
|
_getSchemaOrValueType(schema, value) {
|
||||||
const type = schema.type;
|
const type = schema.type;
|
||||||
|
|
||||||
if (Array.isArray(type)) {
|
if (Array.isArray(type)) {
|
||||||
if (typeof value !== 'undefined') {
|
if (typeof value !== 'undefined') {
|
||||||
const valueType = this.getValueType(value);
|
const valueType = this._getValueType(value);
|
||||||
if (type.indexOf(valueType) >= 0) {
|
if (type.indexOf(valueType) >= 0) {
|
||||||
return valueType;
|
return valueType;
|
||||||
}
|
}
|
||||||
@ -202,7 +257,7 @@ class JsonSchemaValidator {
|
|||||||
|
|
||||||
if (typeof type === 'undefined') {
|
if (typeof type === 'undefined') {
|
||||||
if (typeof value !== 'undefined') {
|
if (typeof value !== 'undefined') {
|
||||||
return this.getValueType(value);
|
return this._getValueType(value);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -210,37 +265,37 @@ class JsonSchemaValidator {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(value, schema, info) {
|
_validate(value, schema, info) {
|
||||||
this.validateSingleSchema(value, schema, info);
|
this._validateSingleSchema(value, schema, info);
|
||||||
this.validateConditional(value, schema, info);
|
this._validateConditional(value, schema, info);
|
||||||
this.validateAllOf(value, schema, info);
|
this._validateAllOf(value, schema, info);
|
||||||
this.validateAnyOf(value, schema, info);
|
this._validateAnyOf(value, schema, info);
|
||||||
this.validateOneOf(value, schema, info);
|
this._validateOneOf(value, schema, info);
|
||||||
this.validateNoneOf(value, schema, info);
|
this._validateNoneOf(value, schema, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
validateConditional(value, schema, info) {
|
_validateConditional(value, schema, info) {
|
||||||
const ifSchema = schema.if;
|
const ifSchema = schema.if;
|
||||||
if (!this.isObject(ifSchema)) { return; }
|
if (!this._isObject(ifSchema)) { return; }
|
||||||
|
|
||||||
let okay = true;
|
let okay = true;
|
||||||
info.schemaPush('if', ifSchema);
|
info.schemaPush('if', ifSchema);
|
||||||
try {
|
try {
|
||||||
this.validate(value, ifSchema, info);
|
this._validate(value, ifSchema, info);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
okay = false;
|
okay = false;
|
||||||
}
|
}
|
||||||
info.schemaPop();
|
info.schemaPop();
|
||||||
|
|
||||||
const nextSchema = okay ? schema.then : schema.else;
|
const nextSchema = okay ? schema.then : schema.else;
|
||||||
if (this.isObject(nextSchema)) {
|
if (this._isObject(nextSchema)) {
|
||||||
info.schemaPush(okay ? 'then' : 'else', nextSchema);
|
info.schemaPush(okay ? 'then' : 'else', nextSchema);
|
||||||
this.validate(value, nextSchema, info);
|
this._validate(value, nextSchema, info);
|
||||||
info.schemaPop();
|
info.schemaPop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAllOf(value, schema, info) {
|
_validateAllOf(value, schema, info) {
|
||||||
const subSchemas = schema.allOf;
|
const subSchemas = schema.allOf;
|
||||||
if (!Array.isArray(subSchemas)) { return; }
|
if (!Array.isArray(subSchemas)) { return; }
|
||||||
|
|
||||||
@ -248,13 +303,13 @@ class JsonSchemaValidator {
|
|||||||
for (let i = 0; i < subSchemas.length; ++i) {
|
for (let i = 0; i < subSchemas.length; ++i) {
|
||||||
const subSchema = subSchemas[i];
|
const subSchema = subSchemas[i];
|
||||||
info.schemaPush(i, subSchema);
|
info.schemaPush(i, subSchema);
|
||||||
this.validate(value, subSchema, info);
|
this._validate(value, subSchema, info);
|
||||||
info.schemaPop();
|
info.schemaPop();
|
||||||
}
|
}
|
||||||
info.schemaPop();
|
info.schemaPop();
|
||||||
}
|
}
|
||||||
|
|
||||||
validateAnyOf(value, schema, info) {
|
_validateAnyOf(value, schema, info) {
|
||||||
const subSchemas = schema.anyOf;
|
const subSchemas = schema.anyOf;
|
||||||
if (!Array.isArray(subSchemas)) { return; }
|
if (!Array.isArray(subSchemas)) { return; }
|
||||||
|
|
||||||
@ -263,7 +318,7 @@ class JsonSchemaValidator {
|
|||||||
const subSchema = subSchemas[i];
|
const subSchema = subSchemas[i];
|
||||||
info.schemaPush(i, subSchema);
|
info.schemaPush(i, subSchema);
|
||||||
try {
|
try {
|
||||||
this.validate(value, subSchema, info);
|
this._validate(value, subSchema, info);
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
@ -275,7 +330,7 @@ class JsonSchemaValidator {
|
|||||||
// info.schemaPop(); // Unreachable
|
// info.schemaPop(); // Unreachable
|
||||||
}
|
}
|
||||||
|
|
||||||
validateOneOf(value, schema, info) {
|
_validateOneOf(value, schema, info) {
|
||||||
const subSchemas = schema.oneOf;
|
const subSchemas = schema.oneOf;
|
||||||
if (!Array.isArray(subSchemas)) { return; }
|
if (!Array.isArray(subSchemas)) { return; }
|
||||||
|
|
||||||
@ -285,7 +340,7 @@ class JsonSchemaValidator {
|
|||||||
const subSchema = subSchemas[i];
|
const subSchema = subSchemas[i];
|
||||||
info.schemaPush(i, subSchema);
|
info.schemaPush(i, subSchema);
|
||||||
try {
|
try {
|
||||||
this.validate(value, subSchema, info);
|
this._validate(value, subSchema, info);
|
||||||
++count;
|
++count;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
@ -300,7 +355,7 @@ class JsonSchemaValidator {
|
|||||||
info.schemaPop();
|
info.schemaPop();
|
||||||
}
|
}
|
||||||
|
|
||||||
validateNoneOf(value, schema, info) {
|
_validateNoneOf(value, schema, info) {
|
||||||
const subSchemas = schema.not;
|
const subSchemas = schema.not;
|
||||||
if (!Array.isArray(subSchemas)) { return; }
|
if (!Array.isArray(subSchemas)) { return; }
|
||||||
|
|
||||||
@ -309,7 +364,7 @@ class JsonSchemaValidator {
|
|||||||
const subSchema = subSchemas[i];
|
const subSchema = subSchemas[i];
|
||||||
info.schemaPush(i, subSchema);
|
info.schemaPush(i, subSchema);
|
||||||
try {
|
try {
|
||||||
this.validate(value, subSchema, info);
|
this._validate(value, subSchema, info);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
info.schemaPop();
|
info.schemaPop();
|
||||||
continue;
|
continue;
|
||||||
@ -319,40 +374,40 @@ class JsonSchemaValidator {
|
|||||||
info.schemaPop();
|
info.schemaPop();
|
||||||
}
|
}
|
||||||
|
|
||||||
validateSingleSchema(value, schema, info) {
|
_validateSingleSchema(value, schema, info) {
|
||||||
const type = this.getValueType(value);
|
const type = this._getValueType(value);
|
||||||
const schemaType = schema.type;
|
const schemaType = schema.type;
|
||||||
if (!this.isValueTypeAny(value, type, schemaType)) {
|
if (!this._isValueTypeAny(value, type, schemaType)) {
|
||||||
throw new JsonSchemaValidationError(`Value type ${type} does not match schema type ${schemaType}`, value, schema, info);
|
throw new JsonSchemaValidationError(`Value type ${type} does not match schema type ${schemaType}`, value, schema, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaConst = schema.const;
|
const schemaConst = schema.const;
|
||||||
if (typeof schemaConst !== 'undefined' && !this.valuesAreEqual(value, schemaConst)) {
|
if (typeof schemaConst !== 'undefined' && !this._valuesAreEqual(value, schemaConst)) {
|
||||||
throw new JsonSchemaValidationError('Invalid constant value', value, schema, info);
|
throw new JsonSchemaValidationError('Invalid constant value', value, schema, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
const schemaEnum = schema.enum;
|
const schemaEnum = schema.enum;
|
||||||
if (Array.isArray(schemaEnum) && !this.valuesAreEqualAny(value, schemaEnum)) {
|
if (Array.isArray(schemaEnum) && !this._valuesAreEqualAny(value, schemaEnum)) {
|
||||||
throw new JsonSchemaValidationError('Invalid enum value', value, schema, info);
|
throw new JsonSchemaValidationError('Invalid enum value', value, schema, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'number':
|
case 'number':
|
||||||
this.validateNumber(value, schema, info);
|
this._validateNumber(value, schema, info);
|
||||||
break;
|
break;
|
||||||
case 'string':
|
case 'string':
|
||||||
this.validateString(value, schema, info);
|
this._validateString(value, schema, info);
|
||||||
break;
|
break;
|
||||||
case 'array':
|
case 'array':
|
||||||
this.validateArray(value, schema, info);
|
this._validateArray(value, schema, info);
|
||||||
break;
|
break;
|
||||||
case 'object':
|
case 'object':
|
||||||
this.validateObject(value, schema, info);
|
this._validateObject(value, schema, info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateNumber(value, schema, info) {
|
_validateNumber(value, schema, info) {
|
||||||
const multipleOf = schema.multipleOf;
|
const multipleOf = schema.multipleOf;
|
||||||
if (typeof multipleOf === 'number' && Math.floor(value / multipleOf) * multipleOf !== value) {
|
if (typeof multipleOf === 'number' && Math.floor(value / multipleOf) * multipleOf !== value) {
|
||||||
throw new JsonSchemaValidationError(`Number is not a multiple of ${multipleOf}`, value, schema, info);
|
throw new JsonSchemaValidationError(`Number is not a multiple of ${multipleOf}`, value, schema, info);
|
||||||
@ -379,7 +434,7 @@ class JsonSchemaValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateString(value, schema, info) {
|
_validateString(value, schema, info) {
|
||||||
const minLength = schema.minLength;
|
const minLength = schema.minLength;
|
||||||
if (typeof minLength === 'number' && value.length < minLength) {
|
if (typeof minLength === 'number' && value.length < minLength) {
|
||||||
throw new JsonSchemaValidationError('String length too short', value, schema, info);
|
throw new JsonSchemaValidationError('String length too short', value, schema, info);
|
||||||
@ -408,7 +463,7 @@ class JsonSchemaValidator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateArray(value, schema, info) {
|
_validateArray(value, schema, info) {
|
||||||
const minItems = schema.minItems;
|
const minItems = schema.minItems;
|
||||||
if (typeof minItems === 'number' && value.length < minItems) {
|
if (typeof minItems === 'number' && value.length < minItems) {
|
||||||
throw new JsonSchemaValidationError('Array length too short', value, schema, info);
|
throw new JsonSchemaValidationError('Array length too short', value, schema, info);
|
||||||
@ -419,11 +474,11 @@ class JsonSchemaValidator {
|
|||||||
throw new JsonSchemaValidationError('Array length too long', value, schema, info);
|
throw new JsonSchemaValidationError('Array length too long', value, schema, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.validateArrayContains(value, schema, info);
|
this._validateArrayContains(value, schema, info);
|
||||||
|
|
||||||
for (let i = 0, ii = value.length; i < ii; ++i) {
|
for (let i = 0, ii = value.length; i < ii; ++i) {
|
||||||
const schemaPath = [];
|
const schemaPath = [];
|
||||||
const propertySchema = this.getPropertySchema(schema, i, value, schemaPath);
|
const propertySchema = this._getPropertySchema(schema, i, value, schemaPath);
|
||||||
if (propertySchema === null) {
|
if (propertySchema === null) {
|
||||||
throw new JsonSchemaValidationError(`No schema found for array[${i}]`, value, schema, info);
|
throw new JsonSchemaValidationError(`No schema found for array[${i}]`, value, schema, info);
|
||||||
}
|
}
|
||||||
@ -432,22 +487,22 @@ class JsonSchemaValidator {
|
|||||||
|
|
||||||
for (const [p, s] of schemaPath) { info.schemaPush(p, s); }
|
for (const [p, s] of schemaPath) { info.schemaPush(p, s); }
|
||||||
info.valuePush(i, propertyValue);
|
info.valuePush(i, propertyValue);
|
||||||
this.validate(propertyValue, propertySchema, info);
|
this._validate(propertyValue, propertySchema, info);
|
||||||
info.valuePop();
|
info.valuePop();
|
||||||
for (let j = 0, jj = schemaPath.length; j < jj; ++j) { info.schemaPop(); }
|
for (let j = 0, jj = schemaPath.length; j < jj; ++j) { info.schemaPop(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validateArrayContains(value, schema, info) {
|
_validateArrayContains(value, schema, info) {
|
||||||
const containsSchema = schema.contains;
|
const containsSchema = schema.contains;
|
||||||
if (!this.isObject(containsSchema)) { return; }
|
if (!this._isObject(containsSchema)) { return; }
|
||||||
|
|
||||||
info.schemaPush('contains', containsSchema);
|
info.schemaPush('contains', containsSchema);
|
||||||
for (let i = 0, ii = value.length; i < ii; ++i) {
|
for (let i = 0, ii = value.length; i < ii; ++i) {
|
||||||
const propertyValue = value[i];
|
const propertyValue = value[i];
|
||||||
info.valuePush(i, propertyValue);
|
info.valuePush(i, propertyValue);
|
||||||
try {
|
try {
|
||||||
this.validate(propertyValue, containsSchema, info);
|
this._validate(propertyValue, containsSchema, info);
|
||||||
info.schemaPop();
|
info.schemaPop();
|
||||||
return;
|
return;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -458,7 +513,7 @@ class JsonSchemaValidator {
|
|||||||
throw new JsonSchemaValidationError('contains schema didn\'t match', value, schema, info);
|
throw new JsonSchemaValidationError('contains schema didn\'t match', value, schema, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
validateObject(value, schema, info) {
|
_validateObject(value, schema, info) {
|
||||||
const properties = new Set(Object.getOwnPropertyNames(value));
|
const properties = new Set(Object.getOwnPropertyNames(value));
|
||||||
|
|
||||||
const required = schema.required;
|
const required = schema.required;
|
||||||
@ -482,7 +537,7 @@ class JsonSchemaValidator {
|
|||||||
|
|
||||||
for (const property of properties) {
|
for (const property of properties) {
|
||||||
const schemaPath = [];
|
const schemaPath = [];
|
||||||
const propertySchema = this.getPropertySchema(schema, property, value, schemaPath);
|
const propertySchema = this._getPropertySchema(schema, property, value, schemaPath);
|
||||||
if (propertySchema === null) {
|
if (propertySchema === null) {
|
||||||
throw new JsonSchemaValidationError(`No schema found for ${property}`, value, schema, info);
|
throw new JsonSchemaValidationError(`No schema found for ${property}`, value, schema, info);
|
||||||
}
|
}
|
||||||
@ -491,18 +546,18 @@ class JsonSchemaValidator {
|
|||||||
|
|
||||||
for (const [p, s] of schemaPath) { info.schemaPush(p, s); }
|
for (const [p, s] of schemaPath) { info.schemaPush(p, s); }
|
||||||
info.valuePush(property, propertyValue);
|
info.valuePush(property, propertyValue);
|
||||||
this.validate(propertyValue, propertySchema, info);
|
this._validate(propertyValue, propertySchema, info);
|
||||||
info.valuePop();
|
info.valuePop();
|
||||||
for (let i = 0; i < schemaPath.length; ++i) { info.schemaPop(); }
|
for (let i = 0; i < schemaPath.length; ++i) { info.schemaPop(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isValueTypeAny(value, type, schemaTypes) {
|
_isValueTypeAny(value, type, schemaTypes) {
|
||||||
if (typeof schemaTypes === 'string') {
|
if (typeof schemaTypes === 'string') {
|
||||||
return this.isValueType(value, type, schemaTypes);
|
return this._isValueType(value, type, schemaTypes);
|
||||||
} else if (Array.isArray(schemaTypes)) {
|
} else if (Array.isArray(schemaTypes)) {
|
||||||
for (const schemaType of schemaTypes) {
|
for (const schemaType of schemaTypes) {
|
||||||
if (this.isValueType(value, type, schemaType)) {
|
if (this._isValueType(value, type, schemaType)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -511,14 +566,14 @@ class JsonSchemaValidator {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
isValueType(value, type, schemaType) {
|
_isValueType(value, type, schemaType) {
|
||||||
return (
|
return (
|
||||||
type === schemaType ||
|
type === schemaType ||
|
||||||
(schemaType === 'integer' && Math.floor(value) === value)
|
(schemaType === 'integer' && Math.floor(value) === value)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getValueType(value) {
|
_getValueType(value) {
|
||||||
const type = typeof value;
|
const type = typeof value;
|
||||||
if (type === 'object') {
|
if (type === 'object') {
|
||||||
if (value === null) { return 'null'; }
|
if (value === null) { return 'null'; }
|
||||||
@ -527,20 +582,20 @@ class JsonSchemaValidator {
|
|||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
valuesAreEqualAny(value1, valueList) {
|
_valuesAreEqualAny(value1, valueList) {
|
||||||
for (const value2 of valueList) {
|
for (const value2 of valueList) {
|
||||||
if (this.valuesAreEqual(value1, value2)) {
|
if (this._valuesAreEqual(value1, value2)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
valuesAreEqual(value1, value2) {
|
_valuesAreEqual(value1, value2) {
|
||||||
return value1 === value2;
|
return value1 === value2;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultTypeValue(type) {
|
_getDefaultTypeValue(type) {
|
||||||
if (typeof type === 'string') {
|
if (typeof type === 'string') {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'null':
|
case 'null':
|
||||||
@ -561,38 +616,7 @@ class JsonSchemaValidator {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getValidValueOrDefault(schema, value) {
|
_populateObjectDefaults(value, schema) {
|
||||||
let type = this.getValueType(value);
|
|
||||||
const schemaType = schema.type;
|
|
||||||
if (!this.isValueTypeAny(value, type, schemaType)) {
|
|
||||||
let assignDefault = true;
|
|
||||||
|
|
||||||
const schemaDefault = schema.default;
|
|
||||||
if (typeof schemaDefault !== 'undefined') {
|
|
||||||
value = JsonSchema.clone(schemaDefault);
|
|
||||||
type = this.getValueType(value);
|
|
||||||
assignDefault = !this.isValueTypeAny(value, type, schemaType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (assignDefault) {
|
|
||||||
value = this.getDefaultTypeValue(schemaType);
|
|
||||||
type = this.getValueType(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case 'object':
|
|
||||||
value = this.populateObjectDefaults(value, schema);
|
|
||||||
break;
|
|
||||||
case 'array':
|
|
||||||
value = this.populateArrayDefaults(value, schema);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
populateObjectDefaults(value, schema) {
|
|
||||||
const properties = new Set(Object.getOwnPropertyNames(value));
|
const properties = new Set(Object.getOwnPropertyNames(value));
|
||||||
|
|
||||||
const required = schema.required;
|
const required = schema.required;
|
||||||
@ -600,14 +624,14 @@ class JsonSchemaValidator {
|
|||||||
for (const property of required) {
|
for (const property of required) {
|
||||||
properties.delete(property);
|
properties.delete(property);
|
||||||
|
|
||||||
const propertySchema = this.getPropertySchema(schema, property, value);
|
const propertySchema = this._getPropertySchema(schema, property, value, null);
|
||||||
if (propertySchema === null) { continue; }
|
if (propertySchema === null) { continue; }
|
||||||
value[property] = this.getValidValueOrDefault(propertySchema, value[property]);
|
value[property] = this.getValidValueOrDefault(propertySchema, value[property]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const property of properties) {
|
for (const property of properties) {
|
||||||
const propertySchema = this.getPropertySchema(schema, property, value);
|
const propertySchema = this._getPropertySchema(schema, property, value, null);
|
||||||
if (propertySchema === null) {
|
if (propertySchema === null) {
|
||||||
Reflect.deleteProperty(value, property);
|
Reflect.deleteProperty(value, property);
|
||||||
} else {
|
} else {
|
||||||
@ -618,9 +642,9 @@ class JsonSchemaValidator {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
populateArrayDefaults(value, schema) {
|
_populateArrayDefaults(value, schema) {
|
||||||
for (let i = 0, ii = value.length; i < ii; ++i) {
|
for (let i = 0, ii = value.length; i < ii; ++i) {
|
||||||
const propertySchema = this.getPropertySchema(schema, i, value);
|
const propertySchema = this._getPropertySchema(schema, i, value, null);
|
||||||
if (propertySchema === null) { continue; }
|
if (propertySchema === null) { continue; }
|
||||||
value[i] = this.getValidValueOrDefault(propertySchema, value[i]);
|
value[i] = this.getValidValueOrDefault(propertySchema, value[i]);
|
||||||
}
|
}
|
||||||
@ -628,7 +652,7 @@ class JsonSchemaValidator {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
isObject(value) {
|
_isObject(value) {
|
||||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -679,22 +703,3 @@ class JsonSchemaValidationError extends Error {
|
|||||||
this.info = info;
|
this.info = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class JsonSchema {
|
|
||||||
static createProxy(target, schema) {
|
|
||||||
const validator = new JsonSchemaValidator();
|
|
||||||
return new Proxy(target, new JsonSchemaProxyHandler(schema, validator));
|
|
||||||
}
|
|
||||||
|
|
||||||
static validate(value, schema) {
|
|
||||||
return new JsonSchemaValidator().validate(value, schema, new JsonSchemaTraversalInfo(value, schema));
|
|
||||||
}
|
|
||||||
|
|
||||||
static getValidValueOrDefault(schema, value) {
|
|
||||||
return new JsonSchemaValidator().getValidValueOrDefault(schema, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
static clone(value) {
|
|
||||||
return clone(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -26,7 +26,7 @@ vm.execute([
|
|||||||
'mixed/js/cache-map.js',
|
'mixed/js/cache-map.js',
|
||||||
'bg/js/json-schema.js'
|
'bg/js/json-schema.js'
|
||||||
]);
|
]);
|
||||||
const JsonSchema = vm.get('JsonSchema');
|
const JsonSchemaValidator = vm.get('JsonSchemaValidator');
|
||||||
|
|
||||||
|
|
||||||
function readSchema(relativeFileName) {
|
function readSchema(relativeFileName) {
|
||||||
@ -45,7 +45,7 @@ async function validateDictionaryBanks(zip, fileNameFormat, schema) {
|
|||||||
if (!file) { break; }
|
if (!file) { break; }
|
||||||
|
|
||||||
const data = JSON.parse(await file.async('string'));
|
const data = JSON.parse(await file.async('string'));
|
||||||
JsonSchema.validate(data, schema);
|
new JsonSchemaValidator().validate(data, schema);
|
||||||
|
|
||||||
++index;
|
++index;
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ async function validateDictionary(archive, schemas) {
|
|||||||
const index = JSON.parse(await indexFile.async('string'));
|
const index = JSON.parse(await indexFile.async('string'));
|
||||||
const version = index.format || index.version;
|
const version = index.format || index.version;
|
||||||
|
|
||||||
JsonSchema.validate(index, schemas.index);
|
new JsonSchemaValidator().validate(index, schemas.index);
|
||||||
|
|
||||||
await validateDictionaryBanks(archive, 'term_bank_?.json', version === 1 ? schemas.termBankV1 : schemas.termBankV3);
|
await validateDictionaryBanks(archive, 'term_bank_?.json', version === 1 ? schemas.termBankV1 : schemas.termBankV3);
|
||||||
await validateDictionaryBanks(archive, 'term_meta_bank_?.json', schemas.termMetaBankV3);
|
await validateDictionaryBanks(archive, 'term_meta_bank_?.json', schemas.termMetaBankV3);
|
||||||
|
@ -24,7 +24,7 @@ vm.execute([
|
|||||||
'mixed/js/cache-map.js',
|
'mixed/js/cache-map.js',
|
||||||
'bg/js/json-schema.js'
|
'bg/js/json-schema.js'
|
||||||
]);
|
]);
|
||||||
const JsonSchema = vm.get('JsonSchema');
|
const JsonSchemaValidator = vm.get('JsonSchemaValidator');
|
||||||
|
|
||||||
|
|
||||||
function main() {
|
function main() {
|
||||||
@ -45,7 +45,7 @@ function main() {
|
|||||||
console.log(`Validating ${dataFileName}...`);
|
console.log(`Validating ${dataFileName}...`);
|
||||||
const dataSource = fs.readFileSync(dataFileName, {encoding: 'utf8'});
|
const dataSource = fs.readFileSync(dataFileName, {encoding: 'utf8'});
|
||||||
const data = JSON.parse(dataSource);
|
const data = JSON.parse(dataSource);
|
||||||
JsonSchema.validate(data, schema);
|
new JsonSchemaValidator().validate(data, schema);
|
||||||
console.log('No issues found');
|
console.log('No issues found');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
@ -24,7 +24,7 @@ vm.execute([
|
|||||||
'mixed/js/cache-map.js',
|
'mixed/js/cache-map.js',
|
||||||
'bg/js/json-schema.js'
|
'bg/js/json-schema.js'
|
||||||
]);
|
]);
|
||||||
const JsonSchema = vm.get('JsonSchema');
|
const JsonSchemaValidator = vm.get('JsonSchemaValidator');
|
||||||
|
|
||||||
|
|
||||||
function testValidate1() {
|
function testValidate1() {
|
||||||
@ -54,12 +54,7 @@ function testValidate1() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const schemaValidate = (value) => {
|
const schemaValidate = (value) => {
|
||||||
try {
|
return new JsonSchemaValidator().isValid(value, schema);
|
||||||
JsonSchema.validate(value, schema);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const jsValidate = (value) => {
|
const jsValidate = (value) => {
|
||||||
@ -395,12 +390,7 @@ function testValidate2() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
const schemaValidate = (value, schema) => {
|
const schemaValidate = (value, schema) => {
|
||||||
try {
|
return new JsonSchemaValidator().isValid(value, schema);
|
||||||
JsonSchema.validate(value, schema);
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const {schema, inputs} of data) {
|
for (const {schema, inputs} of data) {
|
||||||
@ -555,7 +545,7 @@ function testGetValidValueOrDefault1() {
|
|||||||
|
|
||||||
for (const {schema, inputs} of data) {
|
for (const {schema, inputs} of data) {
|
||||||
for (const [value, expected] of inputs) {
|
for (const [value, expected] of inputs) {
|
||||||
const actual = JsonSchema.getValidValueOrDefault(schema, value);
|
const actual = new JsonSchemaValidator().getValidValueOrDefault(schema, value);
|
||||||
vm.assert.deepStrictEqual(actual, expected);
|
vm.assert.deepStrictEqual(actual, expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user