summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2020-08-11 19:21:26 -0400
committerGitHub <noreply@github.com>2020-08-11 19:21:26 -0400
commit587822c16e3f573362fdfe291c9afc37ca31bb15 (patch)
tree4c135921ec532027921db0f11ccaf715bd55c622
parentabfa0362dd51d2f0864a3e73aa84cbba11040ca7 (diff)
More JSON schema improvements (#729)
* Add support for constant values * Add contains check for arrays * Add tests * Simplify getValidValueOrDefault testing
-rw-r--r--ext/bg/js/json-schema.js27
-rw-r--r--test/test-schema.js491
2 files changed, 371 insertions, 147 deletions
diff --git a/ext/bg/js/json-schema.js b/ext/bg/js/json-schema.js
index b777ac09..7cc87bb0 100644
--- a/ext/bg/js/json-schema.js
+++ b/ext/bg/js/json-schema.js
@@ -326,6 +326,11 @@ class JsonSchemaValidator {
throw new JsonSchemaValidationError(`Value type ${type} does not match schema type ${schemaType}`, value, schema, info);
}
+ const schemaConst = schema.const;
+ if (typeof schemaConst !== 'undefined' && !this.valuesAreEqual(value, schemaConst)) {
+ throw new JsonSchemaValidationError('Invalid constant value', value, schema, info);
+ }
+
const schemaEnum = schema.enum;
if (Array.isArray(schemaEnum) && !this.valuesAreEqualAny(value, schemaEnum)) {
throw new JsonSchemaValidationError('Invalid enum value', value, schema, info);
@@ -414,6 +419,8 @@ class JsonSchemaValidator {
throw new JsonSchemaValidationError('Array length too long', value, schema, info);
}
+ this.validateArrayContains(value, schema, info);
+
for (let i = 0, ii = value.length; i < ii; ++i) {
const schemaPath = [];
const propertySchema = this.getPropertySchema(schema, i, value, schemaPath);
@@ -431,6 +438,26 @@ class JsonSchemaValidator {
}
}
+ validateArrayContains(value, schema, info) {
+ const containsSchema = 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
+ }
+ info.valuePop();
+ }
+ throw new JsonSchemaValidationError('contains schema didn\'t match', value, schema, info);
+ }
+
validateObject(value, schema, info) {
const properties = new Set(Object.getOwnPropertyNames(value));
diff --git a/test/test-schema.js b/test/test-schema.js
index b976d81f..b3544b28 100644
--- a/test/test-schema.js
+++ b/test/test-schema.js
@@ -185,6 +185,212 @@ function testValidate2() {
inputs: [
{expected: false, value: ''}
]
+ },
+
+ // Const tests
+ {
+ schema: {
+ const: 32
+ },
+ inputs: [
+ {expected: true, value: 32},
+ {expected: false, value: 0},
+ {expected: false, value: '32'},
+ {expected: false, value: null},
+ {expected: false, value: {a: 'b'}},
+ {expected: false, value: [1, 2, 3]}
+ ]
+ },
+ {
+ schema: {
+ const: '32'
+ },
+ inputs: [
+ {expected: false, value: 32},
+ {expected: false, value: 0},
+ {expected: true, value: '32'},
+ {expected: false, value: null},
+ {expected: false, value: {a: 'b'}},
+ {expected: false, value: [1, 2, 3]}
+ ]
+ },
+ {
+ schema: {
+ const: null
+ },
+ inputs: [
+ {expected: false, value: 32},
+ {expected: false, value: 0},
+ {expected: false, value: '32'},
+ {expected: true, value: null},
+ {expected: false, value: {a: 'b'}},
+ {expected: false, value: [1, 2, 3]}
+ ]
+ },
+ {
+ schema: {
+ const: {a: 'b'}
+ },
+ inputs: [
+ {expected: false, value: 32},
+ {expected: false, value: 0},
+ {expected: false, value: '32'},
+ {expected: false, value: null},
+ {expected: false, value: {a: 'b'}},
+ {expected: false, value: [1, 2, 3]}
+ ]
+ },
+ {
+ schema: {
+ const: [1, 2, 3]
+ },
+ inputs: [
+ {expected: false, value: 32},
+ {expected: false, value: 0},
+ {expected: false, value: '32'},
+ {expected: false, value: null},
+ {expected: false, value: {a: 'b'}},
+ {expected: false, value: [1, 2, 3]}
+ ]
+ },
+
+ // Array contains tests
+ {
+ schema: {
+ type: 'array',
+ contains: {const: 32}
+ },
+ inputs: [
+ {expected: false, value: []},
+ {expected: true, value: [32]},
+ {expected: true, value: [1, 32]},
+ {expected: true, value: [1, 32, 1]},
+ {expected: false, value: [33]},
+ {expected: false, value: [1, 33]},
+ {expected: false, value: [1, 33, 1]}
+ ]
+ },
+
+ // Number limits tests
+ {
+ schema: {
+ type: 'number',
+ minimum: 0
+ },
+ inputs: [
+ {expected: false, value: -1},
+ {expected: true, value: 0},
+ {expected: true, value: 1}
+ ]
+ },
+ {
+ schema: {
+ type: 'number',
+ exclusiveMinimum: 0
+ },
+ inputs: [
+ {expected: false, value: -1},
+ {expected: false, value: 0},
+ {expected: true, value: 1}
+ ]
+ },
+ {
+ schema: {
+ type: 'number',
+ maximum: 0
+ },
+ inputs: [
+ {expected: true, value: -1},
+ {expected: true, value: 0},
+ {expected: false, value: 1}
+ ]
+ },
+ {
+ schema: {
+ type: 'number',
+ exclusiveMaximum: 0
+ },
+ inputs: [
+ {expected: true, value: -1},
+ {expected: false, value: 0},
+ {expected: false, value: 1}
+ ]
+ },
+
+ // Integer limits tests
+ {
+ schema: {
+ type: 'integer',
+ minimum: 0
+ },
+ inputs: [
+ {expected: false, value: -1},
+ {expected: true, value: 0},
+ {expected: true, value: 1}
+ ]
+ },
+ {
+ schema: {
+ type: 'integer',
+ exclusiveMinimum: 0
+ },
+ inputs: [
+ {expected: false, value: -1},
+ {expected: false, value: 0},
+ {expected: true, value: 1}
+ ]
+ },
+ {
+ schema: {
+ type: 'integer',
+ maximum: 0
+ },
+ inputs: [
+ {expected: true, value: -1},
+ {expected: true, value: 0},
+ {expected: false, value: 1}
+ ]
+ },
+ {
+ schema: {
+ type: 'integer',
+ exclusiveMaximum: 0
+ },
+ inputs: [
+ {expected: true, value: -1},
+ {expected: false, value: 0},
+ {expected: false, value: 1}
+ ]
+ },
+
+ // Numeric type tests
+ {
+ schema: {
+ type: 'number'
+ },
+ inputs: [
+ {expected: true, value: 0},
+ {expected: true, value: 0.5},
+ {expected: true, value: 1},
+ {expected: false, value: '0'},
+ {expected: false, value: null},
+ {expected: false, value: []},
+ {expected: false, value: {}}
+ ]
+ },
+ {
+ schema: {
+ type: 'integer'
+ },
+ inputs: [
+ {expected: true, value: 0},
+ {expected: false, value: 0.5},
+ {expected: true, value: 1},
+ {expected: false, value: '0'},
+ {expected: false, value: null},
+ {expected: false, value: []},
+ {expected: false, value: {}}
+ ]
}
];
@@ -207,158 +413,151 @@ function testValidate2() {
function testGetValidValueOrDefault1() {
- // Test value defaulting on objects with additionalProperties=false
- const schema = {
- type: 'object',
- required: ['test'],
- properties: {
- test: {
- type: 'string',
- default: 'default'
- }
+ const data = [
+ // Test value defaulting on objects with additionalProperties=false
+ {
+ schema: {
+ type: 'object',
+ required: ['test'],
+ properties: {
+ test: {
+ type: 'string',
+ default: 'default'
+ }
+ },
+ additionalProperties: false
+ },
+ inputs: [
+ [
+ void 0,
+ {test: 'default'}
+ ],
+ [
+ null,
+ {test: 'default'}
+ ],
+ [
+ 0,
+ {test: 'default'}
+ ],
+ [
+ '',
+ {test: 'default'}
+ ],
+ [
+ [],
+ {test: 'default'}
+ ],
+ [
+ {},
+ {test: 'default'}
+ ],
+ [
+ {test: 'value'},
+ {test: 'value'}
+ ],
+ [
+ {test2: 'value2'},
+ {test: 'default'}
+ ],
+ [
+ {test: 'value', test2: 'value2'},
+ {test: 'value'}
+ ]
+ ]
},
- additionalProperties: false
- };
-
- const testData = [
- [
- void 0,
- {test: 'default'}
- ],
- [
- null,
- {test: 'default'}
- ],
- [
- 0,
- {test: 'default'}
- ],
- [
- '',
- {test: 'default'}
- ],
- [
- [],
- {test: 'default'}
- ],
- [
- {},
- {test: 'default'}
- ],
- [
- {test: 'value'},
- {test: 'value'}
- ],
- [
- {test2: 'value2'},
- {test: 'default'}
- ],
- [
- {test: 'value', test2: 'value2'},
- {test: 'value'}
- ]
- ];
-
- for (const [value, expected] of testData) {
- const actual = JsonSchema.getValidValueOrDefault(schema, value);
- vm.assert.deepStrictEqual(actual, expected);
- }
-}
-function testGetValidValueOrDefault2() {
- // Test value defaulting on objects with additionalProperties=true
- const schema = {
- type: 'object',
- required: ['test'],
- properties: {
- test: {
- type: 'string',
- default: 'default'
- }
+ // Test value defaulting on objects with additionalProperties=true
+ {
+ schema: {
+ type: 'object',
+ required: ['test'],
+ properties: {
+ test: {
+ type: 'string',
+ default: 'default'
+ }
+ },
+ additionalProperties: true
+ },
+ inputs: [
+ [
+ {},
+ {test: 'default'}
+ ],
+ [
+ {test: 'value'},
+ {test: 'value'}
+ ],
+ [
+ {test2: 'value2'},
+ {test: 'default', test2: 'value2'}
+ ],
+ [
+ {test: 'value', test2: 'value2'},
+ {test: 'value', test2: 'value2'}
+ ]
+ ]
},
- additionalProperties: true
- };
- const testData = [
- [
- {},
- {test: 'default'}
- ],
- [
- {test: 'value'},
- {test: 'value'}
- ],
- [
- {test2: 'value2'},
- {test: 'default', test2: 'value2'}
- ],
- [
- {test: 'value', test2: 'value2'},
- {test: 'value', test2: 'value2'}
- ]
- ];
-
- for (const [value, expected] of testData) {
- const actual = JsonSchema.getValidValueOrDefault(schema, value);
- vm.assert.deepStrictEqual(actual, expected);
- }
-}
-
-function testGetValidValueOrDefault3() {
- // Test value defaulting on objects with additionalProperties={schema}
- const schema = {
- type: 'object',
- required: ['test'],
- properties: {
- test: {
- type: 'string',
- default: 'default'
- }
- },
- additionalProperties: {
- type: 'number',
- default: 10
+ // Test value defaulting on objects with additionalProperties={schema}
+ {
+ schema: {
+ type: 'object',
+ required: ['test'],
+ properties: {
+ test: {
+ type: 'string',
+ default: 'default'
+ }
+ },
+ additionalProperties: {
+ type: 'number',
+ default: 10
+ }
+ },
+ inputs: [
+ [
+ {},
+ {test: 'default'}
+ ],
+ [
+ {test: 'value'},
+ {test: 'value'}
+ ],
+ [
+ {test2: 'value2'},
+ {test: 'default', test2: 10}
+ ],
+ [
+ {test: 'value', test2: 'value2'},
+ {test: 'value', test2: 10}
+ ],
+ [
+ {test2: 2},
+ {test: 'default', test2: 2}
+ ],
+ [
+ {test: 'value', test2: 2},
+ {test: 'value', test2: 2}
+ ],
+ [
+ {test: 'value', test2: 2, test3: null},
+ {test: 'value', test2: 2, test3: 10}
+ ],
+ [
+ {test: 'value', test2: 2, test3: void 0},
+ {test: 'value', test2: 2, test3: 10}
+ ]
+ ]
}
- };
-
- const testData = [
- [
- {},
- {test: 'default'}
- ],
- [
- {test: 'value'},
- {test: 'value'}
- ],
- [
- {test2: 'value2'},
- {test: 'default', test2: 10}
- ],
- [
- {test: 'value', test2: 'value2'},
- {test: 'value', test2: 10}
- ],
- [
- {test2: 2},
- {test: 'default', test2: 2}
- ],
- [
- {test: 'value', test2: 2},
- {test: 'value', test2: 2}
- ],
- [
- {test: 'value', test2: 2, test3: null},
- {test: 'value', test2: 2, test3: 10}
- ],
- [
- {test: 'value', test2: 2, test3: void 0},
- {test: 'value', test2: 2, test3: 10}
- ]
];
- for (const [value, expected] of testData) {
- const actual = JsonSchema.getValidValueOrDefault(schema, value);
- vm.assert.deepStrictEqual(actual, expected);
+ for (const {schema, inputs} of data) {
+ for (const [value, expected] of inputs) {
+ const actual = JsonSchema.getValidValueOrDefault(schema, value);
+ vm.assert.deepStrictEqual(actual, expected);
+ }
}
}
@@ -367,8 +566,6 @@ function main() {
testValidate1();
testValidate2();
testGetValidValueOrDefault1();
- testGetValidValueOrDefault2();
- testGetValidValueOrDefault3();
}